HTML Imports

爲何須要導入? 先想一想你在 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 解析器。

容許在調試和非調試模式下切換,只須要修改導入的目標。你的應用無需知道導入的目標是打包/編譯好的資源仍是一棵導入樹。

相關文章
相關標籤/搜索