Service Worker · 概念
Workbox
发布于
Workbox 是 Chrome 团队维护的一组 JavaScript 库,实现了 service worker 中原本要手写重复的路由、缓存、预缓存与更新消息代码;当前主版本为 7(2023-04 发布)。它只建立在标准的 Service Worker、Cache 与 Fetch API 之上,因此凡是有 service worker 的浏览器都能运行(Chrome 40、Firefox 44、Safari 11.1、Edge 17;BCD api.ServiceWorker),而可选模块在底层 API 缺失时会降级,例如 Firefox 与 Safari 中的 workbox-background-sync。
每个 workbox-* 包对应 service worker 的一项任务;worker 脚本只导入用到的部分,既可以经打包器从 npm 导入,也可以从 CDN 以 ES 模块方式导入。
| 包 | 取代手写 worker 中的哪部分 |
|---|---|
workbox-routing |
fetch 处理器里的 if/else 链:registerRoute(matcher, handler) 与 NavigationRoute |
workbox-strategies |
五种策略函数体:CacheFirst、NetworkFirst、StaleWhileRevalidate、NetworkOnly、CacheOnly,带 networkTimeoutSeconds 与插件钩子 |
workbox-precaching |
install/activate 中按内容修订号填充与清理缓存 |
workbox-expiration |
缓存条目数与时长上限(maxEntries、maxAgeSeconds) |
workbox-cacheable-response |
cache.put() 之前的 response.ok 检查 |
workbox-background-sync |
在 sync 事件中重放的 IndexedDB 队列;没有 Background Sync 的浏览器中,队列在 worker 下次启动时重放 |
workbox-broadcast-update |
Stale-While-Revalidate 刷新改变了缓存响应时发出的 BroadcastChannel 消息 |
workbox-window |
页面侧的注册、更新检测与 skipWaiting 消息传递 |
预缓存是无法在运行时手写的那部分。构建工具扫描输出目录,生成一个 { url, revision } 数组并替换 worker 中的 self.__WB_MANIFEST 标记;precacheAndRoute() 把每个文件存入名为 workbox-precache-v2-<scope> 的缓存,以 URL 为键、修订号作为 __WB_REVISION__ 查询参数附加其后,并为这些 URL 安装一条 Cache First 路由。更新时只抓取修订号变化的条目;activate 处理器删除不再出现在清单中的条目。文件名本身已含内容哈希的文件得到 revision: null,仅凭 URL 即可标识版本。
两种构建模式都能生成这份清单。GenerateSW(在 workbox-build、workbox-cli、workbox-webpack-plugin 与 vite-plugin-pwa 中)根据配置写出完整的 worker,在 worker 只需要预缓存与运行时缓存规则时已经够用;InjectManifest 接受你自己写的 worker 源文件,替换其中的 __WB_MANIFEST 标记,任何自定义的 fetch、push、sync 或 message 处理器都需要它。取舍在于:GenerateSW 每次构建都重新生成 worker,配置是唯一的事实来源;InjectManifest 让 worker 成为普通代码,代价是导入要自己维护。
页面侧,workbox-window 的 Workbox 类封装了 register(),并抛出 installed、waiting、controlling、activated 事件;wb.messageSkipWaiting() 发送一条 { type: 'SKIP_WAITING' } 消息,GenerateSW 生成的 worker 自带对应的 message 监听器,InjectManifest 的 worker 则要加上第一个示例中那四行监听代码。
第一个示例是一个完整的 InjectManifest worker,含预缓存与一条运行时路由;第二个是页面侧代码,包括浏览器没有 service worker 时的分支。
带预缓存与图片路由的 InjectManifest worker
Section titled “带预缓存与图片路由的 InjectManifest worker”__WB_MANIFEST 标记在构建时被替换;ExpirationPlugin 限制运行时缓存的规模,预缓存则不需要它,因为清单本身已经限定了范围。
// src/sw.js:swSrc 文件,__WB_MANIFEST 由构建工具填入import { precacheAndRoute, cleanupOutdatedCaches } from 'workbox-precaching';import { registerRoute } from 'workbox-routing';import { CacheFirst } from 'workbox-strategies';import { ExpirationPlugin } from 'workbox-expiration';import { CacheableResponsePlugin } from 'workbox-cacheable-response';
cleanupOutdatedCaches(); // 删除旧版预缓存格式留下的缓存precacheAndRoute(self.__WB_MANIFEST); // [{ url: '/index.html', revision: 'a1b2' }, …]
self.addEventListener('message', (event) => { if (event.data?.type === 'SKIP_WAITING') self.skipWaiting(); // 与 workbox-window 配对});
registerRoute( ({ request }) => request.destination === 'image', new CacheFirst({ cacheName: 'images', plugins: [ new CacheableResponsePlugin({ statuses: [0, 200] }), new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 30 * 24 * 60 * 60 }), ], }));statuses 中包含 0 会缓存 opaque 的跨源图片,Chromium 在配额核算中把每条按约 7 MB 计入;图片为同源或带 CORS 时应去掉 0,只存储真正的 200 响应。
用 workbox-window 注册并检测支持
Section titled “用 workbox-window 注册并检测支持”Workbox.register() 兑现为注册对象,或以与 navigator.serviceWorker.register() 相同的错误拒绝。serviceWorker 不存在时页面继续以仅在线方式工作,所以把导入放在特性检测之后,避免白白加载模块。
if ('serviceWorker' in navigator) { const { Workbox } = await import('workbox-window'); const wb = new Workbox('/sw.js');
wb.addEventListener('waiting', () => { showBanner('新版本已就绪。', () => { wb.addEventListener('controlling', () => window.location.reload()); wb.messageSkipWaiting(); }); });
wb.register();} else { // 没有 service worker:不显示更新横幅;应用直接走网络。}在 controlling 事件中刷新,而不是 messageSkipWaiting() 之后立即刷新,是为了等新 worker 完成接管,这样刷新后的页面由新的预缓存提供;过早刷新可能加载到旧 HTML 与新资源的混合。
- Workbox: precaching module(developer.chrome.com)
- Workbox: the ways of Workbox(developer.chrome.com)
- Workbox: workbox-window module(developer.chrome.com)
- Service worker 缓存策略
- skipWaiting() 与更新流程
- 预缓存
- Service worker 离线兜底
规范
| 规范 | 状态 |
|---|---|
| 无。 | |