service-worker

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Service Worker

Service Worker

Table of Contents

目录

Constraints

限制条件

  • HTTPS required (localhost exempt for dev)
  • No DOM access — runs on separate thread
  • Fully async — no synchronous XHR, no localStorage
  • No dynamic
    import()
    — only static
    import
    statements
  • Scope defaults to the directory containing the SW file
  • self
    refers to
    ServiceWorkerGlobalScope
  • 要求HTTPS(开发环境下localhost除外)
  • 无法访问DOM——运行在独立线程中
  • 完全异步——不支持同步XHR,不支持localStorage
  • 不支持动态
    import()
    ——仅支持静态
    import
    语句
  • 作用域默认为包含SW文件的目录
  • self
    指向
    ServiceWorkerGlobalScope

Lifecycle

生命周期

register() → Download → Install → [Wait] → Activate → Fetch control
  1. Register from main thread via
    navigator.serviceWorker.register()
  2. Install event fires once — use to pre-cache static assets
  3. Wait — new SW waits until all tabs using old SW are closed (skip with
    self.skipWaiting()
    )
  4. Activate event fires — use to clean up old caches
  5. Fetch events start flowing — SW controls page network requests
A document must reload to be controlled (or call
clients.claim()
during activate).
register() → Download → Install → [Wait] → Activate → Fetch control
  1. 注册:通过
    navigator.serviceWorker.register()
    从主线程注册
  2. Install事件仅触发一次——用于预缓存静态资源
  3. 等待:新的SW会等待所有使用旧SW的标签页关闭(可通过
    self.skipWaiting()
    跳过)
  4. Activate事件触发——用于清理旧缓存
  5. Fetch事件开始触发——SW控制页面的网络请求
文档必须重新加载才能被SW控制(或在Activate阶段调用
clients.claim()
)。

Registration

注册

js
// main.js — register from the page
if ("serviceWorker" in navigator) {
  const reg = await navigator.serviceWorker.register("/sw.js", { scope: "/" });
  // reg.installing | reg.waiting | reg.active
}
Scope rules:
  • SW at
    /sw.js
    can control
    /
    and all subpaths
  • SW at
    /app/sw.js
    can only control
    /app/
    by default
  • Broaden scope with
    Service-Worker-Allowed
    response header
js
// main.js — register from the page
if ("serviceWorker" in navigator) {
  const reg = await navigator.serviceWorker.register("/sw.js", { scope: "/" });
  // reg.installing | reg.waiting | reg.active
}
作用域规则:
  • 位于
    /sw.js
    的SW可以控制
    /
    及所有子路径
  • 位于
    /app/sw.js
    的SW默认仅能控制
    /app/
  • 可通过
    Service-Worker-Allowed
    响应头扩大作用域

Install Event — Pre-cache Assets

Install 事件——预缓存资源

js
// sw.js
const CACHE_NAME = "v1";
const PRECACHE_URLS = ["/", "/index.html", "/style.css", "/app.js"];

self.addEventListener("install", (event) => {
  event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS)));
});
waitUntil(promise)
— keeps install phase alive until the promise settles. If rejected, installation fails and the SW won't activate.
js
// sw.js
const CACHE_NAME = "v1";
const PRECACHE_URLS = ["/", "/index.html", "/style.css", "/app.js"];

self.addEventListener("install", (event) => {
  event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS)));
});
waitUntil(promise)
——保持Install阶段活跃,直到promise完成。如果promise被拒绝,安装失败,SW不会激活。

Activate Event — Clean Up Old Caches

Activate 事件——清理旧缓存

js
self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches
      .keys()
      .then((keys) =>
        Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))),
      ),
  );
});
js
self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches
      .keys()
      .then((keys) =>
        Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))),
      ),
  );
});

Fetch Event — Intercept Requests

Fetch 事件——拦截请求

js
self.addEventListener("fetch", (event) => {
  event.respondWith(caches.match(event.request).then((cached) => cached || fetch(event.request)));
});
respondWith(promise)
— must be called synchronously (within the event handler, not in a microtask). The promise resolves to a
Response
.
For caching strategy patterns (cache-first, network-first, stale-while-revalidate), see references/caching-strategies.md.
js
self.addEventListener("fetch", (event) => {
  event.respondWith(caches.match(event.request).then((cached) => cached || fetch(event.request)));
});
respondWith(promise)
——必须同步调用(在事件处理函数内,而非微任务中)。promise会解析为一个
Response
对象。
关于缓存策略模式(缓存优先、网络优先、回退到缓存并更新),请查看references/caching-strategies.md

Navigation Preload

导航预加载

Avoid the startup delay when a SW boots to handle a navigation:
js
self.addEventListener("activate", (event) => {
  event.waitUntil(self.registration?.navigationPreload.enable());
});

self.addEventListener("fetch", (event) => {
  event.respondWith(
    (async () => {
      const cached = await caches.match(event.request);
      if (cached) return cached;

      const preloaded = await event.preloadResponse;
      if (preloaded) return preloaded;

      return fetch(event.request);
    })(),
  );
});
避免SW启动处理导航时的延迟:
js
self.addEventListener("activate", (event) => {
  event.waitUntil(self.registration?.navigationPreload.enable());
});

self.addEventListener("fetch", (event) => {
  event.respondWith(
    (async () => {
      const cached = await caches.match(event.request);
      if (cached) return cached;

      const preloaded = await event.preloadResponse;
      if (preloaded) return preloaded;

      return fetch(event.request);
    })(),
  );
});

Updating a Service Worker

更新 Service Worker

  • Browser byte-compares the SW file on each navigation (or every 24h)
  • New version installs in background while old version still serves
  • Increment the cache name (e.g.,
    v1
    v2
    ) in the new version
  • Delete old caches in the
    activate
    handler
  • Call
    self.skipWaiting()
    in
    install
    to activate immediately
  • Call
    self.clients.claim()
    in
    activate
    to take control of open pages
  • 浏览器会在每次导航时对比SW文件的字节(或每24小时一次)
  • 新版本会在后台安装,同时旧版本仍在提供服务
  • 在新版本中递增缓存名称(例如
    v1
    v2
  • activate
    处理函数中删除旧缓存
  • install
    阶段调用
    self.skipWaiting()
    立即激活新版本
  • activate
    阶段调用
    self.clients.claim()
    获取对已打开页面的控制权

Communicating with Pages

与页面通信

js
// Page → SW
navigator.serviceWorker.controller.postMessage({ type: "SKIP_WAITING" });

// SW → Page (via Clients API)
const clients = await self.clients.matchAll({ type: "window" });
clients.forEach((client) => client.postMessage({ type: "UPDATED" }));

// SW listens
self.addEventListener("message", (event) => {
  if (event.data?.type === "SKIP_WAITING") self.skipWaiting();
});
js
// 页面 → SW
navigator.serviceWorker.controller.postMessage({ type: "SKIP_WAITING" });

// SW → 页面(通过Clients API)
const clients = await self.clients.matchAll({ type: "window" });
clients.forEach((client) => client.postMessage({ type: "UPDATED" }));

// SW 监听消息
self.addEventListener("message", (event) => {
  if (event.data?.type === "SKIP_WAITING") self.skipWaiting();
});

Common Pitfalls

常见陷阱

  1. Response cloning
    response.clone()
    before both caching and returning, since body streams can only be read once
  2. Opaque responses — cross-origin fetches without CORS return opaque responses (status 0).
    cache.add()
    will refuse them. Use
    cache.put()
    but you can't inspect the response
  3. waitUntil timing — call
    event.waitUntil()
    synchronously within the event handler, not inside an async callback
  4. Scope ceiling — a SW cannot control URLs above its own directory unless
    Service-Worker-Allowed
    header is set
  5. No state persistence — the SW may terminate at any time when idle. Don't store state in global variables — use Cache API or IndexedDB
  1. 响应克隆——在缓存和返回响应前调用
    response.clone()
    ,因为响应体流只能被读取一次
  2. 不透明响应——没有CORS的跨域请求会返回不透明响应(状态码0)。
    cache.add()
    会拒绝这类响应。可使用
    cache.put()
    但无法检查响应内容
  3. waitUntil 时机——在事件处理函数内同步调用
    event.waitUntil()
    ,而非异步回调中
  4. 作用域上限——SW无法控制其所在目录之上的URL,除非设置了
    Service-Worker-Allowed
  5. 无状态持久化——SW在闲置时可能随时终止。不要在全局变量中存储状态——使用Cache API或IndexedDB

Push Notifications & Background Sync

推送通知与后台同步

For push subscription, handling push events, and background sync implementation, see references/push-and-sync.md.
关于推送订阅、处理推送事件和后台同步的实现,请查看references/push-and-sync.md

API Quick Reference

API 速查

For detailed interfaces (
Cache
,
CacheStorage
,
FetchEvent
,
Clients
,
ServiceWorkerRegistration
,
ServiceWorkerGlobalScope
), see references/api-reference.md.
关于详细的接口(
Cache
CacheStorage
FetchEvent
Clients
ServiceWorkerRegistration
ServiceWorkerGlobalScope
),请查看references/api-reference.md

Next.js Integration

Next.js 集成

In Next.js, place the service worker file in
public/sw.js
.
public/sw.js
is intentionally plain JS (not processed by Next.js build pipeline). Register it from a client component:
tsx
"use client";
import { useEffect } from "react";

export function ServiceWorkerRegistrar() {
  useEffect(() => {
    if ("serviceWorker" in navigator) {
      navigator.serviceWorker.register("/sw.js");
    }
  }, []);
  return null;
}
Add to root layout. Next.js serves
public/
files at the root, so
/sw.js
scope covers
/
.
在Next.js中,将service worker文件放在
public/sw.js
目录下。
public/sw.js
是纯JS文件(不会被Next.js构建流程处理)。从客户端组件中注册它:
tsx
"use client";
import { useEffect } from "react";

export function ServiceWorkerRegistrar() {
  useEffect(() => {
    if ("serviceWorker" in navigator) {
      navigator.serviceWorker.register("/sw.js");
    }
  }, []);
  return null;
}
将该组件添加到根布局中。Next.js会将
public/
目录下的文件部署到根路径,因此
/sw.js
的作用域覆盖
/

DevTools

开发者工具

  • Chrome:
    chrome://inspect/#service-workers
    or Application > Service Workers
  • Firefox:
    about:debugging#/runtime/this-firefox
    or Application > Service Workers
  • Edge:
    edge://inspect/#service-workers
    or Application > Service Workers
Unregister, update, and inspect caches from the Application panel. Use "Update on reload" checkbox during development.
  • Chrome
    chrome://inspect/#service-workers
    或 开发者工具 > Application > Service Workers
  • Firefox
    about:debugging#/runtime/this-firefox
    或 开发者工具 > Application > Service Workers
  • Edge
    edge://inspect/#service-workers
    或 开发者工具 > Application > Service Workers
可从Application面板中注销、更新SW并检查缓存。开发期间可勾选"Update on reload"复选框。