# 资源提示（preload 与 preconnect）

> link rel=preload、preconnect、modulepreload 与 prefetch 各提前启动什么、避免重复抓取的 as 与 crossorigin 规则，以及 Chrome 对未用 preload 的警告。

资源提示是 `<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](https://web.dev/articles/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](/zh/reference/performance/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 中在标志之后。不支持的提示会被忽略，页面照常加载，只是少了那段提前量。

## 示例

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

### 预加载首次绘制需要的 Web 字体

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

```html
<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`，抓取才会以正确的参数开始。

```js
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` 之前该做的测试；兜底就是什么都不做，因为真正的请求之后仍会发生。

```js
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()` 从引擎的关系列表而不是网络测试作答，所以便宜到可以逐个提示调用。

:::observed
对没有任何请求消费的 preload，Chrome 在 `load` 之后几秒向 Console 打出警告：`The resource https://example.com/fonts/brand-sans.woff2 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate` `` `as` `` `value and it is preloaded intentionally.`。最常见的触发原因是没带 `crossorigin` 的字体预加载：Network 面板里该 WOFF2 出现两次，一次来自提示、一次来自 `@font-face`，而警告之所以触发是因为第一份副本没有匹配上（[Preload critical assets](https://web.dev/articles/preload-critical-assets)，web.dev）。
:::

## 另请参阅

- [rel=preload](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/preload)（developer.mozilla.org）
- [Preload critical assets to improve loading speed](https://web.dev/articles/preload-critical-assets)（web.dev）
- [browser-compat-data 中的 link.json](https://github.com/mdn/browser-compat-data/blob/main/html/elements/link.json)（github.com）
- [字体与图片优化](/zh/reference/performance/fonts-images/)
- [Speculation Rules API](/zh/reference/performance/speculation-rules/)
- [用动态 import() 做代码拆分](/zh/reference/performance/code-splitting/)