HTML Import

基本操作

长久以来,网页可以加载外部的样式表、脚本、图片、多媒体,却无法方便地加载其他网页,iframe和ajax都只能提供部分的解决方案,且有很大的局限。HTML Import就是为了解决加载外部网页这个问题,而提出来的。

下面代码用于测试当前浏览器是否支持HTML Import。

  1. function supportsImports() {
  2. return 'import' in document.createElement('link');
  3. }
  4. if (supportsImports()) {
  5. // 支持
  6. } else {
  7. // 不支持
  8. }

HTML Import用于将外部的HTML文档加载进当前文档。我们可以将组件的HTML、CSS、JavaScript封装在一个文件里,然后使用下面的代码插入需要使用该组件的网页。

  1. <link rel="import" href="dialog.html">

上面代码在网页中插入一个对话框组件,该组建封装在dialog.html文件。注意,dialog.html文件中的样式和JavaScript脚本,都对所插入的整个网页有效。

假定A网页通过HTML Import加载了B网页,即B是一个组件,那么B网页的样式表和脚本,对A网页也有效(准确得说,只有style标签中的样式对A网页有效,link标签加载的样式表对A网页无效)。所以可以把多个样式表和脚本,都放在B网页中,都从那里加载。这对大型的框架,是很方便的加载方法。

如果B与A不在同一个域,那么A所在的域必须打开CORS。

  1. <!-- example.com必须打开CORS -->
  2. <link rel="import" href="http://example.com/elements.html">

除了用link标签,也可以用JavaScript调用link元素,完成HTML Import。

  1. var link = document.createElement('link');
  2. link.rel = 'import';
  3. link.href = 'file.html'
  4. link.onload = function(e) {...};
  5. link.onerror = function(e) {...};
  6. document.head.appendChild(link);

HTML Import加载成功时,会在link元素上触发load事件,加载失败时(比如404错误)会触发error事件,可以对这两个事件指定回调函数。

  1. <script async>
  2. function handleLoad(e) {
  3. console.log('Loaded import: ' + e.target.href);
  4. }
  5. function handleError(e) {
  6. console.log('Error loading import: ' + e.target.href);
  7. }
  8. </script>
  9. <link rel="import" href="file.html"
  10. onload="handleLoad(event)" onerror="handleError(event)">

上面代码中,handleLoad和handleError函数的定义,必须在link元素的前面。因为浏览器元素遇到link元素时,立刻解析并加载外部网页(同步操作),如果这时没有对这两个函数定义,就会报错。

HTML Import是同步加载,会阻塞当前网页的渲染,这主要是为了样式表的考虑,因为外部网页的样式表对当前网页也有效。如果想避免这一点,可以为link元素加上async属性。当然,这也意味着,如果外部网页定义了组件,就不能立即使用了,必须等HTML Import完成,才能使用。

  1. <link rel="import" href="/path/to/import_that_takes_5secs.html" async>

但是,HTML Import不会阻塞当前网页的解析和脚本执行(即阻塞渲染)。这意味着在加载的同时,主页面的脚本会继续执行。

最后,HTML Import支持多重加载,即被加载的网页同时又加载其他网页。如果这些网页都重复加载同一个外部脚本,浏览器只会抓取并执行一次该脚本。比如,A网页加载了B网页,它们各自都需要加载jQuery,浏览器只会加载一次jQuery。

脚本的执行

外部网页的内容,并不会自动显示在当前网页中,它只是储存在浏览器中,等到被调用的时候才加载进入当前网页。为了加载网页网页,必须用DOM操作获取加载的内容。具体来说,就是使用link元素的import属性,来获取加载的内容。这一点与iframe完全不同。

  1. var content = document.querySelector('link[rel="import"]').import;

发生以下情况时,link.import属性为null。

  • 浏览器不支持HTML Import
  • link元素没有声明rel="import"
  • link元素没有被加入DOM
  • link元素已经从DOM中移除
  • 对方域名没有打开CORS

下面代码用于从加载的外部网页选取id为template的元素,然后将其克隆后加入当前网页的DOM。

  1. var el = linkElement.import.querySelector('#template');
  2. document.body.appendChild(el.cloneNode(true));

当前网页可以获取外部网页,反过来也一样,外部网页中的脚本,不仅可以获取本身的DOM,还可以获取link元素所在的当前网页的DOM。

  1. // 以下代码位于被加载(import)的外部网页
  2. // importDoc指向被加载的DOM
  3. var importDoc = document.currentScript.ownerDocument;
  4. // mainDoc指向主文档的DOM
  5. var mainDoc = document;
  6. // 将子页面的样式表添加主文档
  7. var styles = importDoc.querySelector('link[rel="stylesheet"]');
  8. mainDoc.head.appendChild(styles.cloneNode(true));

上面代码将所加载的外部网页的样式表,添加进当前网页。

被加载的外部网页的脚本是直接在当前网页的上下文执行,因为它的window.document指的是当前网页的document,而且它定义的函数可以被当前网页的脚本直接引用。

Web Component的封装

对于Web Component来说,HTML Import的一个重要应用是在所加载的网页中,自动登记Custom Element。

  1. <script>
  2. // 定义并登记<say-hi>
  3. var proto = Object.create(HTMLElement.prototype);
  4. proto.createdCallback = function() {
  5. this.innerHTML = 'Hello, <b>' +
  6. (this.getAttribute('name') || '?') + '</b>';
  7. };
  8. document.registerElement('say-hi', {prototype: proto});
  9. </script>
  10. <template id="t">
  11. <style>
  12. ::content > * {
  13. color: red;
  14. }
  15. </style>
  16. <span>I'm a shadow-element using Shadow DOM!</span>
  17. <content></content>
  18. </template>
  19. <script>
  20. (function() {
  21. var importDoc = document.currentScript.ownerDocument; //指向被加载的网页
  22. // 定义并登记<shadow-element>
  23. var proto2 = Object.create(HTMLElement.prototype);
  24. proto2.createdCallback = function() {
  25. var template = importDoc.querySelector('#t');
  26. var clone = document.importNode(template.content, true);
  27. var root = this.createShadowRoot();
  28. root.appendChild(clone);
  29. };
  30. document.registerElement('shadow-element', {prototype: proto2});
  31. })();
  32. </script>

上面代码定义并登记了两个元素:\和\。在主页面使用这两个元素,非常简单。

  1. <head>
  2. <link rel="import" href="elements.html">
  3. </head>
  4. <body>
  5. <say-hi name="Eric"></say-hi>
  6. <shadow-element>
  7. <div>( I'm in the light dom )</div>
  8. </shadow-element>
  9. </body>

不难想到,这意味着HTML Import使得Web Component变得可分享了,其他人只要拷贝elements.html,就可以在自己的页面中使用了。