宣告式部分更新

發布日期:2026 年 5 月 19 日

網路早已不再是靜態、以文件為主的媒體,現代人會使用功能豐富的網頁應用程式,原因有很多,包括通訊、購物、觀看多媒體內容,以及管理複雜的生活。

儘管 HTML 不斷進步,但仍會依序從上到下傳送,不太考慮內容何時準備就緒或使用者何時會使用。CSS 可讓您變更內容順序,但通常會對無障礙功能造成重大影響。JavaScript 可讓您透過各種 API 操控 DOM,擺脫這種限制,但通常需要詳細的語法或建構 DOM 樹狀結構,才能插入 HTML。

網路的用戶端/伺服器性質決定了效能的重要性,但為了規避 HTML 的依序性質,我們經常會做出次佳選擇,導致效能降低。包括等待整個頁面準備就緒,或使用大量架構以非同步方式傳送元件。JavaScript 架構的普及程度顯示,相較於網頁起源的嚴格文件心智模型,網頁開發人員更偏好以元件為基礎的模型。

Chrome 團隊一直在考慮這個問題,並以「宣告式部分更新」為名,開發網頁平台的新增功能。

兩組全新 API 可讓您更輕鬆地以非線性方式提供 HTML,無論是 HTML 文件本身順序錯亂,還是透過更簡單的方式,使用新的 JavaScript API 將 HTML 動態插入現有文件,都能輕鬆完成。從 Chrome 148 開始,開發人員可以使用 chrome://flags/#enable-experimental-web-platform-features 旗標測試這些功能。您也可以使用 Polyfill,即使在尚未支援這些 API 的瀏覽器中,也能立即使用這些新 API。

我們正在將這些網路平台新增內容標準化,其他瀏覽器供應商和標準化管道也給予正面評價。我們正在更新相關標準,將這些新 API 納入其中。

亂序串流

第一組變更內容是新的無序串流 API,使用 <template> HTML 元素和處理指令預留位置。例如:

<div>
  <?marker name="placeholder">
</div>

...

<template for="placeholder">
  Here is some <em>HTML content</em>!
</template>

處理指令存在於 XML 中已久,但在 HTML 中會視為註解並遭到忽略。這項新 API 變更可將處理指示帶入 HTML。瀏覽器看到 <?marker name="placeholder"> 處理指令時,不會立即執行任何動作 (與先前一樣),但稍後可以參照這些指令。

<template> 元素會透過 name 屬性查閱對應的處理指令,並取代內容。在本例中,DOM 剖析後會變成:

<div>
  Here is some <em>HTML content</em>!
</div>

除了替代項目的 <?marker> 屬性,還有 <?start><?end> 範圍標記,可在範本處理前顯示暫時的預留位置內容:

<div>
  <?start name="another-placeholder">
  Loading…
  <?end>
</div>

...

<template for="another-placeholder">
  Here is some <em>HTML content</em>!
</template>

在此情況下,Loading… 會顯示到 <template> 出現為止,然後由新內容取代。

您也可以在範本中加入處理指令,允許進行多項更新:

<ul id="results">
  <?start name="results">
  Loading…
  <?end>
</ul>

...

<template for="results">
  <li>Result One</li>
  <?marker name="results">
</template>
...

<template for="results">
  <li>Result Two</li>
  <?marker name="results">
</template>
...

剖析後會產生下列 HTML:

<ul id="results">
  <li>Result One</li>
  <li>Result Two</li>
  <?marker name="results">
</ul>

最後的處理指令,以防之後在文件中新增更多 <template for="results">

示範

這部影片會使用串流 HTML 實作基本相簿應用程式:

使用無序串流實作的相簿示範 (來源)

初始版面配置完成後,狀態和相片都會串流至 HTML。

用途

搭配串流 HTML 使用時,這種 HTML 修補程式的用途相當廣泛:

  • 島嶼架構。Astro 等架構普及的常見模式是島嶼架構,其中元件會獨立於靜態 HTML 上進行補水。<template for> API 可讓您直接在 HTML 中,以類似方式處理靜態內容。JavaScript 架構也可以使用此功能,處理互動性更高的島嶼或元件。
  • 準備好內容後即可交付。有了這種架構,內容準備就緒後即可串流,不必等待需要額外處理的內容 (例如資料庫查詢)。雖然許多平台都允許串流 HTML,但 HTML 的依序性質表示內容通常會遭到延遲,或必須採用複雜的 JavaScript DOM 操作。現在,您可以在等待期間提供靜態內容,然後在 HTML 串流結尾插入較昂貴的內容。
  • HTML 可以按照最佳順序傳送,提升載入網頁效能。更進一步來說,即使訂單已準備就緒,你還是可以變更訂單。舉例來說,巨型選單是常見的導覽功能,內含大量 HTML,使用者必須等到網頁變成互動式,才會看到這些 HTML。這大段 HTML 可稍後再傳送至 HTML 文件,優先處理網頁初始載入時所需的 HTML。HTML 不再是順序的障礙。

以上僅列舉部分用途,我們很期待開發人員如何運用這項新 API。

限制和細微差異

使用 API 時,請注意以下幾項限制和細節:

  • 基於安全考量,<template for> 只能更新同一父項元素內的處理指令。直接將 <template for> 新增至 <body> 元素,即可存取整份文件 (包括 <head>)。
  • <?end> 處理指令為選用,如果缺少此指令,系統會取代 <?start> 元素與所含元素結尾之間的內容。
  • 如果 <template for> 開始串流後才移動處理指示,新內容可能會繼續串流到舊位置,導致意想不到的後果。
  • 請注意,使用 setHTMLinnerHTML 等方法動態插入 <template for> 時,範本在剖析時的「父項」是中間文件片段。也就是說,使用這些方法插入 HTML 無法修改現有 DOM,修補作業會在片段內「就地」進行。不過,使用 streamHTMLUnsafe 等方法串流時 (我們即將介紹!),沒有中間片段,因此範本可以取代現有內容。

未來可能新增的項目

我們正在考慮日後新增以下功能:

  • 用戶端包括。例如:<template for="footer" patchsrc="/partials/footer.html">
  • 批次處理。在用戶端,片段包含項目也可以擴充為處理批次作業,確保多項更新同時進行。
  • 防止覆寫不會變更的內容。這可以透過內容修訂編號或版本控管達成。這樣一來,系統就能在路徑變更或其他更新之間維持狀態,而不是重設內容。
  • 修補時進行清理。例如:<template for=icon safe><svg id="from-untrusted-source">...</svg></template>

Polyfill

Chrome 團隊已發布 template-for-polyfill,並在 npm 上提供,讓網站即使在其他瀏覽器支援這項功能前,也能立即使用。

由於無法直接更新瀏覽器的 HTML 剖析器,因此有些限制,但最常見的用途都涵蓋在內。網站仍應在其他瀏覽器中進行測試。

更新 HTML 插入和串流方法

並非所有內容都能以 HTML 格式傳送。Chrome 在這方面進行的第二項工作,是讓使用者更容易透過 JavaScript 更新內容。

目前已有許多方法可使用 JavaScript,將 HTML 動態插入現有文件中:

  • setHTML
  • setHTMLUnsafe
  • innerHTMLouterHTML 設定器
  • createContextualFragment
  • insertAdjacentHTML

不過,這些方法運作方式略有不同,開發人員可能不會注意到其中的細微差異:

  • 新內容是覆寫還是附加?
  • 例如,是否會逸出 <script> 標記,以清除可能有害的 HTML?
  • 如果不是,是否應執行 <script>
  • 如何搭配 TrustedTypes 使用?

很少有開發人員能誠實地查看這些 API,並自信地回答每個 API 的問題。

但這項功能有很大的限制,就是只能用於預先知道的完整 HTML 組合,且必須先呼叫允許串流 HTML 的函式。實際上,這表示您必須先下載整個內容,才能插入內容,而 HTML 的優點之一就是能夠立即串流內容。您可以透過分割酬載或使用 document.write 等已淘汰的權宜方法,在有限的範圍內解決這個問題,但這些方法會帶來其他問題。

一組全新的靜態和串流 API

Chrome 提議一系列新 API 和現有 setHTMLsetHTMLUnsafe 的擴充功能,可清理這項問題,並導入串流功能:

您可以設定或取代現有 HTML,也可以在現有 HTML 前後插入內容。每種方法都有對應的串流:

動作 靜態 串流
設定元素的 HTML 內容 setHTML(html, options); streamHTML(options);
將整個元素替換為這個 HTML replaceWithHTML(html, options); streamReplaceWithHTML(options);
在元素前加入 HTML beforeHTML(html, options); streamBeforeHTML(options);
將 HTML 新增為元素的第一個子項 prependHTML(html, options); streamPrependHTML(options);
將 HTML 新增為元素的最後一個子項 appendHTML(html, options); streamAppendHTML(options);
在元素後方加入 HTML afterHTML(html, options); streamAfterHTML(options);
全新的插入和串流方式

我們稍後會介紹 Unsafe 版本。雖然這些方法可能看起來很多 (尤其是加入 Unsafe 對等項目時),但一致的命名慣例可讓您更清楚瞭解每個方法的用途,相較於先前提及的不相關方法更是如此。

靜態版本會將新的 HTML 做為 DOM 字串引數,以及選用選項:

const newHTML = "<p>This is a new paragraph</p>";
const contentElement = document.querySelector('#content-to-update');

contentElement.setHTML(newHTML);

串流版本可搭配 Streams API 使用,例如搭配 getWriter()

const contentElement = document.querySelector('#content-to-update');
const writer = contentElement.streamHTMLUnsafe().getWriter();

// Example stream of updating content
while (true) {
 await writer.write(`<p>${++i}</p>`);
 await new Promise((resolve) => setTimeout(resolve, 1000));
}

writer.close();

或者,您也可以透過 管道鏈從擷取回應中取得:

const contentElement = document.querySelector('#content-to-update');

const response = await fetch('/api/content.html');

response.body
  .pipeThrough(new TextDecoderStream())
  .pipeTo(contentElement.streamHTMLUnsafe());

我們也計畫新增便利方法,讓您直接串流,不必經過中繼 TextDecoderStream() 步驟。

options 引數可讓您指定自訂 sanitizer,預設為 default,也就是預設的清除器設定。使用方式如下:

const newHTML = "<p>This is a new paragraph</p>";
const contentElement = document.querySelector('#content-to-update');

// Only allows basic formatting
const basicFormattingSanitzer = new Sanitizer({ elements: ["em", "i", "b", "strong"] });

contentElement.setHTML(newHTML, {sanitizer: basicFormattingSanitzer});

「不安全」的方法

每個 API 也有「不安全」版本:

動作 靜態 串流
設定元素的 HTML 內容 setHTMLUnsafe(html,options); streamHTMLUnsafe(options);
將整個元素替換為這個 HTML replaceWithHTMLUnsafe(html, options); streamReplaceWithHTMLUnsafe(options);
在元素前加入 HTML beforeHTMLUnsafe(html, options); streamBeforeHTMLUnsafe(options);
將 HTML 新增為元素的第一個子項 prependHTMLUnsafe(html, options); streamPrependHTMLUnsafe(options);
將 HTML 新增為元素的最後一個子項 appendHTMLUnsafe(html, options); streamAppendHTMLUnsafe(options);
在元素後方加入 HTML afterHTMLUnsafe(html, options); streamAfterHTMLUnsafe(options);
「不安全」的插入和串流方法

這些「不安全」的方法預設會關閉清除器 (您可以視需要指定自訂清除器),並允許使用選用的 runScripts 選項執行指令碼 (預設為 false)。

setHTML 類似,setHTMLUnsafe 是現有的方法,但已新增 runScripts 選項參數,因此可搭配指令碼執行使用:

const newHTML = `<p>This is a new paragraph</p>
                 <script src=script.js></script>`;
const contentElement = document.querySelector('#content-to-update');

contentElement.setHTMLUnsafe(newHTML, {runScripts: true});

方法中的「不安全」字樣是為了提醒開發人員潛在風險,以及他們可能想清除或限制指令碼,並非表示不應使用這些方法。

這項操作的「不安全性」取決於輸入內容的信任程度。所有 Unsafe 靜態方法都可搭配 DOM 字串或 TrustedHTML 做為 html 引數,並允許使用清除器。不過,runScript 的整體意圖是允許指令碼,因此預設不會使用清除器。

用途

這些新版 API 採用一致的名稱和選項,可讓開發人員更輕鬆地在現有頁面中新增 HTML。串流 API 的優點是不必等到所有新內容都上傳到平台,就能開始串流播放。

用途包括:

  • 在單頁應用程式中動態串流播放大型內容更新。如先前所述,目前 SPA 的一大缺點是無法從初始 HTML 載入的串流性質獲益,但現在可以了!
  • 插入常見內容,例如 HTML 頁尾。使用 JavaScript API 即可擷取局部內容並插入網頁,享有快取功能,不必在每個傳送的網頁中重複這些內容。不過,由於這類內容必須依附 JavaScript 才能執行,因此只應適用於初始載入時不會顯示的內容。

再次提醒,以上只是幾個範例,我們很期待看到大家的作品!

限制和細微差異

這些新 API 也包含幾項限制和細微差異,請務必留意:

  • 如要將串流與 Trusted Types API 整合,必須使用新的 createParserOptions 方法,將清理器插入任何 HTML 設定作業。詳情請參閱信任的型別整合說明
  • <template for> 類似,移動串流中的元素可能會導致非預期的後果或串流錯誤。
  • streamHTMLUnsafe 在許多方面都與主要剖析器類似,包括在 <template for> 指令新增至主要文件時處理這些指令,以及將 defer 指令延後至串流結尾。

Polyfill

Chrome 團隊已發布 html-setters-polyfill,並在 npm 上提供,讓網站即使在其他瀏覽器支援這項功能前,也能立即使用。

請注意,這個 Polyfill 不會串流,而是會在完成時緩衝及套用。這比較像是 API 形狀的 Polyfill,而非功能。

此外,安全內容的設定取決於 setHTMLSanitizer API,但 Safari 不支援這兩者。

同時使用這兩項功能

雖然這兩個 API 各自獨立,但結合使用才能發揮真正的力量。將新的 <template for> 元素串流至 HTML,即可動態更新不同部分的內容,不必使用個別的 JavaScript 參照 DOM 直接指定每個部分。

您可以載入含有處理指令的架構頁面,然後將每個新網頁的範本串流至 HTML 底部,插入這些處理指令,即可實作基本的 SPA 樣式網頁載入。

這兩項 API 肯定還有更多潛在用途和使用案例,請盡情發揮想像力。簡化部分更新的管理作業,有助於減少樣板程式碼、簡化更新作業,並發掘網路的全新潛力!