声明式部分更新

发布时间:2026 年 5 月 19 日,上次更新时间:2026 年 9 月 8 日

网络早已不再是最初的静态、以文档为导向的媒体。现代化的丰富 Web 应用被所有人广泛使用,原因有很多,从通信、购买、消费富媒体内容到管理我们复杂的生活。

尽管 HTML 取得了诸多进步,但仍以从上到下的顺序交付,很少考虑内容何时准备就绪或用户何时使用。借助 CSS,您可以更改内容的顺序,但通常会产生严重的无障碍副作用。JavaScript 可让您通过各种 API 来操控 DOM,从而在一定程度上摆脱这种限制,但这些 API 通常需要冗长的语法,或者需要构建 DOM 树来插入 HTML。

鉴于 Web 具有客户端-服务器性质,性能对于 Web 来说非常重要。不过,为了规避 HTML 的这种顺序特性,人们往往会做出次优选择,从而降低性能。这包括等待整个页面准备就绪,或使用大型框架以异步方式交付组件。JavaScript 框架的普及表明,Web 开发者更喜欢基于组件的模型,而不是 Web 最初的僵化文档心理模型。

Chrome 团队一直在考虑这个问题,并一直在开发名为声明式部分更新的 Web 平台新功能。

前两组新 API 可让您更轻松地以非线性方式传送 HTML,无论是在 HTML 文档本身中以无序方式传送,还是通过更简单的方式使用新的 JavaScript API 将 HTML 动态插入到现有文档中。您还可以使用 Polyfill 立即使用这些新 API,即使在尚不支持这些 API 的浏览器中也是如此。

无序流式传输

Browser Support

  • Chrome: 150.
  • Edge: 150.
  • Firefox: not supported.
  • Safari: not supported.

Source

第一组更改是新的无序流式传输 API,它们使用处理指令占位符和带有 for 属性的 <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"> 处理指令时,它不会立即执行任何操作(与之前一样),但可以在稍后引用该指令。

具有 for 属性<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 元素?

对于初次使用此 API 的用户,这是一个常见问题,建议使用 <slot> 甚至 <template> 元素本身。此提案的初始版本使用标准 HTML 元素,但随着设计不断迭代,处理指令被切换为。处理指令允许在不影响 DOM 的情况下进行修补。这样一来,此元素便可用于 <head> 中(例如,用于 <title> 更新),甚至可用于其他元素中,例如 <table>(用于添加可选的其他行)。

尽管许多 Web 开发者对处理指令的语法并不熟悉,但处理指令的灵活性更高,出现向后兼容性问题的可能性更小。它们已在 XML 中使用,并且由于此提案,现在已成为 HTML 标准的一部分

演示

在此视频中,我们使用流式 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> 只能更新同一父元素内的处理指令。直接向 <body> 元素添加 <template for> 可使其访问整个文档(包括 <head>)。
  • <?end> 处理指令是可选的,如果缺失,系统会替换 <?start> 元素与包含元素末尾之间的内容。
  • <template for> 开始流式传输后移动处理指令也可能会产生意想不到的后果,新内容会继续流式传输到旧位置。
  • 请注意,如果使用 setHTML 等方法或 innerHTML 属性动态插入 <template for>,则在解析模板时,模板的“父级”是一个中间文档 fragment。这意味着,使用这些方法插入 HTML 无法修改现有 DOM,并且修补会在 fragment 内“就地”进行。不过,如果使用 streamHTMLUnsafe 等方法进行流式传输(稍后会介绍),则不会有中间 fragment,因此模板可以替换现有内容。

标准化状态

<template for> 属性是 HTML 标准的一部分,但尚未在所有浏览器中得到支持。

未来可能新增的功能

我们正在考虑在未来添加以下功能:

  • 客户端包含项。例如,<template for="footer" src="/partials/footer.html"> 或甚至无需修补的 <template src="/partials/footer.html">。如需了解详情,请参阅说明。此功能在 chrome://flags/#enable-experimental-web-platform-features 标志后面提供。
  • 防止覆盖不会更改的内容。这可以通过内容修订号或版本控制来实现。这样一来,状态就可以在路线更改或其他更新之间保持不变,而不是重置内容。
  • 在修补时进行清理。例如 <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

不过,它们的工作方式略有不同,存在一些细微差别,开发者可能并不总是会考虑到这些差别:

  • 新内容是覆盖还是附加?
  • 它们是否会清理可能存在危险的 HTML(例如,通过转义 <script> 标记)?
  • 如果不是,是否应运行 <script>
  • 它们如何与 Trusted Types 搭配使用?

很少有开发者能诚实地查看这些 API,并自信地回答有关每个 API 的这些问题。

一个很大的限制是,它们只能用于预先知道的完整 HTML 集,前提是已调用过允许流式传输 HTML 的函数。实际上,这意味着您需要先下载整个内容,然后才能插入,而 HTML 的一个优势是能够立即流式传输内容。虽然可以通过拆分载荷或使用 document.write 等过时的变通方法来解决此问题,但这些方法会带来其他问题。

一组新的静态和流式传输 API

Browser Support

  • Chrome: behind a flag.
  • Edge: behind a flag.
  • Firefox: not supported.
  • Safari: not supported.

Chrome 推出了一套新的 API 和扩展程序,对现有的 setHTMLsetHTMLUnsafe 进行了清理,并引入了流式传输功能。

从 Chrome 148 开始,开发者可以使用 chrome://flags/#enable-experimental-web-platform-features 标志测试这些功能,并且计划在 Chrome 155 中发布这些功能。

您可以使用相应方法来设置或替换内容,也可以使用相应方法在现有 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());

textStream() 便捷方法

Browser Support

  • Chrome: 151.
  • Edge: 151.
  • Firefox: not supported.
  • Safari: not supported.

还添加了一个 textStream 便捷方法,您无需执行中间的 TextDecoderStream() 步骤即可直接进行流式传输:

const contentElement = document.querySelector('#content-to-update');
const response = await fetch('/api/content.html');

response.textStream().pipeTo(contentElement.streamHTMLUnsafe());

options

借助 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。借助流式 API,您无需等到所有新内容都可供平台使用,即可获得性能优势。

用例包括:

  • 在单页应用中动态流式传输大型内容更新。如前所述,当前 SPA 架构的一大缺点是无法受益于初始 HTML 加载的流式传输特性,但这种情况现在已经改变!
  • 插入 HTML 页脚等常见内容。使用 JavaScript API 可让您提取部分内容并将其插入网页中,从而受益于缓存,而不是在发送的每个网页中重复这些内容。不过,由于需要依赖 JavaScript 才能运行,因此这种方法仅适用于在初始加载时不会显示的内容。

同样,这些只是几个示例,我们非常期待看到大家的创意!

限制和细微差别

这些新 API 还包含一些需要注意的限制和细微差别:

  • 将流式传输与 Trusted Types API 集成需要使用新的 createParserOptions 方法,该方法允许将清理器注入到任何 HTML 设置操作中。如需详细了解可信类型集成,请参阅相关说明
  • <template for> 类似,将正在流式传输的元素移入其中可能会产生意外后果或导致流式传输错误。
  • streamHTMLUnsafe 在许多方面都更像主解析器,包括在将 <template for> 指令添加到主文档时对其进行处理,以及将 defer 脚本延迟到流结束时再处理。

标准化状态

较新的插入和流式传输方法正在添加到 HTML 标准中,但并非所有浏览器都支持这些方法。

Polyfill

Chrome 团队已发布 html-setters-polyfill可在 npm 上获取),以便网站即使在其他浏览器中尚未实现此功能之前,也能立即使用此新功能。

请注意,此 polyfill 不会进行流式传输,而是在完成后进行缓冲和应用。它更像是 API 形态的填充区,而不是功能填充区。

此外,设置安全内容取决于 setHTMLSanitizer API,而 Safari 不支持该 API。

将这两者结合使用

虽然这两个 API 是分开的,但将它们结合使用才能发挥出真正的威力。通过将新的 <template for> 元素流式传输到 HTML 中,您可以动态更新内容的不同部分,而无需使用单独的 JavaScript 引用直接定位到 DOM 中的每个部分。

基本的 SPA 样式网页加载可以通过以下方式实现:加载包含处理指令的轮廓网页,然后将每个新网页的模板流式传输到 HTML 的底部,以插入到这些处理指令中。

毫无疑问,这两种 API 还有更多潜力和用例,因此不要让我们的(有限的!)想象力阻碍您。通过更轻松地管理部分更新,您可以减少一些样板代码,更轻松地进行更新,并为 Web 释放新的潜力!

Chrome 还在声明式部分更新项目下开发其他一些 API,但我们很高兴能让您率先使用这两项 API。我们会及时向您通报最新动态,并在这一领域推出更多功能时通知您。