声明式部分更新

发布时间: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,这些 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> 时,模板在解析时的“父级”是中间文档片段。这意味着,使用这些方法插入 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
  • innerHTML和outerHTML
  • createContextualFragment
  • insertAdjacentHTML

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

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

很少有开发者能够坦然面对这些 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,并对现有的 setHTML 和 setHTMLUnsafe 进行了扩展,以清理这些问题,同时还引入了流式传输功能。

这些功能已准备就绪,开发者可从 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.

Source

我们还添加了 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”一词是为了提醒开发者注意潜在风险,以及他们可能需要如何清理或限制脚本,而不是说不应使用这些方法。

这有多“不安全”取决于输入的可信度。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 脚本推迟到数据流结束。

标准化状态

较新的插入和流式传输方法正在添加到 HTML 标准中,但尚未在所有浏览器中得到支持。

Polyfill

Chrome 团队发布了一个 html-setters-polyfill,该 可在 npm 上获取,让网站可以立即使用这项新功能,甚至在其他浏览器中实现此功能之前。

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

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

两者结合使用

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

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

毫无疑问,这两个 API 还有更多潜力和用例,因此请不要让我们的(有限!)想象力限制了您的发挥。通过让部分更新更易于管理,您可以减少一些样板代码,让更新更轻松,并释放 Web 的新潜力!

Chrome 团队正在声明式部分更新项目下开发更多 API,但我们很高兴能先将这两个 API 交到您手中。我们会及时通知您,让您了解此领域的更多动态。