# 预缓存策略：提前把应用外壳送达

> 预缓存如何在 service worker 安装期间填充缓存，让 PWA 的外壳即时且离线加载——哪些该预缓存、哪些该运行时缓存，为何内容修订至关重要，以及 Workbox 的预缓存清单如何将其自动化。

**一句话：** *预缓存*在 service worker 的 **install** 步骤中，把一组已知、固定的文件存入
缓存，使应用外壳在被需要之前就已在设备上——安装后的首次导航能即时且离线地渲染。它是
*运行时缓存*的对应物，后者在用户请求时才存储响应。

## 预缓存对比运行时缓存

- **预缓存** —— 一份在构建时已知的固定清单（HTML 外壳、CSS、JS、图标、字体）。在 `install`
  期间、页面请求之前取回并存储。适合**应用外壳**及其他关键、始终需要的资源。
- **运行时缓存** —— 在 `fetch` 处理器中随请求发生而填充，按选定的缓存策略进行。适合多变、
  或太大/太多以致无法预先列出的内容（API 响应、用户图片、文章）。

预缓存与运行时缓存是互补的：预缓存外壳让首次加载快速且具备离线能力，并围绕它运行时缓存
动态内容。

## 预缓存什么（以及不缓存什么）

- **预缓存：** 最小的应用外壳——渲染一个可用首屏所需的 HTML/CSS/JS 与图标。这里列出的一切
  都会在 `install` 事件期间被下载并存储。
- **改用运行时缓存：** 多变、体积大或按需请求的内容，通常用运行时缓存策略处理，而非预缓存。

预缓存骨架；让运行时策略处理其余。

## 为何内容修订是核心问题

只有当 service worker 能判断某个预缓存文件何时**已变更**，预缓存才安全。如果你预缓存了
`/app.js`，之后在同一 URL 部署了新的 `/app.js`，service worker 需要一个表明内容不同的信号，
否则用户会一直保留陈旧文件。

让变化可被检测的两种方式：

- **带版本的 URL**（`/app.abc123.js`）：内容变化时 URL 本身就会变，因此预缓存条目自然是新
  的。Workbox 会把一个已携带版本信息（如内容哈希）的 URL 视为其自身的修订。
- **每条目一个修订号：** 把每个无版本的 URL（如 `/index.html`）与一个内容修订号配对，这样
  即使 URL 相同，当修订号不同时缓存也会重新取回。

这套簿记正是构建期的**预缓存清单**所自动化的——手工维护极易出错，这也是为何预缓存通常委托
给库来做。

## Workbox 预缓存的实践

Workbox 的 `workbox-precaching` 模块消费一份预缓存清单（构建期生成的 `{ url, revision }` 条目
列表），并为你处理整个生命周期：

- 在 `install` 期间，取回并存储每个列出的资源。
- 在 `activate` 期间，移除先前预缓存中已不在清单里的条目，使旧外壳版本被自动清理。
- 它从缓存提供预缓存的响应，并在某条目的修订号变化时重新取回。

```js
import { precacheAndRoute } from 'workbox-precaching';

// self.__WB_MANIFEST 在构建期被注入 { url, revision } 条目。
precacheAndRoute(self.__WB_MANIFEST);
```

Workbox 提供构建工具——`workbox-build`、`workbox-webpack-plugin` 与 `workbox-cli`——来从你
实际的构建产物生成该预缓存清单，使清单与修订号在每次部署时保持同步。

## 与 HTTP 缓存的关系

预缓存存放于 Cache Storage 层，独立于浏览器的 HTTP 缓存。两者可能相互影响：一次预缓存取回
本身可能被 HTTP 缓存应答，从而把陈旧副本送进你的预缓存。在这种风险要紧之处，使用带版本的
URL，使每个版本都是不同的请求。留意两层的生命周期，别让它们彼此对抗。

## 实践清单

- [ ] 只预缓存最小的应用外壳；动态与大型内容用运行时缓存。
- [ ] 优先使用带版本（哈希）的 URL，让变更的资源成为不同的请求。
- [ ] 对无版本的 URL（如 `/index.html`），将其与内容修订号配对。
- [ ] 用 Workbox 工具在构建期生成预缓存清单；不要手工维护这份列表。
- [ ] 依赖 Workbox 在 `activate` 中清理被取代的预缓存。
- [ ] 警惕可能把陈旧资源预缓存进来的 HTTP 缓存交互。