跳转到内容

Service worker 注册与 scope

一句话: navigator.serviceWorker.register(scriptURL, { scope }) 注册一个 service worker,它会拦截路径以 scope 开头的所有 URL 的 fetch 事件。scope 默认为 worker 脚本所在目录,只能收窄、不能扩展到该范围之外——除非服务器返回 Service-Worker-Allowed 响应头。

调用 register() 是幂等的——若相同脚本已在该 scope 注册,浏览器会忽略此次调用。无论是否触发了新的安装,返回的 Promise 都会 resolve 为一个 ServiceWorkerRegistration 对象。

if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js', { scope: '/' })
.then((registration) => {
console.log('注册成功。Scope:', registration.scope);
})
.catch((err) => {
console.error('注册失败:', err);
});
}

注册应在页面主要内容加载完成后进行,而非放在 <head> 顶部,以免与关键资源抢占带宽。

scope 是一个 URL 路径前缀。注册了 scope /app/ 的 service worker 会拦截:

  • /app/
  • /app/dashboard
  • /app/settings/profile

但不会拦截:

  • /(根路径)
  • /blog/
  • /app-launcher/(不同前缀)

scope 不限制 worker 通过 fetch() 发起请求的目标 URL,只限制哪些页面导航和子资源请求会被 提供给该 worker 处理。

若未指定 scope 选项,默认为 worker 脚本所在目录:

  • Worker 位于 /sw.js → 默认 scope /
  • Worker 位于 /app/sw.js → 默认 scope /app/

将 worker 放在根路径(/sw.js)可获得最大默认 scope。

通过 Service-Worker-Allowed 扩展 Scope

Section titled “通过 Service-Worker-Allowed 扩展 Scope”

/app/sw.js 通常无法声明 / 的 scope——其范围受自身路径约束。若需要更大的 scope,服务器必须在浏览器拉取 worker 脚本时附上 Service-Worker-Allowed 响应头:

Service-Worker-Allowed: /

若没有此响应头,以 { scope: '/' } 注册 /app/sw.js 会抛出 DOMException

同一来源可以有多个注册,允许为不同子应用部署不同的 worker:

// Worker A 覆盖营销页面
navigator.serviceWorker.register('/sw-marketing.js', { scope: '/marketing/' });
// Worker B 覆盖应用外壳
navigator.serviceWorker.register('/sw-app.js', { scope: '/app/' });

Scope 之间可以重叠——浏览器在判断由哪个 worker 处理请求时,会匹配最具体(最长)的 scope。保持 scope 不重叠是运维层面的最佳实践,而非注册约束。

默认情况下,浏览器在获取 worker 脚本时会遵循其 HTTP 缓存,若脚本被积极缓存可能延迟更新。设置 updateViaCache: 'none' 可让浏览器始终绕过 worker 脚本本身的 HTTP 缓存:

navigator.serviceWorker.register('/sw.js', {
scope: '/',
updateViaCache: 'none',
});

W3C Service Workers 规范将 updateViaCache 定义为控制 HTTP 缓存是否被用于 worker 脚本('all')、其导入脚本('imports',默认值)或二者均不使用('none')。

  • DOMContentLoadedload 事件后注册,避免与资源加载竞争。
  • 将 worker 脚本放在所需 scope 路径层级尽可能高的位置(/sw.js 对应 //app/sw.js 对应 /app/)。
  • 注册时使用 updateViaCache: 'none',确保浏览器始终检查新版本。
  • 在开发环境中打印 registration.scope,确认 worker 覆盖了预期路径。
  • 多应用站点应定义不重叠的 scope,并为每个 scope 部署独立的 worker。