跳转到内容

性能 · 概念

资源提示(preload 与 preconnect)

发布于

资源提示是 <head> 中的 <link> 关系,让工作在解析器发现需要之前就开始:preload 抓取当前页面将要使用的一个资源,modulepreload 对 ES 模块及其依赖做同样的事,preconnect 只打开到某个源的连接而不抓取,dns-prefetch 只解析域名,prefetch 以低优先级为后续导航抓取资源。在 PWA 中它们缩短的是首次访问,那时 Service Worker 还什么都没缓存。

预加载扫描器在 HTML 早期找到 <link> 提示,并按 as 类型隐含的优先级排队:as="style" 以最高优先级抓取,as="font" 与 as="script" 为高,as="image" 按图片的通常优先级,除非 fetchpriority 另有指定。预加载的响应在内存缓存中等待真正的请求,后者的 URL、目标类型和 CORS 模式都必须与预加载一致;不一致就意味着两次抓取(Preload critical assets,web.dev)。

  • as。 必填。没有它,抓取被当作优先级错误、Accept 头错误的 XMLHttpRequest,与后来的请求对不上。
  • crossorigin。 字体无论来自哪个源都以匿名 CORS 模式抓取,所以字体的 preload 即使是同源文件也需要 crossorigin;<script crossorigin> 或 fetch() 同样需要 preload 上有匹配的 crossorigin。
  • type。 让浏览器跳过它不支持的格式的预加载,这就是 WOFF2 预加载在会使用 WOFF 兜底的引擎里仍然无害的原因。

跨源请求确定且很快会发生时(字体主机、API 源),preconnect 物有所值;每个连接占一个套接字,超过几个就适得其反,其余用更便宜的 dns-prefetch。modulepreload 是页面启动时需要的 import() 分块的预加载,它还会链接模块,让求值更早开始。prefetch 面向下一次导航而不是当前页面,对于文档已被 Speculation Rules 取代。

preload 在 Chrome 50 和 Safari 11.1 发布;Firefox 在 56 启用、57 因 Web 兼容性问题再次禁用,85 正式发布(BCD html.elements.link.rel.preload,bug 1405761)。preconnect 在 Chrome 46、Firefox 39、Safari 11.1;modulepreload 在 Chrome 66、Firefox 115、Safari 17;prefetch 在 Chromium 和 Firefox 中可用,Safari 中在标志之后。不支持的提示会被忽略,页面照常加载,只是少了那段提前量。

标记示例是常见情形;脚本示例覆盖运行时添加的提示。

字体只有在样式表解析之后才会被发现;提示让它随 HTML 一起开始。crossorigin 是必需的,否则 Chrome 会为 @font-face 请求再抓取一次。

<link rel="preload" href="/fonts/brand-sans.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preconnect" href="https://api.example.com">
<link rel="modulepreload" href="/scripts/app.3f2a1c.js">

preconnect 预热外壳渲染后要调用的 API 源;modulepreload 在到达模块脚本标签之前就启动主分块及其导入。

运行时插入的提示(例如某个路由变得可能时)与 HTML 中的行为一样。先设置 as 和 crossOrigin 再设置 href,抓取才会以正确的参数开始。

function preloadFont(href) {
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'font';
link.type = 'font/woff2';
link.crossOrigin = 'anonymous';
link.href = href;
document.head.append(link);
}

页面没有用到的 preload 会触发下面描述的控制台警告,所以只为当前页面会请求的资源插入提示。

link.relList.supports() 报告引擎是否认识某个关系,这是依赖 modulepreload 或 prefetch 之前该做的测试;兜底就是什么都不做,因为真正的请求之后仍会发生。

function supportsRel(rel) {
const link = document.createElement('link');
return 'relList' in link && typeof link.relList.supports === 'function' && link.relList.supports(rel);
}
if (supportsRel('modulepreload')) {
preloadModule('/scripts/editor.3f2a1c.js');
} else {
// 不支持:import() 运行时会自行抓取分块
}

supports() 从引擎的关系列表而不是网络测试作答,所以便宜到可以逐个提示调用。

规范

规范状态
无。