Service Worker 无法自动实现多版本静态资源的可回滚快照,必须由构建层生成带版本标识的资源路径、运行时 Cache API 显式存取、外部控制面(如 HTML 入口分流)三者协同完成。

Service Worker 本身不提供“本地快照管理”能力,也**无法自动建立多版本静态资源的可回滚快照**。所谓“快照”,必须由构建层生成带唯一标识(如哈希或语义化版本)的资源路径 + 运行时 Cache API 显式存取 + 外部控制面(如 HTML 入口分流)三者共同实现。
为什么直接用 caches.open() 命名缓存不能当快照用
很多人以为给每个版本起个新缓存名(比如 'static-cache-v1.2.3')就等于有了快照——但问题在于:
-
caches.keys()能列出所有缓存名,但 Service Worker 没有内置机制判断哪个是“上一版”、哪个该被保留、哪个可清理 - 旧缓存不会自动失效,也不会被自动读取;
fetch事件里若没显式构造带版本的cacheKey,请求仍会命中最新缓存或完全不命中 - 用户刷新页面后,即使 HTML 已切到旧版入口,已激活的 SW 仍用当前脚本逻辑,不会“倒带”去读旧缓存
- 浏览器对同一 scope 的 SW 脚本有强缓存,
navigator.serviceWorker.register('/sw.js')不会重拉,除非 URL 变(如加?v=1.2.3)或调用unregister()
真正能回滚的关键:运行时缓存键必须带版本上下文
静态资源的“快照感”来自请求 URL 和缓存键的严格绑定。不能只靠缓存名区分,而要在每次 fetch 时动态拼出带版本的 key:
示例逻辑(放在 service-worker.js 的 fetch 事件中):
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// 从 document.cookie 或 localStorage 读取当前生效的版本号(注意:install 阶段不可读!)
const version = getActiveVersion(); // 自定义函数,例如解析 cookie 中的 'sw-version=1.2.2'
const cacheKey = `${url.origin}${url.pathname}?v=${version}`;
event.respondWith(
caches.match(cacheKey).then(res => res || fetch(event.request))
);
});
要点:
- 版本号必须在
fetch阶段读取,不能写死在 install 阶段 - 缓存 key 必须包含
v=xxx查询参数,否则不同版本的同名资源(如/app.js)会互相覆盖 - CDN 和浏览器需配置为不忽略查询参数做缓存(例如 Cloudflare 的 “Cache Everything” 默认忽略 query string,需手动开启)
- 构建产物必须真实带版本信息:要么文件名含哈希(
app.a1b2c3.js),要么 HTML 中注入的资源 URL 含?v=1.2.2
配合构建与部署才能完成闭环回滚
Service Worker 是执行层,不是调度层。要支持“一键回滚到 v1.2.1”,必须有外部系统协同:
- 构建阶段:每次发布生成独立
sw.js?v=1.2.2,并在 HTML 中注入对应版本号(避免 SW 被浏览器缓存) - CDN/反向代理层:根据灰度开关(如 header
x-sw-version或 cookie)返回不同版本的 HTML 入口,其中包含对应sw.js地址和资源 URL - 清理策略:旧版本缓存不能无限堆积,需在 activate 阶段主动删除非当前活跃版本的缓存(通过
caches.keys()筛选 +caches.delete()) - 客户端降级触发:回滚操作本质是让 HTML 切换回旧版,再配合
self.skipWaiting()强制新 SW 激活——但注意这会中断当前页面,需配合 reload 控制
最易被忽略的一点:回滚不是“恢复缓存”,而是“切换请求路由+缓存键+SW 脚本”三位一体的原子操作。少任何一个环节,用户看到的都可能是新 SW 加载旧资源、或旧 SW 尝试读新缓存——结果就是白屏或 JS 报错。

















