# Service Worker 静态路由 API

> install 事件中的 addRoutes() 如何声明交给网络、缓存或 fetch 处理器的路由，哪些路由可以避免启动 service worker，以及它在什么情况下拒绝。

Service Worker 静态路由 API 让 service worker 在安装时声明哪些请求应由浏览器直接从网络或缓存获取，而不必为取回它们而运行 worker。声明的形式是在 `install` 处理器中传给 `event.addRoutes()` 的一组规则，每条规则把一个针对请求的条件与响应应当来自的来源配成一对。

## 语法

```js
addRoutes(routerRules)
```

`routerRules` 是单个规则对象或由它们组成的数组，方法返回一个以 `undefined` 兑现的 promise。该方法位于 `InstallEvent` 上，因此可从 `install` 处理器访问，一组规则也就属于安装它的那个 worker 版本。规范指出 `event.addRoutes(rules)` 默认延长事件的生命周期，就像调用了 `event.waitUntil(promise)` 一样。

## 各引擎支持情况

BCD `api.InstallEvent.addRoutes` 记录了从 Chrome 123 和 Safari 27 起的支持，并镜像到它列出的基于 Chromium 的浏览器与 Safari 衍生版本；Firefox 没有支持，BCD 指向 [Mozilla bug 1855580](https://bugzil.la/1855580) 说明实现状态。Chrome 的发布文章把该 API 描述为从 Chrome 123 起可用，之前自 Chrome 116 起以 `registerRouter()` 之名进行过源试用，该方法被这篇文章标记为已弃用，计划在 Chrome 125 中移除。

## 参数

一条规则是 `{ condition, source }`。下面这些条件键来自 MDN 的 [`addRoutes()` 参考页](https://developer.mozilla.org/en-US/docs/Web/API/InstallEvent/addRoutes)；当一个条件中同时出现多个键时，资源必须满足全部条件才匹配该规则。

| 条件键 | 取值 | 匹配什么 |
|---|---|---|
| `urlPattern` | 一个 [`URLPattern`](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) 实例，或可传入其构造函数的输入模式 | 匹配规则的那些 URL。不允许正则捕获组，因此 `URLPattern.hasRegExpGroups` 必须为 `false`。 |
| `requestMethod` | 字符串，如 `"get"`、`"put"`、`"head"` | 发送请求所用的 HTTP 方法。 |
| `requestMode` | 字符串，如 `"same-origin"`、`"no-cors"`、`"cors"` | 请求具有的模式。 |
| `requestDestination` | 字符串，如 `"audio"`、`"document"`、`"script"`、`"worker"` | 请求的目标，即请求的内容类型。 |
| `runningStatus` | `"running"` 或 `"not-running"` | service worker 所需的运行状态。 |
| `or` | 条件对象数组 | 其中一组嵌套条件。`or` 内的条件与其他条件互斥。 |
| `not` | 一个条件对象 | 必须明确不被满足的条件。`not` 内的条件与其他条件互斥。 |

`source` 是一个枚举值或一个对象。MDN 列出的枚举值为 `"cache"`、`"network"`、`"fetch-event"` 与 `"race-network-and-fetch-handler"`，最后一个会同时尝试网络与 fetch 事件处理器，并采用先完成的那一个。MDN 把 `"fetch-event"` 与 `runningStatus` 条件搭配使用：worker 运行时经由它加载资源，不运行时回退到网络上的静态路由。对象形式携带单个 `cacheName` 属性来指名一个缓存；MDN 指出如果该缓存不存在，浏览器会改用网络，这样在网络可用时仍能取到所请求的资源。

## 异常

MDN 列出的 `TypeError` 成因有：`routerRules` 内有无效的规则对象、worker 没有 fetch 事件处理器时 `source` 为 `"fetch-event"`，以及 `or` 与另一类条件组合使用。

- 无效规则会让携带它的那次调用被拒绝，所以在上线前先校验生成出来的规则列表。
- `"fetch-event"` 与 `"race-network-and-fetch-handler"` 这两个来源依赖已注册的 `fetch` 处理器。[`addRoutes()` 算法](https://w3c.github.io/ServiceWorker/#register-router-method) 规定：当 `source` 为 `"fetch-event"` 或 `"race-network-and-fetch-handler"`，而 service worker 要处理的事件类型集合不包含 `fetch` 时，返回一个以 `TypeError` 拒绝的 promise。`source` 为 `"network"`、`"cache"` 或 `cacheName` 对象的规则不在该步骤的指名范围内。
- 条件中 `or` 与同级键并列时必须改写为两条规则，每条对应一个分支并重复该同级键。MDN 自己给出的改写示例就把 `urlPattern` 与 `requestMethod` 配对了两次。
- 当 Check Router Registration Limit 算法（在 worker 现有路由规则列表的副本追加新规则后运行）返回 false 时，规范同样以 `TypeError` 拒绝该 promise。

:::observed
Chromium 的规则转换器 `third_party/blink/renderer/modules/service_worker/service_worker_router_type_converter.cc` 抛出 `TypeError` 时使用这些确切字符串（[Chromium 源码](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/service_worker/service_worker_router_type_converter.cc)）：

```text
At least one condition must be set, but no condition has been set to the rule.
Cannot set other conditions when the `or` condition is specified
Cannot set other conditions when the `not` condition is specified
fetch-event source is specified without a fetch handler
race-network-and-fetch-event source is specified without a fetch handler
```

竞速来源的消息把它叫作 `race-network-and-fetch-event`，而规则设置的 `source` 取值是 `race-network-and-fetch-handler`。
:::

空的条件对象会被拒绝，而不是成为匹配一切的规则，这正是上面的第一条字符串。

## 示例

下面这些示例是多数应用需要的形态：在某个路径上绕过 worker、在方法存在时才声明路由，以及用一个命名缓存服务图片扩展名。

### 在 worker 处于冷状态时把路径交给网络

条件把 URL 模式与 `runningStatus` 组合起来，因此该规则在 service worker 不运行时生效，而这正是会付出启动开销的情形。

```js
addEventListener('install', (event) => {
  event.addRoutes({
    condition: {
      urlPattern: '/articles/*',
      runningStatus: 'not-running',
    },
    source: 'network',
  });
});
```

worker 正在运行时这条规则不匹配，请求会到达 `fetch` 处理器，因此同一路径上的缓存优先策略在热导航中仍然适用。Chrome 的发布文章展示了镜像写法：`runningStatus: "running"` 配 `source: "fetch-event"`。

### 在方法存在时才声明路由，并带回退

`addRoutes()` 是事件对象上的方法，所以使用前可以检测。缺失分支不是降级模式：它就是静态路由出现之前 service worker 的行为，一切由 `fetch` 处理器决定。

```js
addEventListener('install', (event) => {
  if (!('addRoutes' in event)) {
    // 该事件上没有 addRoutes()：把路由交给 fetch 处理器。
    return;
  }
  event
    .addRoutes([
      { condition: { urlPattern: '/form/*', requestMethod: 'post' }, source: 'network' },
      { condition: { requestDestination: 'font' }, source: { cacheName: 'fonts' } },
    ])
    .catch((error) => {
      console.error('static routes rejected, using the fetch handler instead', error);
    });
});
```

保留 `catch`。该方法以拒绝而不是同步抛出的方式报告失败，因此没有处理器的拒绝会让应用仅靠 fetch 处理器运行，并表现为一个未处理的 promise 拒绝。

### 用一个命名缓存服务两种图片扩展名

`or` 接受一组条件且与其他条件互斥，所以扩展名放进数组里，共享的来源留在数组外面。

```js
addEventListener('install', (event) => {
  event.addRoutes({
    condition: {
      or: [{ urlPattern: '*.png' }, { urlPattern: '*.jpg' }],
    },
    source: { cacheName: 'pictures' },
  });
});
```

若要把同样这两个模式收窄到 `GET` 请求，就写两条规则，各自带自己的 `urlPattern` 与 `requestMethod`，这也是 MDN 针对这种情况给出的改写方式。

Chrome DevTools 会显示浏览器实际注册了什么：Chrome 的发布文章指出，已注册的路由规则显示在 Application 面板的 Service Worker 标签中；在 Network 面板里，匹配到已注册规则的请求会在 size 列中标出，指针悬停时显示已注册的路由 ID。

## 另请参阅

- [FetchEvent 与请求路由](/zh/reference/service-worker/fetch-event/)
- [Service worker 缓存策略](/zh/reference/service-worker/caching-strategies/)
- [NavigationPreloadManager](/zh/reference/service-worker/navigation-preload/)
- [Service worker 生命周期](/zh/reference/service-worker/lifecycle/)
- [InstallEvent: addRoutes() method](https://developer.mozilla.org/en-US/docs/Web/API/InstallEvent/addRoutes)（developer.mozilla.org）
- [Service Workers: the addRoutes() method](https://w3c.github.io/ServiceWorker/#register-router-method)（w3c.github.io）
- [Use the Service Worker Static Routing API to bypass the service worker for specific paths](https://developer.chrome.com/blog/service-worker-static-routing)（developer.chrome.com）