更新问题
reference
生命周期
Installing
行为:
开发者通常在 install事件中预缓存关键静态资源(如 HTML、CSS、JS)。
使用 event.waitUntil()确保安装完成前不进入下一阶段。若 Promise 被拒绝,安装失败,Service Worker 直接变为 redundant状态
跳过等待:通过 registration.installing属性访问正在安装的 Service Worker
Installed/Waiting
安装成功后进入 installed(或 waiting)状态。
行为:
新 Service Worker 等待旧版本控制的页面全部关闭后才会激活
可通过 registration.waiting访问等待中的 Service Worker
跳过等待:调用 self.skipWaiting()可强制立即激活,但需谨慎处理缓存冲突
Activating
激活条件:
旧 Service Worker 控制的页面全部关闭。
调用 skipWaiting()或超时机制触发。
核心事件:activate事件,通常用于清理旧缓存(如删除过期的 CacheStorage内容)
状态检查:通过 registration.active访问已激活的 Service Worker
运行阶段(Activated)
功能:
控制页面并处理 fetch(网络请求拦截)、push(推送通知)等事件。
默认仅控制激活后新打开的页面。调用 clients.claim()可立即控制所有同源页面
终止:若长时间无事件处理,浏览器可能终止 Service Worker 以节省资源。
当新旧 sw 存在的时候,如果采用了 self.skipWaiting(),那么页面可能在遇到某些缓存不匹配问题的时候,会出现意外错误; 正常的更新流程:所有的旧受控页面关闭,新的 sw 将会进行接管
需要提示更新的时候:
方法1(比较果断,不考虑用户行为):
页面 js 中监听
controllerchange, 当版本替换的时候,执行location.reload()sw 进行
skipWaiting会触发controllerchange
方法2(询问):
function emitUpdate() {
var event = document.createEvent('Event')
event.initEvent('sw.update', true, true)
window.dispatchEvent(event)
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/service-worker.js')
.then(function (reg) {
// 表示已安装但处于等待状态的 Service Worker(即新版本已下载但未激活)。
if (reg.waiting) {
emitUpdate()
return
}
// 当检测到新版本的 Service Worker 开始安装时触发
reg.onupdatefound = function () {
var installingWorker = reg.installing
installingWorker.onstatechange = function () {
switch (installingWorker.state) {
// 新 Worker 安装完成。
case 'installed':
// 检查当前是否有活跃的 Worker。若存在(即非首次安装),则触发更新事件
if (navigator.serviceWorker.controller) {
emitUpdate()
}
break
}
}
}
})
.catch(function (e) {
console.error('Error during service worker registration:', e)
})
}demo
某些资源直接使用 event.request 进行缓存,之后 match 不会被命中(script[type="module"]), 需要改成使用 event.request.url
// https://juejin.cn/post/6950988377684443167?searchId=202506142335433B82D94BC578B6A454F2#heading-10
const SW_VERSION = '0.0.1'
// 需要缓存的资源后缀
const CACHE_SUFFIX = ['js', 'css']
// 一开始就需要加载的资源
const INITIAL_SOURCES = [
'/', // 取消缓存,页面直接离线不可用
// '/sw.js',
]
// e.waitUntil 接收一个 promise,promise 完成,那么这个事件就完成,install=》activate
// 保证 sw 能够正常运行(加载完必需的资源),避免浏览器关闭等事件导致的加载失败
self.addEventListener('install', (e) => {
// e.waitUntil(self.skipWaiting()) // 立即激活
e.waitUntil(
caches.open(SW_VERSION).then(function (cache) {
return cache.addAll(INITIAL_SOURCES)
})
)
})
// 激活的时候,清除旧 version cache
self.addEventListener('activate', function (event) {
const keys = caches.keys()
event.waitUntil(
keys.then(function (keyList) {
return Promise.all(
keyList.filter((it) => it !== SW_VERSION).map((key) => caches.delete(key))
)
})
)
})
// activate 完成,该事件才会生效,因此 activate 需要配合 waitUntil 先清除旧资源,避免提前 match 导致资源不对
self.addEventListener('fetch', function (event) {
const { method, url } = event.request
const { pathname } = new URL(url)
const format = pathname.split('.').at(-1)
event.respondWith(
caches.match(url).then(function (response) {
if (response !== undefined) {
return response
}
return fetch(event.request).then(function (response) {
if (
!response ||
url.includes('chrome-extension') ||
// ['/', ''].includes(pathname) ||
method === 'POST' ||
!(url || '').startsWith('http') ||
response.status !== 200 ||
!CACHE_SUFFIX.includes(format)
) {
return response
}
const clonedRes = response.clone()
caches.open(SW_VERSION).then(function (cache) {
cache.put(url, clonedRes)
})
return response
})
})
)
})
// 监听消息
self.addEventListener('message', (event) => {
if (event.data === 'skipWaiting') {
self.skipWaiting()
}
})// 更新提示
async function updateAlert(): Promise<void> {
return window.confirm('检测到新版本,立即更新?') ? Promise.resolve(void 0) : Promise.reject()
}
// 自动检测版本
function createIntervalVersionCheck(reg: ServiceWorkerRegistration, debounce = 60e3) {
setInterval(() => {
reg.update()
}, debounce)
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js')
.then(function (reg) {
// registration worked
reg.onupdatefound = function () {
var installingWorker = reg.installing
if (!installingWorker) return
installingWorker.onstatechange = function () {
switch (installingWorker!.state) {
// 新 Worker 安装完成。
case 'installed':
// 检查当前是否有活跃的 Worker。若存在(即非首次安装),则触发更新事件
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.getRegistration().then((reg) => {
if (!reg || !reg.waiting) return
await updateAlert()
reg.waiting.postMessage('skipWaiting')
})
}
break
}
}
}
createIntervalVersionCheck(reg)
})
.catch(function (error) {
// registration failed
console.log('Registration failed with ' + error)
})
// 监听版本切换,进行刷新
navigator.serviceWorker.addEventListener('controllerchange', () => {
window.location.reload()
})
}