包含網站
為什麼要匯入?
想想您如何在網路上載入不同類型的資源。對於 JS,我們有 <script src>
。對於 CSS 供應商,您的選擇可能是 <link rel="stylesheet">
。圖片規格為 <img>
。影片有 <video>
。音訊,<audio>
...來到重點!大部分網頁內容都以簡單的宣告式方式自行載入。HTML 則不行。選項如下:
<iframe>
- 實驗中正確,但重量過高。iframe 的內容與網頁的完全分離,雖然這大多是很棒的功能,但卻會帶來額外的挑戰 (縮小影格大小至其內容並不容易,但編寫指令碼時非常傷心,因此幾乎不可能設定樣式)。- AJAX - 我喜歡
xhr.responseType="document"
,但是您表示我需要 JS 來載入 HTML 嗎?答錯了。 - CrazyHacksTM - 嵌入字串,以註解的形式隱藏 (例如
<script type="text/html">
)。哇!
看到怪物嗎?網路上最基本的 HTML 內容,需要投入大量心力才能理解。幸好,網頁元件就是我們解決問題的最佳方法。
開始使用
HTML 匯入是網頁元件轉換的一部分,可用來在其他 HTML 文件中加入 HTML 文件。且不限於標記。匯入項目可包含 CSS、JavaScript 或 .html
檔案可包含的所有內容。換句話說,這會匯入載入相關 HTML/CSS/JS 的絕佳工具。
基本概念
宣告 <link rel="import">
,在您的網頁上納入匯入項目:
<head>
<link rel="import" href="/path/to/imports/stuff.html">
</head>
匯入的網址稱為「匯入位置」。如要從其他網域載入內容,匯入位置必須啟用 CORS:
<!-- Resources on other origins must be CORS-enabled. -->
<link rel="import" href="http://example.com/elements.html">
功能偵測與支援
如要偵測支援,請檢查 <link>
元素是否含有 .import
:
function supportsImports() {
return 'import' in document.createElement('link');
}
if (supportsImports()) {
// Good to go!
} else {
// Use other libraries/require systems to load files.
}
瀏覽器功能仍處於早期開發階段。Chrome 31 是第一個看到實作的瀏覽器,但其他瀏覽器廠商正在等待 ES Modules 執行。但如果使用其他瀏覽器,則在廣泛支援之前,webcomponents.js polyfill 能正常運作。
整合資源
匯入項目提供了將 HTML/CSS/JS (甚至是其他 HTML 匯入) 組合成單一可交付項目的慣例做法。這是內建功能 但非常強大如果您正在建立主題、程式庫,或只想將應用程式分段成多個有邏輯的區塊,為使用者提供單一網址,是極具吸引力的體驗。您甚至可以透過匯入功能推送整個應用程式。不妨先思考一下。
實際範例為「Bootstrap」。Bootstrap 由個別檔案 (bootstrap.css、bootstrap.js、字型) 組成,需要 JQuery 做為外掛程式的外掛程式,並且提供標記範例。這類開發人員提供高度彈性。而是對他們想要使用的架構部分做出購買。也就是說,我會帶你的平時 JoeDeveloperTM 輕鬆踏出第一步,並下載所有 Bootstrap。
匯入項目讓 Bootstrap 這類容器更加合理。我來談談未來載入 Bootstrap 的未來:
<head>
<link rel="import" href="bootstrap.html">
</head>
使用者只要載入 HTML 匯入連結即可。他們不需要再處理分散的檔案。相反地,Bootstrap.html 整個 Bootstrap 都會納入匯入作業中:
<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>
...
<!-- scaffolding markup -->
<template>
...
</template>
等等這很令人興奮。
載入/錯誤事件
匯入成功時,<link>
元素會觸發 load
事件,嘗試失敗時則觸發 onerror
(例如資源 404 時)。
匯入作業會嘗試立即載入。如要避免頭痛,最簡單的方法就是使用 onload
/onerror
屬性:
<script>
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.setAttribute('async', ''); // make it async!
link.href = 'file.html';
link.onload = function(e) {...};
link.onerror = function(e) {...};
document.head.appendChild(link);
使用內容
在網頁上包含匯入項目,並不代表「的檔案內容會顯示在這裡」。意思是「剖析器,先擷取擷取此文件再使用」。如要實際使用這些內容,您必須採取行動並編寫指令碼。
aha!
的一個重要時刻,發現匯入作業僅是一種文件。事實上,匯入的內容稱為「匯入文件」。您可以使用標準 DOM API 處理匯入中的真值!
link.import
如要存取匯入的內容,請使用連結元素的 .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>
h3 {
color: red !important;
}
</style>
<h3>Warning!
<p>This page is under construction
</div>
<div class="outdated">
<h3>Heads up!
<p>This content may be out of date
</div>
匯入者可以擷取這份文件的特定部分,並將這些內容複製到自己的網頁中:
<head>
<link rel="import" href="warnings.html">
</head>
<body>
...
<script>
var link = document.querySelector('link[rel="import"]');
var content = link.import;
// Grab DOM from warning.html's document.
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">
<style>
/* Note: <style> in an import apply to the main
document by default. That is, style tags don't need to be
explicitly added to the main document. */
#somecontainer {
color: blue;
}
</style>
...
<script>
// importDoc references this import's document
var importDoc = document.currentScript.ownerDocument;
// mainDoc references the main document (the page that's importing us)
var mainDoc = document;
// Grab the first stylesheet from this import, clone it,
// and append it to the importing document.
var styles = importDoc.querySelector('link[rel="stylesheet"]');
mainDoc.head.appendChild(styles.cloneNode(true));
</script>
留意這裡的情況。匯入作業中的指令碼會參照匯入的文件 (document.currentScript.ownerDocument
),並將該文件的一部分附加到匯入頁面 (mainDoc.head.appendChild(...)
)。如果希望我說明,請理解。
匯入作業中的 JavaScript 規則:
- 匯入中的指令碼會在包含匯入
document
的視窗結構定義中執行。因此window.document
是指主頁面文件。這有兩個實用的輔助程式:- 匯入作業中定義的函式最終結束日期為
window
。 - 您不必採取任何行動,例如將匯入項目的
<script>
區塊附加至主頁面。指令碼也會再次執行。
- 匯入作業中定義的函式最終結束日期為
- 匯入作業不會禁止剖析主頁面。不過,這些指令碼中的指令碼會依序處理。這表示您可以呈現較晚的行為,同時維持正確的指令碼順序。下文將詳細說明。
提供網頁元件
HTML Imports 的設計可完美呈現網路上可重複使用的內容。特別是,這是發布網頁元件的理想方式。無論是基礎 HTML <template>
,還是使用 Shadow DOM 的純粹自訂元素,全都沒問題 [1、2、3]。如果同時使用這些技術,匯入項目就會成為 Web 元件的 #include
。
包含範本
HTML 範本元素非常適合用於匯入 HTML。<template>
適合用於去除標記區段,讓匯入應用程式依需求使用。將內容納入 <template>
也可讓您享有在不使用內容的情況下,指定內容的額外好處。也就是說,範本要加入 DOM 後才能執行。好棒!
import.html
<template>
<h1>Hello World!</h1>
<!-- Img is not requested until the <template> goes live. -->
<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"]');
// Clone the <template> in the import.
var template = link.import.querySelector('template');
var clone = document.importNode(template.content, true);
document.querySelector('#container').appendChild(clone);
</script>
</body>
註冊自訂元素
自訂元素是另一項 Web 元件技術,與 HTML 匯入功能都可完美搭配。匯入作業可以執行指令碼,因此為何不定義 + 註冊自訂元素,讓使用者不必這麼做?這就叫「自動註冊」。
elements.html
<script>
// Define and register <say-hi>.
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
this.innerHTML = 'Hello, <b>' +
(this.getAttribute('name') || '?') + '</b>';
};
document.registerElement('say-hi', {prototype: proto});
</script>
<template id="t">
<style>
::content > * {
color: red;
}
</style>
<span>I'm a shadow-element using Shadow DOM!</span>
<content></content>
</template>
<script>
(function() {
var importDoc = document.currentScript.ownerDocument; // importee
// Define and register <shadow-element>
// that uses Shadow DOM and a template.
var proto2 = Object.create(HTMLElement.prototype);
proto2.createdCallback = function() {
// get template in import
var template = importDoc.querySelector('#t');
// import template into
var clone = document.importNode(template.content, true);
var root = this.createShadowRoot();
root.appendChild(clone);
};
document.registerElement('shadow-element', {prototype: proto2});
})();
</script>
這項匯入會定義 (並註冊) 兩個元素:<say-hi>
和 <shadow-element>
。第一個顯示會在匯入作業中自行註冊的基本自訂元素。第二個範例說明如何實作自訂元素,從 <template>
建立 Shadow DOM,然後自行註冊。
如果要在 HTML 匯入作業中註冊自訂元素,匯入工具的最佳作法是直接在其網頁上宣告您的元素。不需要接線。
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 匯入工具成為共用網頁元件的理想方式。
管理依附元件和子匯入
子匯入
如要納入另一項匯入項目,這種做法會很實用。舉例來說,如果您想要重複使用或擴充其他元件,請使用匯入功能載入其他元素。
以下是來自 Polymer 的實際範例。這是新的分頁元件 (<paper-tabs>
),可重複使用版面配置和選取器元件。這些依附元件是使用 HTML 匯入功能管理。
card-tabs.html (簡化):
<link rel="import" href="iron-selector.html">
<link rel="import" href="classes/iron-flex-layout.html">
<dom-module id="paper-tabs">
<template>
<style>...</style>
<iron-selector class="layout horizonta center">
<content select="*"></content>
</iron-selector>
</template>
<script>...</script>
</dom-module>
應用程式開發人員可使用以下方法匯入這個新元素:
<link rel="import" href="paper-tabs.html">
<paper-tabs></paper-tabs>
日後推出更出色的 <iron-selector2>
時,您可以更換 <iron-selector>
,並立即開始使用。別讓使用者因為匯入作業和網頁元件而中斷。
依附元件管理
我們都知道,每頁載入 JQuery 超過一次就會導致錯誤。當多個元件使用相同的程式庫時,這不會是網頁元件的嚴重問題嗎?如果不是使用 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.registerElement('ajax-element', {prototype: proto});
</script>
如果首頁本身需要 jquery.html,也可以加入 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 被納入許多不同的匯入樹狀結構中,但瀏覽器只會擷取及處理文件一次。檢查網路面板可證明這一點:
效能注意事項
HTML 匯入功能相當強大,但就像任何新的網路技術一樣,建議您謹慎使用匯入。網站開發的最佳做法仍然存在。以下是幾件注意事項:
串連匯入
減少網路要求是非常重要的事。如果您有多個頂層匯入連結,不妨考慮將這些連結合併為單一資源,並匯入該檔案!
Vulcanize 是 Polymer 團隊提供的 npm 建構工具,可將一組 HTML 匯入作業週期性合併至單一檔案。您可以將此視為網頁元件的串連建構步驟。
匯入功能會使用瀏覽器快取
許多使用者忘了瀏覽器的網路堆疊在過去幾年間一直經過微調。匯入 (和子匯入) 也可以利用這個邏輯。http://cdn.com/bootstrap.html
匯入作業可能含有子資源,但系統會快取這些子資源。
新增內容後,內容才能派上用場
在呼叫相關服務之前,請將內容視為已宣告。使用一般動態建立的樣式表:
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'styles.css';
將 link
加入 DOM 後,瀏覽器才會要求 style.css:
document.head.appendChild(link); // browser requests styles.css
另一個範例是動態建立的標記:
var h2 = document.createElement('h2');
h2.textContent = 'Booyah!';
h2
相對來說不具意義,除非您將其新增至 DOM。
相同的概念也適用於匯入文件。除非您將其內容附加至 DOM,否則沒有人工管理。事實上,在匯入文件中,唯一必須「執行」的項目為 <script>
。請參閱匯入的指令碼。
針對非同步載入進行最佳化
匯入區塊轉譯
匯入封鎖顯示主頁面。這與 <link rel="stylesheet">
的作用類似。瀏覽器一開始會禁止在樣式表中顯示轉譯功能,是為了將 FOUC 降到最低。匯入項目可以包含觸控筆,因此運作方式也很類似。
如要進行完全非同步且不封鎖剖析器或轉譯,請使用 async
屬性:
<link rel="import" href="/path/to/import_that_takes_5secs.html" async>
async
並不是預設的 HTML 匯入功能,原因是開發人員需要完成更多操作。根據預設,同步表示如果 HTML 匯入內含自訂元素定義的 HTML 匯入項目,就能按順序正確載入及升級。在完全非同步的世界中,開發人員必須自己管理舞蹈和升級的時間。
您也可以動態建立非同步匯入作業:
var l = document.createElement('link');
l.rel = 'import';
l.href = 'elements.html';
l.setAttribute('async', '');
l.onload = function(e) { ... };
匯入作業不會禁止剖析
匯入作業不會禁止剖析主頁面。系統會依序處理匯入作業中的指令碼,但不會封鎖匯入網頁。這表示您可以呈現較晚的行為,同時維持正確的指令碼順序。將匯入內容放入 <head>
的好處之一,就是可讓剖析器盡快開始處理內容。儘管如此,請務必記住主要文件中的 <script>
,「仍然」會繼續封鎖網頁。匯入之後的第一個 <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">
<!-- avoid including script -->
</head>
<body>
<!-- avoid including script -->
<div id="container"></div>
<!-- avoid including script -->
...
<script>
// Other scripts n' stuff.
// Bring in the import content.
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>
<!-- no need for script. the import takes care of things -->
</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'); // Import is added early :)
</script>
<script>
// other scripts
</script>
</head>
<body>
<div id="container"></div>
...
</body>
或者,您也可以將匯入內容在 <body>
結尾附近:
<head>
<script>
// other scripts
</script>
</head>
<body>
<div id="container"></div>
...
<script>
function addImportLink(url) { ... }
addImportLink('/path/to/import.html'); // Import is added very late :(
</script>
</body>
注意事項
匯入的 MIME 類型為
text/html
。其他來源的資源必須啟用 CORS。
系統會擷取並剖析來自相同網址的項目一次。這表示匯入中的指令碼只會在首次出現匯入時執行。
匯入作業中的指令碼會依序處理,但不會禁止主要文件剖析作業。
匯入連結並不是指「#include the content here」。而是「剖析器,先擷取擷取這份文件,以便稍後使用」。雖然指令碼會在匯入時執行,但必須在主頁中明確加入樣式表、標記及其他資源。請注意,您不需要明確加入
<style>
。這是 HTML 匯入和<iframe>
之間的主要差異,也就是「load and render this content here」。
結論
HTML 匯入功能可將 HTML/CSS/JS 合併為單一資源。雖然這個概念因人而異,但在網頁元件領域中卻變得極為強大。開發人員可以建立可重複使用的元件,供他人使用,並導入自己的應用程式,這些元件全都可透過 <link rel="import">
傳遞。
HTML 匯入功能是一項簡單的概念,但可為平台提供許多有趣的用途。
用途
- 將相關的 HTML/CSS/JS 組合成單一組合發布。理論上,您可以將整個網頁應用程式匯入另一個應用程式。
- 程式碼整理 - 按照邏輯將概念分類到不同檔案中,鼓勵模組性和重複使用性**。
- 提供一或多個自訂元素定義。匯入作業可用來register並加入應用程式。這樣做可以實踐良好的軟體模式,將元素的介面/定義與使用方式區隔開來。
- 管理依附元件:自動刪除重複資源。
- 區塊指令碼 - 在匯入前,大型的 JS 程式庫會先剖析檔案,以便開始執行,造成速度緩慢。透過匯入功能,程式庫可在區塊 A 剖析後立即開始運作。延遲時間更短!
// TODO: DevSite - Code sample removed as it used inline event handlers
平行處理 HTML 剖析 - 瀏覽器首次能同時執行兩個 (或更多) HTML 剖析器。
只需變更匯入目標本身,即可在應用程式中啟用偵錯和非偵錯模式。您的應用程式不需要知道匯入目標是封裝/編譯的資源還是匯入樹狀結構。