离线缓存版本管理必须由service worker显式控制,且版本号需同步体现在cache name、资源url和sw脚本三处;缺一不可,否则导致缓存混用或更新失效。

离线缓存的版本管理不是靠 HTML 文件里改个注释或加个 <meta> 就能生效的;它必须由 Service Worker 显式控制,且版本号要贯穿 cache name、资源 URL、SW 脚本本身三个层面,缺一不可。
cacheName 必须带语义化版本号,不能用固定名
用 'pages' 或 'assets' 这类无版本名,会导致新旧逻辑混用——比如 v2 的 SW 试图读 v1 的缓存,caches.open('pages') 打开的可能是上个版本残留的数据,造成结构不匹配或数据错乱。
- 正确做法:每个缓存名都绑定发布版本,例如
'pages-v2.3.0'、'assets-v2.3.0',甚至按用途再细分:'fonts-v2.3.0' - 构建时建议将版本号注入 SW 脚本(如通过 Webpack DefinePlugin 或 Vite define),避免硬编码导致发布遗漏
- 不要复用已有 cache name:即使内容完全一样,也应创建新名 + 清理旧名,否则 activate 阶段无法安全删除
差量更新依赖资源 URL 的稳定性或哈希变更
Service Worker 不会自动感知文件内容是否变化;它只认 URL。如果 main.js 更新了但 URL 没变,cache.addAll(['main.js']) 在 install 阶段仍会命中 HTTP 缓存并写入旧响应,导致“缓存没更新”的假象。
- 静态资源必须使用内容哈希命名,例如
main.a1b2c3.js,确保 URL 变更即内容变更 - 若无法改名(如 CMS 输出的 HTML),需在构建时生成资源清单(manifest.json),并在 SW 中用
cache.put(new Request(url), response)手动比对哈希或版本字段 - Workbox 用户注意:
workbox.precaching.precacheAndRoute()是否生效,取决于传入清单里每个条目的revision是否改变,而不是文件名
SW 脚本自身更新失败,整个版本策略就卡死
很多人以为注册了新 sw.js 就等于更新完成,其实 SW 有严格状态机:install → waiting → activate。旧 SW 正在控制页面时,新 SW 会停在 waiting 状态,既不清理旧缓存,也不接管请求。
- 必须在新 SW 开头加
self.skipWaiting(),让其跳过 waiting 直接进入 activate - 在
activate事件中清理旧缓存前,务必调用self.clients.claim(),否则部分页面仍由旧 SW 控制,caches.delete()可能误删正在使用的缓存 - 验证是否激活成功:监听
navigator.serviceWorker.addEventListener('controllerchange', ...),而非只看register()返回的 Promise
HTML 文件本身不能触发缓存清理,但它是版本对齐的关键锚点
index.html 是唯一每次访问都强制加载(通常不缓存)的入口,它的内容决定了当前应加载哪套缓存。如果它引用了 v2 的 SW,但内部仍嵌入 v1 的资源路径(如内联 CSS 或 base64 图片),就会出现「新壳配旧料」。
- 所有离线包内嵌资源(如
<script></script>内联 JS、data:图片)必须与当前缓存版本一致,否则无法被 SW 的fetch事件捕获和响应 - 避免在 HTML 中用
<meta http-equiv="Cache-Control">幻想控制 SW 缓存——它只影响浏览器 HTTP 缓存层,对caches.open()完全无效 - 上线前建议用 DevTools 的 Application → Cache Storage 手动检查各 cache name 下的资源 URL 和响应时间戳,确认与构建产物一致
最常被忽略的一点:版本号必须同步更新三处——SW 脚本里的 cacheName 字符串、构建产物的文件名/哈希、以及 HTML 中所有显式引用的资源路径。漏掉任何一处,差量更新就变成“伪更新”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











