能否自定义Service Worker的onupdatefound事件实现两种更新模式?
CRA + Service Worker 实现常规/强制双更新模式
一、常规更新(后台下载,关闭应用后自动替换)
CRA默认用Workbox做Service Worker管理,要实现后台静默下载新版本、等用户关掉所有页面再替换的逻辑,直接改Service Worker的激活策略就行:
- 编辑
service-worker.js的install事件,让新SW下载完资源后不立即激活:
self.addEventListener('install', (event) => { // 后台预缓存所有静态资源(CRA会自动生成资源列表,不用手动写) event.waitUntil( caches.open('app-cache-v1').then((cache) => cache.addAll(self.__WB_MANIFEST)) ); // 禁止跳过等待,让旧SW继续控制当前页面 self.skipWaiting(false); });
- 调整
activate事件,不主动接管现有页面,等用户下次打开再生效:
self.addEventListener('activate', (event) => { // 清理过期的旧缓存(可选,避免缓存冗余) event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.filter(name => name !== 'app-cache-v1').map(name => caches.delete(name)) ); }) ); // 这里不要调用self.clients.claim(),让新SW在用户下次打开页面时才接管 });
这样新SW会在后台悄悄下完所有资源,但不会立刻替换旧的,直到用户把所有打开的应用页面都关掉,下次再打开就自动用新版本了。
二、强制更新(基于lastSupportedVersion限制旧版本)
遇到严重bug要强制所有人升级时,用lastSupportedVersion常量来卡版本,思路就是:检查用户当前用的版本是不是低于最低支持版本,是的话直接强制激活新SW并刷新所有页面。
- 先在
service-worker.js顶部定义最低支持版本:
// 要强制更新时,直接把这个值改成当前最新版本就行 const lastSupportedVersion = '1.2.0';
- 在
install事件里加版本校验(得先把当前应用版本注入到SW里,CRA可以用环境变量或改webpack配置实现):
// 假设构建时已经把package.json里的版本注入成self.appVersion了 self.addEventListener('install', (event) => { // 如果当前版本低于最低支持,直接跳过等待,准备强制激活 if (self.appVersion < lastSupportedVersion) { self.skipWaiting(); } else { // 走常规更新的缓存逻辑 event.waitUntil( caches.open('app-cache-v1').then((cache) => cache.addAll(self.__WB_MANIFEST)) ); self.skipWaiting(false); } });
- 在
activate事件里,版本不符合就强制刷新所有页面:
self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then(cacheNames => { // 先清旧缓存 return Promise.all( cacheNames.filter(name => name !== 'app-cache-v1').map(name => caches.delete(name)) ).then(() => { // 版本不够的话,强制刷新所有打开的页面 if (self.appVersion < lastSupportedVersion) { return self.clients.matchAll({ type: 'window' }).then(clients => { clients.forEach(client => { client.navigate(client.url); // 强制跳转到当前页面,触发新版本加载 }); }); } }); }) ); // 版本不够时,强制接管所有页面 if (self.appVersion < lastSupportedVersion) { self.clients.claim(); } });
- 版本注入的小技巧:CRA里如果不想eject,可以用
craco改webpack配置,加个DefinePlugin把版本注入进去:
// craco.config.js const webpack = require('webpack'); module.exports = { webpack: { plugins: [ new webpack.DefinePlugin({ 'self.appVersion': JSON.stringify(require('./package.json').version) }) ] } };
注意点
- 常规更新完全不打扰用户,新版本在后台准备好,下次打开就生效;
- 强制更新只要改
lastSupportedVersion的值发版,所有用旧版本的用户打开页面就会被强制刷新到最新版; - 版本号要用语义化格式(比如x.y.z),确保字符串对比能正确判断版本高低。
内容的提问来源于stack exchange,提问作者ds1371dani
相关产品推荐
相关产品推荐

