为何须要导入? 先想一想你在 web 上是如何加载不一样类型的资源。对于 JS,咱们有 <script src>
。<link rel="stylesheet">
应该是 CSS 的首选。图片能够用 <img>
。视频则有 <video>
。音频,<audio>
…… 你明白我在说什么了吧! web 上绝大部分的内容都有简单明了的加载方式。可对于 HTML 呢?下面是可选的几种方案:javascript
<iframe>
- 可用但笨重。iframe 中的内容所有存在于一个不一样于当前页的独立上下文中。这是个很棒的特性,但也为开发者们带来了额外的挑战 (将 frame 按照内容尺寸来缩放已经有点难度,在 iframe 和当前页面之间写点 JS 能把人绕晕,更别提操做样式了)。 AJAX - 我喜欢 xhr.responseType="document",但是加载 HTML 要用 JS? 这就不大对劲了。 CrazyHacks™ - 用字符串的方式嵌入页面,像注释同样隐藏 (例如 <script type="text/html">
)。呕! 好笑不? 做为 web 上最基础的内容,HTML,居然须要这么麻烦才能获得咱们想要的结果。幸运的是,Web Components 总算找到了一条正确的路。css
开始html
HTML 导入,Web Components 阵容中的一员,是在其余 HTML 文档中包含 HTML 文档的一种方法。固然并不是仅限于此,你还能够包含 CSS,JavaScript,或 .html 文件中能包含的任何内容。换句话说,这使得导入成为了加载相关 HTML/CSS/JS 的神器。java
基础jquery
经过声明 <link rel="import">
来在页面中包含一个导入 :web
<head> <link rel="import" href="/path/to/imports/stuff.html"> </head>
导入中的 URL 被称为 导入地址。若想跨域导入内容,导入地址必须容许 CORS:ajax
<!-- 其余域内的资源必须容许 CORS --> <link rel="import" href="http://example.com/elements.html">
浏览器的网络协议栈(network stack)会对访问相同 URL 的请求自动去重。这意味着从同一个 URL 导入的内容只会被获取一次。不管这个地址被导入多少次,最终它将只执行一次。npm
特性检测与支持bootstrap
要检测浏览器是否支持导入,可验证 <link> 元素上是否存在 import:跨域
function supportsImports() { return 'import' in document.createElement('link'); } if (supportsImports()) { // 支持导入! } else { // 使用其余的库来加载文件。 }
目前支持该特性的浏览器比较有限。Chrome 31 最早实现了该特性。你能够在 about:flags 页面中启用 Enable HTML Imports。对于其余浏览器可使用 Polymer 的 polyfill。
在 about:flags 中 启用 HTML Imports。 开启 experimental Web Platform features 能够体验 web component 中的其余实验特性。
打包资源
可使用导入将 HTML/CSS/JS (甚至其余 HTML 导入) 打包成一个单独的可传递文件。这是个不容忽视的特色。假设你写了一套主题,库,或仅仅想把你的应用按照逻辑拆分,你也仅需给其余人提供一个 URL。天呐,你甚至能够用导入来传输整个应用,想一想这该有多棒。
仅用一个 URL,你就能够将多个文件打包成一个文件提供给他人使用。 一个现实中的例子是 Bootstrap。Bootstrap 由多个单独的文件组成 (bootstrap.css,bootstrap.js,字体), 它的插件依赖于 jQuery,并提供了带标记的例子。开发者们喜欢拥有像去餐厅点菜同样的灵活性。这容许开发者只加载框架中 他们 想用的内容。
导入对于相似 Bootstrap 的内容来讲意义非凡,下面我将展现将来加载 Bootstrap 的方式:
<head> <link rel="import" href="bootstrap.html"> </head>
用户只需加载一个 HTML Import 连接。他们不再用为那些乱七八糟的文件而烦心。相反,整个 Bootstrap 都将包裹在一个导入 bootstrap.html 之中:
<link rel="stylesheet" href="bootstrap.css"> <link rel="stylesheet" href="fonts.css"> <script src="jquery.js"></script> <script src="bootstrap.js"></script> <script src="bootstrap-tooltip.js"></script> <script src="bootstrap-dropdown.js"></script> ... <!-- 脚手架标记 --> <template> ... </template>
让这一切都快点变成现实吧,这玩意简直太棒了!
Load/error 事件
当导入成功时 <link> 元素会触发 load 事件,加载失败时 (例如资源出现 404) 则会触发 error。
导入会尝试当即加载。一个简单的办法是使用 onload/onerror 特性:
<script async> function handleLoad(e) { console.log('Loaded import: ' + e.target.href); } function handleError(e) { console.log('Error loading import: ' + e.target.href); } </script> <link rel="import" href="file.html" onload="handleLoad(event)" onerror="handleError(event)">
注意上面事件处理的定义要早于导入开始加载页面。浏览器一旦解析到导入的标签,它就会当即加载资源。若是此时处理函数不存在,你将在控制台看到函数名未定义的错误。
或者,你能够动态建立导入:
var link = document.createElement('link'); link.rel = 'import'; link.href = 'file.html' link.onload = function(e) {...}; link.onerror = function(e) {...}; document.head.appendChild(link);
使用内容
在页面中包含导入并不意味着 ”把那个文件的内容都塞到这”。它表示 ”解析器,去把这个文档给我取回来好让我用”。若想真正的使用该文档的内容,你得写点脚本。
当你意识到导入就是一个文档时,你确定会 啊哈! 一声。事实上,导入的内容被称为 导入文档。你能够 使用标准的 DOM API 来操做导入的内容!
link.import
若想访问导入的内容,须要使用 link 元素的 import 属性:
var content = document.querySelector('link[rel="import"]').import;
在下面几种状况下,link.import 值为 null :
浏览器不支持 HTML 导入。 <link>
没有 rel="import"。 <link>
没有被加入到 DOM 中。 <link>
从 DOM 中被移除。 资源没有开启 CORS。 完整示例
假设 warnings.html 包含以下内容:
<div class="warning"> <style scoped> h3 { color: red; } </style> <h3>Warning!</h3> <p>This page is under construction</p> </div> <div class="outdated"> <h3>Heads up!</h3> <p>This content may be out of date</p> </div>
你能够获取导入文档中的一部分并把它们复制到当前页面中:
<head> <link rel="import" href="warnings.html"> </head> <body> ... <script> 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)); </script> </body>
在导入中使用脚本
导入的内容并不在主文档中。它们仅仅做为主文档的附属而存在。即使如此,导入的内容仍是可以在主页面中生效。导入可以访问它本身的 DOM 或/和包含它的页面中的 DOM:
示例 - import.html 向主页面中添加它本身的样式表
<link rel="stylesheet" href="http://www.example.com/styles.css"> <link rel="stylesheet" href="http://www.example.com/styles2.css"> ... <script> // importDoc 是导入文档的引用 var importDoc = document.currentScript.ownerDocument; // mainDoc 是主文档(包含导入的页面)的引用 var mainDoc = document; // 获取导入中的第一个样式表,复制, // 将它附加到主文档中。 var styles = importDoc.querySelector('link[rel="stylesheet"]'); mainDoc.head.appendChild(styles.cloneNode(true)); </script>
留意这里的操做。导入中的脚本得到了导入文档的引用 (document.currentScript.ownerDocument),随后将导入文档中的部份内容附加到了主页面中 (mainDoc.head.appendChild(…))。这段代码看起来不怎么优雅。
导入中的脚本要么直接运行代码,要么就定义个函数留给主页面使用。这很像 Python 中模块定义的方式。
导入中 JavaScript 的规则: 导入中的脚本会在包含导入文档的 window 上下文中运行。所以 window.document 关联的是主页面文档。这会产生两个有用的推论: 导入中定义的函数最终会出如今 window 上。 你不用将导入文档中的 <script>
块附加到主页面。再重申一遍,脚本会自动执行。 导入不会阻塞主页面的解析。不过,导入文档中的脚本会按照顺序执行。它们对于主页面来讲就像拥有了延迟(defer)执行的行为。后面会详细讲解。 传输 Web Component
HTML 导入的设计很好的契合了在 web 上加载重用资源的需求。尤为是对于分发 Web Component。不管是基本的 HTML <template>
仍是十分红熟的自定义元素/Shadow DOM [1,2,3]。当把这些技术结合在一块儿使用时,导入就充当了 Web Component 中 #include 的角色。 包含模板 HTML Template 元素是 HTML 导入的好搭档。<template>
特别适合于为须要导入的应用搭建必要的标记。将内容包裹在一个 <template>
元素中还为你提供了延迟加载内容的好处。也就是说,在 template 元素加入到 DOM 以前,它包含的脚本不会执行。 import.html
<template> <h1>Hello World!</h1> <img src="world.png"> <!-- 只有当模板生效后才会去请求图片 --> <script>alert("Executed when the template is activated.");</script> </template>
index.html
<head> <link rel="import" href="import.html"> </head> <body> <div id="container"></div> <script> var link = document.querySelector('link[rel="import"]'); // 从导入中复制 <template>。 var template = link.import.querySelector('template'); var content = template.content.cloneNode(true) document.querySelector('#container').appendChild(content); </script> </body>
注册自定义元素 自定义元素是 Web Component 技术中的另外一位成员,它和 HTML 导入也是出奇的搭配。导入可以运行脚本,既然如此,为何不定义 + 注册你本身的自定义元素,这样一来用户就避免重复操做了呢? 让咱们就叫它..."自动注册(auto-registration)"。
elements.html
<script> // 定义并注册 <say-hi>。 var proto = Object.create(HTMLElement.prototype); proto.createdCallback = function() { this.innerHTML = 'Hello, <b>' + (this.getAttribute('name') || '?') + '</b>'; }; document.register('say-hi', {prototype: proto}); // 定义并注册使用了 Shadow DOM 的 <shadow-element>。 var proto2 = Object.create(HTMLElement.prototype); proto2.createdCallback = function() { var root = this.createShadowRoot(); root.innerHTML = "<style>::content > *{color: red}</style>" + "I'm a " + this.localName + " using Shadow DOM!<content></content>"; }; document.register('shadow-element', {prototype: proto2}); </script>
这个导入定义 (并注册) 了两个元素,<say-hi> 和 <shadow-element>。主页面能够直接使用它们,无需作任何额外操做。
index.html
<head> <link rel="import" href="elements.html"> </head> <body> <say-hi name="Eric"></say-hi> <shadow-element> <div>( I'm in the light dom )</div> </shadow-element> </body>
在我看来,这样的工做流程使得 HTML 导入成为了共享 Web Components 的理想方式。 管理依赖和子导入 嘿。据说你挺喜欢导入, 因此我就在你的导入里又加了个导入。 子导入(Sub-imports) 若导入可以嵌套将会提供更多便利。例如,若是你想复用或继承另外一个组件,使用导入加载其余元素。 下面是 Polymer 中的真实例子。经过复用布局还有选择器组件,咱们获得了一个新的选项卡组件 (<polymer-ui-tabs>)。它们的依赖经过 HTML 导入来管理。
polymer-ui-tabs.html
<link rel="import" href="polymer-selector.html"> <link rel="import" href="polymer-flex-layout.html"> <polymer-element name="polymer-ui-tabs" extends="polymer-selector" ...> <template> <link rel="stylesheet" href="polymer-ui-tabs.css"> <polymer-flex-layout></polymer-flex-layout> <shadow></shadow> </template> </polymer-element>
完整源码 应用开发者能够引入这个新元素:
<link rel="import" href="polymer-ui-tabs.html"> <polymer-ui-tabs></polymer-ui-tabs>
若之后出现了一个更新,更棒的 <polymer-selector2>
,你就能够绝不犹豫的用它替换 <polymer-selector>
。多亏有了导入和 web 组件,你不再用担忧惹恼你的用户了。 依赖管理 咱们都知道一个页面载入多个 jQuery 会出问题。如果多个组件引用了相同的库,对于 Web 组件来讲会不会是个严重的问题? 若是使用 HTML 引用,你就彻底不用担忧! 导入能够用来管理这些依赖。 将库放进一个 HTML 导入中,就自动避免了重复加载问题。文档只会被解析一次。脚本也只执行一次。来举个例子吧,好比说你定义了一个导入,jquery.html,它会加载 JQuery。 jquery.html
<script src="http://cdn.com/jquery.js"></script>
这个导入能够被其余导入复用: import2.html
<link rel="import" href="jquery.html"> <div>Hello, I'm import 2</div>
ajax-element.html
<link rel="import" href="jquery.html"> <link rel="import" href="import2.html"> <script> var proto = Object.create(HTMLElement.prototype); proto.makeRequest = function(url, done) { return $.ajax(url).done(function() { done(); }); }; document.register('ajax-element', {prototype: proto}); </script>
若主页面也须要这个库,连它也能够包含 jquery.html:
<head> <link rel="import" href="jquery.html"> <link rel="import" href="ajax-element.html"> </head> <body> ... <script> $(document).ready(function() { var el = document.createElement('ajax-element'); el.makeRequest('http://example.com'); }); </script> </body>
尽管 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 以前,它就是一个空操做。实际上,在导入文档中直接 "运行" 的只有 <script>
。参见导入中的脚本操做。 优化异步载入 导入不会阻塞主页面解析。导入中的脚本会按照顺序执行,但也不会阻塞主页面。这意味着你在维护脚本顺序时得到了相似于延迟加载的行为。将导入放到 <head>
的好处在于它可让解析器尽快的去解析导入的内容。即使如此,你还得记得主页面中的 <script>
仍然 会阻塞页面:
<head> <link rel="import" href="/path/to/import_that_takes_5secs.html"> <script>console.log('I block page rendering');</script> </head>
根据你的应用架构和使用场景不一样,有几种方法能够优化异步行为。下面要使用的技巧能够缓解对主页面渲染的阻塞。
场景 #1 (推荐): <head>
中没有脚本或 <body>
没有内联脚本
我对放置 <script>
的建议就是没关系跟着你的导入。把它们尽量远的放置…你确定早就按照最佳实践这么作了,不是吗!?;)
看个例子:
<head> <link rel="import" href="/path/to/import.html"> <link rel="import" href="/path/to/import2.html"> <!-- 避免在这放脚本 --> </head> <body> <!-- 避免在这放脚本 --> <div id="container"></div> <!-- 避免在这放脚本 --> ... <script> // 其余的脚本。 // 得到导入内容。 var link = document.querySelector('link[rel="import"]'); var post = link.import.querySelector('#blog-post'); var container = document.querySelector('#container'); container.appendChild(post.cloneNode(true)); </script> </body>
全部内容都放到底部。
场景 1.5: 导入添加本身的内容
另外一个选择是让导入添加本身的内容. 若导入的做者和应用开发者之间达成了某种约定,那么导入就能够将它自身加入到主页面的某个位置:
import.html:
<div id="blog-post">...</div> <script> var me = document.currentScript.ownerDocument; var post = me.querySelector('#blog-post'); var container = document.querySelector('#container'); container.appendChild(post.cloneNode(true)); </script>
index.html
<head> <link rel="import" href="/path/to/import.html"> </head> <body> <!-- 不须要写脚本。导入会本身处理 --> </body>
场景 #2: <head>
或 <body>
中有(内联)脚本
若某个导入的加载须要耗费很长时间,跟在导入后面的第一个 <script>
将会阻塞页面渲染。以 Google Analytics 为例,它推荐将跟踪代码放在 <head>
中,若你必须将 <script>
放到 <head>
中,那么动态的添加导入将会避免阻塞页面:
<head> <script> 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'); // 导入被提早添加 :) </script> <script> // 其余脚本 </script> </head> <body> <div id="container"></div> ... </body>
或者,将导入放到 <body> 结束处:
<head> <script> // 其余脚本 </script> </head> <body> <div id="container"></div> ... <script> function addImportLink(url) { ... } addImportLink('/path/to/import.html'); // 导入很晚才能被添加 :( </script> </body>
注意: 不推荐最后的方法。解析器在解析页面结束以前不会去操做导入的内容。
要点 导入的 MIME 类型是 text/html。
导入跨域资源须要启用 CORS。
来自相同 URL 的导入仅获取和解析一次。这表示导入中的脚本只在第一次导入的时候执行。 导入中的脚本按顺序执行,它们不会阻塞主页面解析。 导入连接不表明 "#把内容添加到这里"。它表明 "解析器,去把这个文档取过来,我一会要用"。脚本在导入期间运行,而样式,标记,还有其余资源须要明确的加入到主页面中。这是 HTML 导入和<iframe>
之间的最大区别,后者表示 "在这里加载并渲染资源"。 总结 HTML 导入容许将 HTML/CSS/JS 打包成一个单独资源。这个想法在 Web 组件开发世界中显得极为重要。开发者能够建立重用的组件,其余人经过引入 <link rel="import">
就可以在本身的应用中使用这些组件。 HTML 导入是个简单的概念,但却促成了许多有趣的使用案例。
使用案例
将相关的HTML/CSS/JS 做为一个单独的包 来分发。理论上来讲,你能够在应用里面导入一个完整的 web 应用。 代码组织 - 将概念按照逻辑划分为不一样的文件,鼓励模块化 & 复用性**。 传输 一或多个自定义元素 的定义。能够在应用内使用导入来注册 和包含自定义元素。这符合良好的软件模式,即将接口/定义与使用分离。 管理依赖 - 自动解决资源的重复加载。 脚本块 - 没有导入以前,一个大型的 JS 库须要在使用前所有解析,这一般很慢。有了导入,只要块 A 解析完毕,库就可以当即使用。延迟更少了!
<link rel="import" href="chunks.html">: <script>/* script chunk A goes here */</script> <script>/* script chunk B goes here */</script> <script>/* script chunk C goes here */</script>
... 并行 HTML 解析 - 这是首次可以让浏览器并行运行两个 (或多个) HTML 解析器。
容许在调试和非调试模式下切换,只须要修改导入的目标。你的应用无需知道导入的目标是打包/编译好的资源仍是一棵导入树。