service worker 的语义版本回滚依赖缓存命名隔离、精准请求匹配与运行时版本决策协同实现;需按版本命名缓存(如 static-v2.3.1)、动态解析版本标识匹配资源、activate 阶段保留最近 2–3 个旧版缓存以支持快速回滚。

Service Worker 本身不提供快照式版本回滚能力,所谓“语义版本回滚”,本质是通过缓存命名隔离 + 精准请求匹配 + 运行时版本决策三者协同实现的可逆缓存控制。关键不在 fetch 拦截本身,而在拦截前如何组织缓存、拦截中如何识别版本、拦截后如何安全切换。
缓存必须按语义版本命名,且不可复用旧名
Cache API 不支持原子替换,同一缓存名(如 static)下多次 caches.open().put() 会覆盖条目,但旧页面仍可能读取该缓存——导致资源错配。正确做法是让缓存名携带明确语义标识:
- 推荐格式:
static-v2.3.1、static-20260414,避免纯哈希(如static-abc123),否则构建未变也触发重缓存 - 每次发布新静态资源包,必须更新版本号,并在
install阶段打开新缓存名,只写不删 - HTML 中引用的资源 URL(如
/js/app.js?v=2.3.1)需与预缓存列表中的路径完全一致,包括查询参数和大小写
fetch 拦截中需动态解析当前应使用的版本
不能硬编码缓存名,因为回滚意味着同一用户可能在不同时间命中不同版本。应在 fetch 事件中实时判断:
- 从
document.cookie、localStorage或服务端注入的全局变量中读取当前激活的版本标识(注意:install 阶段无法读取这些) - 构造带版本的缓存键:
const cacheKey = new URL(request.url).origin + new URL(request.url).pathname + '?v=' + version - 对静态资源(
.js、.css、.woff2等)严格匹配该键,而非裸 URL;HTML 页面则单独用request.destination === 'document'判断
activate 阶段清理非目标版本,但保留回滚窗口
回滚不是删除历史,而是确保旧版本缓存仍可用、新版本不干扰。activate 阶段要做的是“减法”而非“清零”:
- 调用
caches.keys()获取所有缓存名,筛选出非当前活跃版本的缓存(如当前是v2.3.1,则清理v2.2.0、v2.1.5,但保留v2.3.0以备紧急回退) - 使用
caches.delete(name)清理,且必须包裹在event.waitUntil()中,防止中断 - 不建议自动删除所有旧版——保留最近 2–3 个语义版本,是实现快速人工回滚的基础保障
配合服务端完成灰度与回滚的闭环
仅靠 Service Worker 无法触发回滚动作。真正生效依赖三方协同:
-
构建层:每个版本生成唯一
sw.js?v=2.3.1,并确保 HTML 中注册的是带版本号的路径,避免浏览器强缓存旧 SW -
CDN/网关层:根据用户标识(如 cookie 中的
app-version=2.3.0)返回对应版本的 HTML 入口,其中包含该版本的 SW 注册代码和资源路径 - 前端运行时:提供轻量级回滚开关(如按钮或快捷键),修改本地版本标识后刷新页面,SW 自动加载对应缓存










