本文介绍一种采用HTTP Link响应标头的资源预加载方式http-preload,通过预先定义preload manifest文件来描述哪个页面需要预加载哪些资源,并使用Node.js, Tomcat, Nginx等多种服务端的middleware/filter读取preload manifest按照其中的描述规则来实现页面资源预加载。
背景
Chrome 103新增了对HTTP状态码 103 (Early Hints)的支持。在此之前的四五年,HTTP状态码 103只是个传说,因为没有浏览器支持它。现在虽有浏览器支持它,但服务端支持HTTP status 103的框架/库却寥寥无几。
操作
第一步:定义Preload Manifest
{
"$schema": "https://raw.githubusercontent.com/http-preload/manifest/master/preload-v1.schema.json",
"manifestVersion": 1,
"conditions": {
"supportsModulepreload": "(userAgentData, headers) => userAgentData.brands.some((e)=>e.brand==='Chromium'&&parseInt(e.version)>=66)"
},
"resources": {
"/index.html": [
{"rel": "preload", "href": "/assets/index.css", "as": "style"},
{"rel": "dns-prefetch", "href": "https://example.com/"}
],
"/index.html supportsModulepreload": [
{"rel": "modulepreload", "href": "/src/foobar.js"},
{"rel": "modulepreload", "href": "/lib/foo.js"},
{"rel": "modulepreload", "href": "/lib/bar.js"},
{"rel": "modulepreload", "href": "/src/qux.js"}
]
}
}
其中:
- "$schema"指的是JSON Schema URL,也可以用相对路径指向离线版的preload-v1.schema.json,用以在IDE(如Visual Studio Code)中提供内容辅助、格式校验。
- "manifestVersion"的值应与所用json schema对版本的要求相匹配
- "conditions"中每个key是JavaScript标识符(仅ASCII子集),每个value应为函数表达式或箭头函数。
- 其中参数userAgentData会是类似于Client Hints User Agent API
navigator.userAgentData
一样的对象,但不限于安全上下文(https协议) - 参数headers会是一个包含所有请求头的对象(在Node.js HTTP/2实现中还包含伪请求头:method, :path, :scheme等)
- 其中参数userAgentData会是类似于Client Hints User Agent API
- “resources"中每个key是页面文件的URI,可选地加一个空格” "和一个已在"conditions"中定义的条件标识符。
该描述文件的作用为:
当服务端收到URI(不含querystring)为"/index.html"的HTTP请求时(或从"/“转发而来),”/index.html"对应的资源列表将被无条件地列为预加载Link请求头,而"/index.html supportsModulepreload"对应地资源列表是否应该发送到客户端取决于函数调用supportsModulepreload(userAgentData, headers)
的返回值是否等效于true
详细说明请参见 manifest
第二步:安装使用Preload Filter / Middleware
preload-middleware支持在Node.js serve-index, express, koa, local-web-server使用http-preload
preload-servlet4-filter preload-servlet-filter分别支持在Java 1.8 + Tomcat 9, Java 11 + Tomcat 10中使用http-preload
还有preload-njs-filter,支持在Nginx+njs环境中使用的部分http-preload特性(需要手动重新改njs源码并编译njs;尚不支持HTTP status 103 Early Hints)
P.S. Nginx生态虽有模块ngx_http_early_hints声称支持103 Early Hints,但它在HTTP/2.0协议中不可用,更要命的是它很久没得到更新,不兼容较新的Nginx 1.22.0。
最好让http-preload与HTTP/2.0配套使用,因为预加载会使得但并发请求增多,使用HTTP/1.1协议容易导致并发连接过多时HTTP请求出现排队现象,从而有违预加载的初衷。
第三步:测试预加载的效果
检出并运行上述preload filter/middleware git仓库并运行示例程序,打开浏览器DevTools,并访问本地https://127.0.0.1/ 你将发现之前ES模块加载的依赖链变平了,页面用的到资源都会预先加载。
通过HTTP Link:
标头的方式来实现预加载理论上比通过HTML <link>
标签的方式来得更及时,但也会略微增加每个请求的TTFB(Time to First Byte),因为服务端要多增加一层过滤器,多执行至少2个if判断。因此不能保证采用http-preload会比采用<link>
标签效果更好,开发者是否采用,应以实际测验结果为依据。一般来说,在网络延迟较高的环境下采用http-preload,页面整体的加载速度优势会明显一些。