HTML 匯入

包含網站

為什麼要匯入?

想想您如何在網路上載入不同類型的資源。對於 JS,我們有 <script src>。對於 CSS 供應商,您的選擇可能是 <link rel="stylesheet">。圖片規格為 <img>。影片有 <video>。音訊,<audio>...來到重點!大部分網頁內容都以簡單的宣告式方式自行載入。HTML 則不行。選項如下:

  1. <iframe> - 實驗中正確,但重量過高。iframe 的內容與網頁的完全分離,雖然這大多是很棒的功能,但卻會帶來額外的挑戰 (縮小影格大小至其內容並不容易,但編寫指令碼時非常傷心,因此幾乎不可能設定樣式)。
  2. AJAX - 我喜歡 xhr.responseType="document",但是您表示我需要 JS 來載入 HTML 嗎?答錯了。
  3. 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.importnull

  • 瀏覽器不支援 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 的純粹自訂元素,全都沒問題 [123]。如果同時使用這些技術,匯入項目就會成為 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 被納入許多不同的匯入樹狀結構中,但瀏覽器只會擷取及處理文件一次。檢查網路面板可證明這一點:

jquery.html 請求一次
jquery.html 要求一次

效能注意事項

HTML 匯入功能相當強大,但就像任何新的網路技術一樣,建議您謹慎使用匯入。網站開發的最佳做法仍然存在。以下是幾件注意事項:

串連匯入

減少網路要求是非常重要的事。如果您有多個頂層匯入連結,不妨考慮將這些連結合併為單一資源,並匯入該檔案!

VulcanizePolymer 團隊提供的 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 剖析器。

  • 只需變更匯入目標本身,即可在應用程式中啟用偵錯和非偵錯模式。您的應用程式不需要知道匯入目標是封裝/編譯的資源還是匯入樹狀結構。