@zhouyy
2018-03-10T14:39:21.000000Z
字数 4984
阅读 1375
<head><link rel="import" href="/path/to/imports/stuff.html"></head>
<!-- 其他域内的资源必须允许 CORS --><link rel="import" href="http://example.com/elements.html">
function supportsImports() {return 'import' in document.createElement('link');}if (supportsImports()) {// 支持导入!} else {// 使用其他的库来加载文件。}
This page is under construction
This content may be out of date
你可以获取导入文档中的一部分并把它们复制到当前页面中:
...
var link = document.querySelector('link[rel="import"]');
var content = link.import;
// 从 warning.html 的文档中获取 DOM。
var el = content.querySelector('.warning');
document.body.appendChild(el.cloneNode(true));
在导入中使用脚本
导入的内容并不在主文档中。它们仅仅作为主文档的附属而存在。即便如此,导入的内容还是能够在主页面中生效。导入能够访问它自己的 DOM 或/和包含它的页面中的 DOM:
示例 - import.html 向主页面中添加它自己的样式表
...
留意这里的操作。导入中的脚本获得了导入文档的引用 (document.currentScript.ownerDocument),随后将导入文档中的部分内容附加到了主页面中 (mainDoc.head.appendChild(...))。这段代码看起来不怎么优雅。
导入中的脚本要么直接运行代码,要么就定义个函数留给主页面使用。这很像 Python 中模块定义的方式。
导入中 JavaScript 的规则:
导入中的脚本会在包含导入文档的 window 上下文中运行。因此 window.document 关联的是主页面文档。这会产生两个有用的推论:
导入中定义的函数最终会出现在 window 上。
你不用将导入文档中的 块附加到主页面。再重申一遍,脚本会自动执行。
导入不会阻塞主页面的解析。不过,导入文档中的脚本会按照顺序执行。它们对于主页面来说就像拥有了延迟(defer)执行的行为。后面会详细讲解。
传输 Web Component
HTML 导入的设计很好的契合了在 web 上加载重用资源的需求。尤其是对于分发 Web Component。无论是基本的 HTML 还是十分成熟的自定义元素/Shadow DOM [1,2,3]。当把这些技术结合在一起使用时,导入就充当了 Web Component 中 #include 的角色。
包含模板
HTML Template 元素是 HTML 导入的好搭档。 特别适合于为需要导入的应用搭建必要的标记。将内容包裹在一个 元素中还为你提供了延迟加载内容的好处。也就是说,在 template 元素加入到 DOM 之前,它包含的脚本不会执行。
import.html
// 从导入中复制 <template>。
var template = link.import.querySelector('template');
var content = template.content.cloneNode(true)
document.querySelector('#container').appendChild(content);
注册自定义元素
自定义元素是 Web Component 技术中的另一位成员,它和 HTML 导入也是出奇的搭配。导入能够运行脚本,既然如此,为什么不定义 + 注册你自己的自定义元素,这样一来用户就避免重复操作了呢? 让我们就叫它..."自动注册(auto-registration)"。
elements.html
' + (this.getAttribute('name') || '?') + '这个导入定义 (并注册) 了两个元素, 和 。主页面可以直接使用它们,无需做任何额外操作。
index.html
( I'm in the light dom )
在我看来,这样的工作流程使得 HTML 导入成为了共享 Web Components 的理想方式。
管理依赖和子导入
嘿。听说你挺喜欢导入, 所以我就在你的导入里又加了个导入。
子导入(Sub-imports)
若导入能够嵌套将会提供更多便利。例如,如果你想复用或继承另一个组件,使用导入加载其他元素。
下面是 Polymer 中的真实例子。通过复用布局还有选择器组件,我们得到了一个新的选项卡组件 ()。它们的依赖通过 HTML 导入来管理。
polymer-ui-tabs.html
完整源码
应用开发者可以引入这个新元素:
若以后出现了一个更新,更棒的 ,你就可以毫不犹豫的用它替换 。多亏有了导入和 web 组件,你再也不用担心惹恼你的用户了。
依赖管理
我们都知道一个页面载入多个 jQuery 会出问题。若是多个组件引用了相同的库,对于 Web 组件来说会不会是个严重的问题? 如果使用 HTML 引用,你就完全不用担心! 导入可以用来管理这些依赖。
将库放进一个 HTML 导入中,就自动避免了重复加载问题。文档只会被解析一次。脚本也只执行一次。来举个例子吧,比如说你定义了一个导入,jquery.html,它会加载 JQuery。
jquery.html
若主页面也需要这个库,连它也可以包含 jquery.html:
...
尽管 jquery.html 被加进了多个导入树中,浏览器也只会获取一次它的文档。查看网络面板就能证明这一切:
jquery.html is requested once
性能注意事项
HTML 导入绝对是个好东西,但就像许多其他新技术一样,你得明智的去使用它。Web 开发的最佳实践还是需要遵守。下面是一些需要留意的地方。
合并导入
减少网络请求始终是重点。如果需要很多最顶层的导入,那就考虑把它们合并在一个资源里,然后导入该资源!
Vulcanizer 是由 Polymer 团队开发的 npm 构建工具,它能够递归的展开一组 HTML 导入并生成一个单独的文件。可以把它看成构建 Web 组件中合并的步骤。
导入影响浏览器缓存
许多人似乎都忘记了浏览器的网络协议栈经过了多年的精心调整。导入 (包括子导入) 也从中受益。导入 http://cdn.com/bootstrap.html 可能包含子资源,但它们都将被缓存起来。
内容只有在被添加后才是可用的
把导入的内容看成是惰性的,只有当你调用它的服务时它才生效。 看看这个动态创建的样式表:
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'styles.css';
在 link 被加入到 DOM 之前,浏览器不会去请求 styles.css:
document.head.appendChild(link); // 浏览器请求 styles.css
另一个例子就是动态创建标签:
var h2 = document.createElement('h2');
h2.textContent = 'Booyah!';
在你把 h2 添加到 DOM 之前它没有意义。
同样的概念对于导入文档也适用。在你将内容追加到 DOM 之前,它就是一个空操作。实际上,在导入文档中直接 "运行" 的只有 。参见导入中的脚本操作。
优化异步载入
导入不会阻塞主页面解析。导入中的脚本会按照顺序执行,但也不会阻塞主页面。这意味着你在维护脚本顺序时获得了类似于延迟加载的行为。将导入放到 的好处在于它可以让解析器尽快的去解析导入的内容。即便如此,你还得记得主页面中的 仍然 会阻塞页面:
console.log('I block page rendering');
根据你的应用架构和使用场景不同,有几种方法可以优化异步行为。下面要使用的技巧可以缓解对主页面渲染的阻塞。
场景 #1 (推荐): 中没有脚本或 没有内联脚本
我对放置 的建议就是不要紧跟着你的导入。把它们尽可能远的放置...你肯定早就按照最佳实践这么做了,不是吗!?;)
看个例子:
...
// 其他的脚本。
// 获得导入内容。
var link = document.querySelector('link[rel="import"]');
var post = link.import.querySelector('#blog-post');
var container = document.querySelector('#container');
container.appendChild(post.cloneNode(true));
所有内容都放到底部。
场景 1.5: 导入添加自己的内容
另一个选择是让导入添加自己的内容. 若导入的作者和应用开发者之间达成了某种约定,那么导入就可以将它自身加入到主页面的某个位置:
import.html:
index.html
场景 #2: 或 中有(内联)脚本
若某个导入的加载需要耗费很长时间,跟在导入后面的第一个 将会阻塞页面渲染。以 Google Analytics 为例,它推荐将跟踪代码放在 中,若你必须将 放到 中,那么动态的添加导入将会避免阻塞页面:
function addImportLink(url) {
var link = document.createElement('link');
link.rel = 'import';
link.href = url;
link.onload = function(e) {
var post = this.import.querySelector('#blog-post');
var container = document.querySelector('#container');
container.appendChild(post.cloneNode(true));
};
document.head.appendChild(link);
}
addImportLink('/path/to/import.html'); // 导入被提前添加 :)
// 其他脚本
// 其他脚本
function addImportLink(url) { ... }
addImportLink('/path/to/import.html'); // 导入很晚才能被添加 :(
注意: 不推荐最后的方法。解析器在解析页面结束之前不会去操作导入的内容。
要点
导入的 MIME 类型是 text/html。
导入跨域资源需要启用 CORS。
来自相同 URL 的导入仅获取和解析一次。这表示导入中的脚本只在第一次导入的时候执行。
导入中的脚本按顺序执行,它们不会阻塞主页面解析。
导入链接不代表 "#把内容添加到这里"。它代表 "解析器,去把这个文档取过来,我一会要用"。脚本在导入期间运行,而样式,标记,还有其他资源需要明确的加入到主页面中。这是 HTML 导入和 之间的最大区别,后者表示 "在这里加载并渲染资源"。
总结
HTML 导入允许将 HTML/CSS/JS 打包成一个单独资源。这个想法在 Web 组件开发世界中显得极为重要。开发者可以创建重用的组件,其他人通过引入 就能够在自己的应用中使用这些组件。
HTML 导入是个简单的概念,但却促成了许多有趣的使用案例。
使用案例
将相关的HTML/CSS/JS 作为一个单独的包 来分发。理论上来说,你可以在应用里面导入一个完整的 web 应用。
代码组织 - 将概念按照逻辑划分为不同的文件,鼓励模块化 & 复用性**。
传输 一或多个自定义元素 的定义。可以在应用内使用导入来注册 和包含自定义元素。这符合良好的软件模式,即将接口/定义与使用分离。
管理依赖 - 自动解决资源的重复加载。
脚本块 - 没有导入之前,一个大型的 JS 库需要在使用前全部解析,这通常很慢。有了导入,只要块 A 解析完毕,库就能够立即使用。延迟更少了!
:
...
并行 HTML 解析 - 这是首次能够让浏览器并行运行两个 (或多个) HTML 解析器。
允许在调试和非调试模式下切换,只需要修改导入的目标。你的应用无需知道导入的目标是打包/编译好的资源还是一棵导入树。