# Service worker 注册与 scope

> register() 如何把脚本绑定到 URL 前缀 scope，默认 scope 与 Service-Worker-Allowed 如何限定它，重叠如何裁决，以及 updateViaCache。

一条 service worker 注册把一个脚本 URL 与一个 *scope*（同源上的 URL 前缀）配对；浏览器会把 URL 以该前缀开头的每一次导航和子资源请求交给这个 worker 处理。`register()` 自 Chrome 40、Firefox 44、Safari 11.1、Edge 17 起行为未变（BCD `api.ServiceWorkerContainer.register`），`updateViaCache` 选项则自 Chrome 68、Firefox 57、Safari 11.1 起可用。

## 工作原理

`navigator.serviceWorker.register(scriptURL, { scope, type, updateViaCache })` 返回 `Promise<ServiceWorkerRegistration>`。这个调用是幂等的：用相同脚本与 scope 重复调用会直接兑现为已有注册、不会触发安装，因此页面可以在每次加载时都调用它。同一 scope 换一个脚本 URL 会替换注册（新 worker 走正常的安装与等待流程）；换一个 scope 则创建第二条注册。

scope 默认为脚本所在目录：`/sw.js` 得到 `/`，`/app/sw.js` 得到 `/app/`。注册可以用选项收窄 scope（`/app/sw.js` 配 `scope: '/app/admin/'`），但不能把它放宽到脚本目录之外，除非返回脚本的响应带有指明更宽路径的 `Service-Worker-Allowed` 头。没有这个头时，`register('/app/sw.js', { scope: '/' })` 会以 `SecurityError` 拒绝，Chromium 的原文是 `The path of the provided scope ('/') is not under the max scope allowed ('/app/'). Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope.`。

scope 匹配是对 URL 的纯字符串前缀比较，不按路径段比较：scope `/app` 也会匹配 `/application/`，所以 scope 应以斜杠结尾。同一源上多条注册重叠时，浏览器为每个请求选择匹配 scope 最长的那条，因此对 `/app/admin/users` 而言 `/app/admin/` 胜过 `/app/`。scope 只限制 worker 控制哪些*客户端*；worker 自己发出的 `fetch()` 不受限制。

`updateViaCache` 决定更新检查时如何使用 HTTP 缓存。`'imports'`（默认）抓取主脚本时绕过 HTTP 缓存，但允许 `importScripts()` 的依赖来自缓存；`'all'` 两者都允许走缓存；`'none'` 两者都绕过。无论此设置为何，浏览器对主脚本都忽略超过 24 小时的 HTTP 新鲜度。`type: 'module'` 选项（Chrome 91、Firefox 114、Safari 15）允许脚本使用 `import` 语句；模块 worker 不能调用 `importScripts()`。

注册要求安全上下文：除 `localhost` 外的纯 HTTP 源上 `navigator.serviceWorker` 为 `undefined`，因此 `'serviceWorker' in navigator` 这个特性检测同时也是 HTTPS 检测。脚本必须以 JavaScript MIME 类型提供；Chromium 会拒绝 `text/html`（404 页面或 SPA 兜底路由的常见结果），报错为 `Failed to register a ServiceWorker for scope ('https://example.com/') with script ('https://example.com/sw.js'): The script has an unsupported MIME type ('text/html').`。

:::observed
在 Chrome（英文界面）中，scope 超出脚本目录时拒绝信息为 `DOMException: Failed to register a ServiceWorker: The path of the provided scope ('/') is not under the max scope allowed ('/app/'). Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope.`，这句话在 Chromium 的 `service_worker_register_job.cc` 中拼接（见来源）。Firefox 对同一情况只报 `SecurityError: The operation is insecure.`，没有更多细节，因此用 Chrome 的信息诊断 scope 配置错误更快。
:::

## 示例

第一个示例以显式 scope 注册并区分上述两种拒绝原因；第二个示例展示两个 worker 共享一个源，以及如何确认是哪一个在控制页面。

### 以显式 scope 注册并报告失败

在 `load` 事件之后注册，可以让脚本抓取不占首屏渲染的关键路径。`catch` 分支通过错误名与信息区分 MIME 类型失败（部署问题）与 scope 失败（配置问题）。

```js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const registration = await navigator.serviceWorker.register('/sw.js', {
        scope: '/',
        updateViaCache: 'none',
      });
      console.log('scope:', registration.scope); // "https://example.com/"
    } catch (err) {
      if (err.name === 'SecurityError') {
        console.error('scope not allowed; serve Service-Worker-Allowed or move sw.js', err.message);
      } else {
        console.error('registration failed', err.message); // MIME 类型、网络或解析错误
      }
    }
  });
} else {
  // 非安全源或不支持的浏览器：应用保持仅在线模式。
}
```

`updateViaCache: 'none'` 的代价是每次进入 scope 的导航都要为脚本及其导入发一次不走缓存的请求；收益是脚本上的 `Cache-Control: max-age=31536000` 头不再能把更新拖延最多一天。

### 同一源上的两条注册

同一源下的营销站与应用可以各跑一个 worker，使一方的缓存变更不会让另一方失效。最长 scope 规则为每个请求选路；页面中的 `navigator.serviceWorker.controller.scriptURL` 可以确认结果。

```js
await navigator.serviceWorker.register('/sw-site.js', { scope: '/' });
await navigator.serviceWorker.register('/app/sw-app.js', { scope: '/app/' });

// 在 /app/dashboard 页面刷新后：
console.log(navigator.serviceWorker.controller.scriptURL); // ".../app/sw-app.js"

// 枚举该源上的全部注册
const all = await navigator.serviceWorker.getRegistrations();
console.log(all.map((r) => r.scope));
```

两个 worker 的代价是要同时推理两套安装与更新周期，而且 Cache Storage 命名空间默认并不隔离（两者看到同一个 `caches`，缓存名不能冲突）。改为在 `/sw.js` 一个 worker 内用 `fetch` 处理器做路由可以避免这一点，代价是脚本更大。

## 另请参阅

- [Service Workers specification: register(scriptURL, options)](https://w3c.github.io/ServiceWorker/#navigator-service-worker-register)（w3c.github.io）
- [ServiceWorkerRegistration: updateViaCache property](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/updateViaCache)（developer.mozilla.org）
- [Service worker 生命周期](/zh/reference/service-worker/lifecycle/)
- [skipWaiting() 与更新流程](/zh/reference/service-worker/update-skipwaiting/)
- [scope manifest 成员](/zh/reference/manifest/scope/)
- [Service worker 调试](/zh/reference/service-worker/debugging/)