HTML Imports

为什么需要导入?

先想想你在 web 上是如何加载不同类型的资源。对于 JS,我们有  ... 让这一切都快点变成现实吧,这玩意简直太棒了!

Load/error 事件

当导入成功时  元素会触发 load 事件,加载失败时 (例如资源出现 404) 则会触发 error

导入会尝试立即加载。一个简单的办法是使用 onload/onerror 特性:




注意上面事件处理的定义要早于导入开始加载页面。浏览器一旦解析到导入的标签,它就会立即加载资源。如果此时处理函数不存在,你将在控制台看到函数名未定义的错误。

或者,你可以动态创建导入:

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 导入。
  •  没有 rel="import"
  •  没有被加入到 DOM 中。
  •  从 DOM 中被移除。
  • 资源没有开启 CORS。

完整示例

假设 warnings.html 包含如下内容:

Warning!

This page is under construction

Heads up!

This content may be out of date


你可以获取导入文档中的一部分并把它们复制到当前页面中:


  


  ...
  

在导入中使用脚本

导入的内容并不在主文档中。它们仅仅作为主文档的附属而存在。即便如此,导入的内容还是能够在主页面中生效。导入能够访问它自己的 DOM 或/和包含它的页面中的 DOM:

示例 - import.html 向主页面中添加它自己的样式表



...


留意这里的操作。导入中的脚本获得了导入文档的引用 (document.currentScript.ownerDocument),随后将导入文档中的部分内容附加到了主页面中 (mainDoc.head.appendChild(...))。这段代码看起来不怎么优雅。

导入中的脚本要么直接运行代码,要么就定义个函数留给主页面使用。这很像 Python 中 模块定义的方式。

导入中 JavaScript 的规则:

  • 导入中的脚本会在包含导入文档的 window 上下文中运行。因此window.document 关联的是主页面文档。这会产生两个有用的推论:
    • 导入中定义的函数最终会出现在 window 上。
    • 你不用将导入文档中的 
      index.html

      
        
      
      
        

      注册自定义元素

      自定义元素是 Web Component 技术中的另一位成员,它和 HTML 导入也是出奇的搭配。导入能够运行脚本,既然如此,为什么不定义 + 注册你自己的自定义元素,这样一来用户就避免重复操作了呢? 让我们就叫它..."自动注册(auto-registration)"。

      elements.html


      这个导入定义 (并注册) 了两个元素, 和 。主页面可以直接使用它们,无需做任何额外操作。

      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

      
      
      Hello, I'm import 2

      ajax-element.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 之前,它就是一个空操作。实际上,在导入文档中直接 "运行" 的只有 

      根据你的应用架构和使用场景不同,有几种方法可以优化异步行为。下面要使用的技巧可以缓解对主页面渲染的阻塞。

      场景 #1 (推荐):  中没有脚本或  没有内联脚本

      我对放置 

      所有内容都放到底部。

      场景 1.5: 导入添加自己的内容

      另一个选择是让导入添加自己的内容. 若导入的作者和应用开发者之间达成了某种约定,那么导入就可以将它自身加入到主页面的某个位置:

      import.html:

      ...

      index.html
      
        
      
      
        
      

      场景 #2:  或  中(内联)脚本

      若某个导入的加载需要耗费很长时间,跟在导入后面的第一个 

      ...
      或者,将导入放到   结束处:
      
        
      
      
        
      ...

      注意: 不推荐最后的方法。解析器在解析页面结束之前不会去操作导入的内容。

      要点

      • 导入的 MIME 类型是 text/html

      • 导入跨域资源需要启用 CORS。

      • 来自相同 URL 的导入仅获取和解析一次。这表示导入中的脚本只在第一次导入的时候执行。

      • 导入中的脚本按顺序执行,它们不会阻塞主页面解析。

      • 导入链接不代表 "#把内容添加到这里"。它代表 "解析器,去把这个文档取过来,我一会要用"。脚本在导入期间运行,而样式,标记,还有其他资源需要明确的加入到主页面中。这是 HTML 导入和