HTML一直以来都支持流式传输。服务器在将页面发送给浏览器之前,并不需要先将整个页面内容加载到内存中。它可以先发送初始的HTML代码,然后等到后续部分准备就绪后再依次发送这些数据。浏览器会按照接收到的顺序解析这些数据并显示页面内容,这就是为什么HTML看起来运行速度很快的原因之一。
然而,传统的HTML流式传输方式存在一个严格的规则:所有内容必须按照文档中的顺序进行传输。如果浏览器先收到了页头信息,接着是侧边栏的内容,最后才是主体内容,那么它也会按照这个顺序来解析这些数据。如果数据库查询耗时过长,导致某些页面内容无法及时传输,那么后续的部分就不得不等待服务器完成处理才能继续发送。
多年来,JavaScript框架一直在努力解决这个问题。服务器端渲染框架能够处理页面结构的构建、加载状态的管理以及延迟内容的传输等问题。有些框架会使用内嵌脚本来修改现有的DOM结构,而像HTMX这样的库则允许开发者使用服务器生成的HTML代码来更新页面的特定部分。
不过,这些解决方案都需要依赖JavaScript才能实现。而“声明式部分更新”这种技术则提出了另一种思路:如果HTML本身能够有一种方式来指定,“当这部分内容到达时,应该将其放置在哪里”,那该多好呢?
这就是Chrome提出的声明式部分更新机制背后的理念。
在本文中,你将了解到声明式部分更新旨在解决哪些问题,这种技术所使用的占位符语法是如何工作的,非顺序传输的HTML内容与传统流式传输方式有何不同,相关的JavaScript API又是如何发挥作用的,以及为什么应该将这一功能视为浏览器的实验性特性,而不是可以直接投入生产的成熟技术。
目录
声明性部分更新试图解决什么问题
以产品页面为例。服务器已经知道了页面的标题、导航栏、页脚以及产品详情,但推荐信息部分的显示需要执行耗时的数据库查询。使用传统的服务器渲染HTML技术,有两种常见的处理方式:
-
第一种方法是:服务器等待所有内容准备就绪后再发送完整的HTML响应。这种方式可以使代码结构保持简洁,但用户需要等待较长时间才能看到有用的内容。
-
第二种方法是:服务器分阶段发送HTML内容。先发送页面的上半部分,然后随着后续内容的准备完毕再依次发送剩余部分。这种方法似乎可以提高性能,因为浏览器可以在完整的响应数据送达之前就开始进行渲染。
然而,仅仅采用分段传输的方式并不能完全解决这个问题。浏览器仍然会按顺序解析HTML代码。如果文档的开头部分包含耗时的推荐信息,那么后面的内容就会被延迟显示,除非对文档结构进行重新设计、添加JavaScript代码或使用框架来辅助处理。
WICG的相关说明指出了传统HTML分段传输技术的两个局限性:
-
HTML内容是按照DOM的结构顺序进行分阶段传输的。
-
在完成最初的文档解析步骤之后,后续的分段传输操作就不会像最初那样频繁进行了。
声明性部分更新技术试图突破这一限制。它允许服务器先发送占位内容,然后再在响应中发送实际的内容。浏览器会将新的内容覆盖在原有的占位内容上。这种技术不需要任何自定义的客户端DOM操作代码。

在上面的示意图中,服务器会按顺序发送不同的HTML片段。浏览器可以在响应数据完全送达之前就开始渲染前面的片段,但最终的渲染顺序仍然与服务器发送数据的顺序一致。
传统HTML分段传输的工作原理
在研究这一技术方案之前,首先需要了解它的基本结构。服务器会发送一个HTTP响应体,该响应体中包含HTML代码。浏览器一收到响应数据就会开始对其进行解析,而不必等到整个响应体全部送达后再开始处理。
下面是一个简单的Node.js示例:
import http from "node:http";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const server = http.createServer(async (req, res) => {
res.writeHead(200, {
"Content-Type": "text/html; charset=utf-8",
});
res.write(`
<!doctype html>
传统HTML分段传输示例
传统HTML分段传输示例
这部分内容会首先被发送。
`);
await sleep(2000);
res.write(`
这部分内容会在两秒后发送。
这部分内容会在四秒后发送。