不按順序 (OOO) HTML 串流 (也稱為宣告式部分更新) 是傳統「由上而下」線性處理 HTML 的替代方案。開發人員可以先在文件中定義預留位置,然後使用相同網路串流中稍後抵達的內容,或透過新的 JavaScript 命令式串流 API「修補」這些預留位置。
快速總覽
這項功能包含兩項主要機制:
- 宣告式修補 (HTML 型):使用新的處理指令 (
<?marker>、<?start>、<?end>) 和<template for>,在 DOM 從伺服器串流傳入時,替換 DOM 中的內容。 - 指令式設定和串流 (以 JS 為基礎):一系列新方法 (例如
streamHTML()、setHTMLUnsafe()),可設定 HTML 或將可讀取的串流直接傳送至 DOM 元素,並提供原生效能和選用指令碼執行功能。
最佳做法
- 優先處理不需捲動位置的內容:優先串流重要 UI (標頭、主頁橫幅、主要導覽、主要內容)。針對「昂貴」或優先順序較低的元件 (例如留言、廣告或相關內容),請使用預留位置。
- 導入原生載入狀態:使用
<?start>和<?end>標記包裝備用內容。當對應的<template for>抵達時,瀏覽器會自動取代這些標記之間的所有內容。 - 盡量降低 Cumulative Layout Shift (CLS):一律為預留位置提供穩定的尺寸。如果無法預測最終內容大小,請使用骨架畫面或 CSS
aspect-ratio預留空間,避免畫面劇烈跳動。 - 無障礙功能和焦點管理:請注意,不按順序更新可能會造成螢幕閱讀器使用者困惑。請使用
aria-live區域提供重要更新,並確保含有活動元素的預留位置遭到取代時,焦點不會消失。
作品詳細資訊
1. 宣告式 HTML 標記
瀏覽器會尋找特定處理指令,找出應修補內容的位置。
<!-- A simple insertion point -->
<div id="sidebar">
<?marker name="dynamic-comments">
</div>
<!-- A replacement range with loading state -->
<main>
<?start name="main-content">
<div class="skeleton">Loading content...</div>
<?end>
</main>
... (other content) ...
<!-- Later in the same stream, provide the content -->
<template for="dynamic-comments">
<div class="comments">...</div>
</template>
<template for="main-content">
<article>
<h1>The Real Content</h1>
<p>This replaces the skeleton div automatically.</p>
</article>
</template>
<template> 取代插入點後,該插入點就不會再存在,也無法重複使用。不過,<template> 可能包含插入點,可在串流中稍後使用。
<?start name="replaced-content">
<div class="skeleton">
<?end>
<!-- Later in the same stream, provide initial content -->
<template for="replaced-content">
<?start name="replaced-content">
<div>Quickly computed result</div>
<?end>
</template>
<!-- Later in the same stream, provide full content -->
<template for="replaced-content">
<?start name="replaced-content">
<div>Full result</div>
<?end>
</template>
2. 命令式 JS 串流
瀏覽器提供 24 種方法 (6 種動作,每種動作有 4 種變化版本),可透過靜態 (從字串) 或串流方式更新 DOM。
API 參考資料矩陣
| 動作 | 靜態 (安全) | 串流 (安全) | 靜態 (不安全) | 串流 (不安全) |
|---|---|---|---|---|
| 組合 | setHTML() |
streamHTML() |
setHTMLUnsafe() |
streamHTMLUnsafe() |
| 取代 | replaceWithHTML() |
streamReplaceWithHTML() |
replaceWithHTMLUnsafe() |
streamReplaceWithHTMLUnsafe() |
| 變更前 | beforeHTML() |
streamBeforeHTML() |
beforeHTMLUnsafe() |
streamBeforeHTMLUnsafe() |
| Prepend | prependHTML() |
streamPrependHTML() |
prependHTMLUnsafe() |
streamPrependHTMLUnsafe() |
| 附加 | appendHTML() |
streamAppendHTML() |
appendHTMLUnsafe() |
streamAppendHTMLUnsafe() |
| 變更後 | afterHTML() |
streamAfterHTML() |
afterHTMLUnsafe() |
streamAfterHTMLUnsafe() |
核心概念
- 安全與不安全:
- 安全方法 (例如
setHTML()) 預設會使用內建的淨化工具,移除<script>標記等潛在危險內容。您可以在選項中傳遞自訂Sanitizer物件,以便在預設清理工具之外套用。 - 不安全方法 (例如
setHTMLUnsafe()) 預設不會清理,但您仍可傳遞自訂Sanitizer。如要允許指令碼在新內容中執行,您必須在選項中明確傳遞{ runScripts: true }。重要事項:只有在安全方法無法運作時,才對信任的內容使用不安全的方法。
- 安全方法 (例如
- 靜態與串流:
- 靜態方法會採用字串 (或
TrustedHTML) 並立即套用。 - 串流方法會傳回
WritableStream。這樣一來,您就能將內容 (例如fetch回應) 直接傳送至 DOM,瀏覽器則會在區塊送達時逐步算繪。
- 靜態方法會採用字串 (或
範例:串流擷取回應
您可以直接將擷取回應傳送至 DOM,不必手動處理區塊。
const main = document.querySelector('main');
const response = await fetch('/api/partial-update');
// Pipe the stream directly into the element
await response.body
.pipeThrough(new TextDecoderStream())
.pipeTo(main.streamHTMLUnsafe({ runScripts: true }));
// Or use the `textStream()` convenience method which streams directly without needing the intermediate `TextDecoderStream()` step
await response
.textStream()
.pipeTo(main.streamHTMLUnsafe({ runScripts: true }))
用途參考矩陣
- 孤島架構:在靜態 HTML 上,為獨立的互動式元件進行凍結,不需完整的架構執行階段。
- 延遲載入「耗費資源」的區塊:立即提供網頁外殼,並在資料庫密集型元件可用時串流載入。
- 單頁應用程式 (SPA) 更新:使用
streamHTML()串流 SPA 中的大型 UI 轉換,不必等待完整的 JSON 酬載和手動 DOM 建構。
反面模式和警告 (請勿這樣做)
- 不要將 OOO 串流用於所有內容。如果網頁過度分割,可能會增加串流的複雜度,且瀏覽器花費太多時間修補小型節點,可能會延遲內容。
- 請勿忽略 Trusted Types。在強制執行 Trusted Types 的環境中使用
*Unsafe方法時,請務必視需要傳遞經過清理的政策物件。 - 請勿假設指令碼執行順序。
<template for>內的指令碼在修補到文件後會立即執行。請勿依賴這些範本相對於其他延遲範本的特定執行順序。 - 請勿在沒有 Polyfill 策略的情況下使用。請務必提供備用方案,或使用 Polyfill (
html-setters-polyfill和template-for-polyfill) 支援所需瀏覽器,直到這項功能成為基準。
備用策略
您可以使用 template-for-polyfill 和 html-setters-polyfill 等 polyfill,為不支援的瀏覽器模擬 API 介面。
不支援 html-processing-instructions 的瀏覽器會將 <?marker>、<?start> 和 <?end> 剖析為註解。template-for-polyfill 仍會將這些註解視為標記,因此請為所有瀏覽器保留相同的處理指令語法。請勿將標記重寫為 HTML 註解或不支援瀏覽器的元素。