如何用 Service Worker 实现离线即走的静态资源静默更新策略

作者:袖梨 2026-07-19
“离线即走”要求Service Worker在install阶段用event.waitUntil()预缓存根路径下的核心静态资源(HTML/CSS/JS/图标),版本号随构建变更,配合skipWaiting()和clients.claim()立即接管页面;fetch阶段对静态资源严格Cache First、零网络兜底,确保无网时秒开UI且联网后静默更新。

“离线即走”不是指关掉网络就闪退,而是用户在无网或弱网时打开页面,仍能立刻渲染出完整 UI,且后续联网后自动静默更新缓存——不打断当前操作、不弹提示、不重载页面。这要求 Service Worker 在 install/activate 阶段完成资源预取与版本切换,同时 fetch 阶段对静态资源严格走 Cache First,并用 skipWaiting()clients.claim() 消除旧 worker 占据导致的延迟接管。

install 阶段必须调用 event.waitUntil() + caches.open().addAll()

这是静默更新的前提:所有核心静态资源(HTML、CSS、JS、关键图标)必须在 install 时一次性写入新缓存,不能留到 fetch 时懒加载。否则首次离线访问会失败。

  • caches.open('static-v2') 中的版本号(如 v2)必须随每次构建变更,否则浏览器认为无更新,跳过 install
  • addAll() 的路径列表需绝对准确;相对路径(如 ./main.js)在 SW 环境中会解析失败,一律用根路径(/main.js
  • 务必在 addAll() 后接 self.skipWaiting(),否则新 worker 会卡在 waiting 状态,直到所有页面关闭

activate 阶段要清理旧缓存 + 调用 clients.claim()

旧缓存不清理会占用空间,更严重的是:若旧缓存名(如 static-v1)还存在,而 fetch 逻辑又没做版本判断,就可能误读过期资源。

  • caches.keys() 获取全部缓存名,用 .filter(name => !name.startsWith('static-v')) 或白名单比对,只保留当前版本
  • caches.delete() 是异步操作,必须包进 event.waitUntil(),否则清理可能被中断
  • self.clients.claim() 让新 worker 立即接管当前页(包括已打开但未注册 SW 的 tab),这是“离线即走”不闪屏的关键

fetch 事件里对静态资源强制 Cache First,且禁止 fallback 到 network

所谓“静默”,就是用户完全感知不到缓存行为。一旦在 fetch 中写了 || fetch(request) 这类兜底逻辑,断网时就会触发 fetch 失败,进而抛错或降级——这不是静默,是暴露问题。

  • 只对明确匹配静态后缀的请求启用该策略:request.url.match(/.(js|css|html|png|svg|woff2)$/i)
  • 响应必须来自 caches.match(request),且不做任何 fetch() 尝试;如果 match 返回 undefined,直接 return new Response('', { status: 404 }) 或返回预置 offline 页面
  • 避免在 match 前做 request.clone() —— clone 会消耗 stream,导致后续 fetch 无法读 body

构建流程必须生成内容哈希并注入 SW 脚本

光靠版本号(static-v2)不够。如果文件内容没变但版本号变了,会导致无效缓存;如果内容变了但版本号没变,又会命中旧缓存。真正可靠的依据是文件内容哈希。

  • Webpack/Vite 构建时启用 contenthash,生成类似 main.a1b2c3d4.js 的文件名
  • 把哈希化后的资源列表(非通配符)写入 SW 脚本,例如通过 injectManifest 插件或构建后替换模板中的 ASSETS_TO_CACHE
  • 不要用 cache.addAll(['/']) 这种方式——它依赖服务器配置,且无法控制缓存粒度

最容易被忽略的点:Service Worker 的作用域(scope)默认是 sw.js 所在路径。如果 sw.js 放在 /assets/sw.js,那它只能控制 /assets/ 下的请求。要把 sw.js 放在根目录,并在注册时显式指定 { scope: '/' },否则 HTML 文件本身都缓存不了,“离线即走”就成空谈。

相关文章

精彩推荐