Service Worker 是一种运行在浏览器后台的 JavaScript 程序,它在构建渐进式 Web 应用(PWA)中扮演着至关重要的角色。通过操控浏览器端的存储、网络请求、消息推送等功能,Service Worker 为用户提供了离线支持、资源缓存和推送通知等丰富的体验。
本文主要来介绍一种可靠的的 Service Worker 注册方案。
通常,会直接在 index.html 文件中注册一个 Service Worker,例如:
navigator.serviceWorker.register("sw.js").then(sw => {
console.log(`serviceWorker 注册成功:${sw}`);
});
1、存在问题
上面几行代码虽然完成了注册 Service Worker,却有以下几点问题:
(1) 如果缓存了 index.html 文件,当修改了 index.html 文件比如遇到了兼容问题想立即注销 Service Worker,由于存在缓存,导致用户 index.html 文件无法更新导致注销 Service Worker 失败。
(2) 如果 sw.js 存在缓存,当 sw.js 更新后,用户也没法更新到最新的 sw.js 文件。
对于 Service Worker 的注册方案应该考虑以下几点:
(1) 为了防止因为 Service Worker 的逻辑错误导致线上问题,因此应该设计可以立即注销 Service Worker。
(2) sw.js 文件更改后,浏览器端能够立即更新。
(3) 做好浏览器兼容问题,在一些不支持 Service Worker 的浏览器下捕获错误,防止阻塞功能。
2、方案设计
单独将 Service Worker 的注册和注销放在一个 initSw.js 文件中,并在 index.html 文件中采用无缓存的方式引入这个文件。
整体流程图如下图:
上面的流程中,在 index.html 文件中通过无缓存的方式加载 initSw.js 文件,实现如下:
(function () {
if (window.addEventListener) {
window.addEventListener("load", () => {
let script = document.createElement("script");
script.src = `initSw.js?t=${Date.now()}`; // 无缓存引用
script.async = true;
script.type = "text/javascript";
script.crossOrigin = "anonymous";
document.head.insertBefore(script, document.head.firstChild);
});
}
})();
接下来,在 initSw.js 中完成注册和注销 Service Worker。因为 register 方法的更新策略是文件内容发生变化或者注册文件路径发生变化时触发更新,所以可以通过对 initSw.js 添加版本参数的方式来立即获取最新的 sw.js 内容,如下所示:
function register(config) {
// 判断浏览器是否支持serviceWorker
if ('serviceWorker' in navigator) {
const { version, path, name } = config
const swUrl = `${path}/${name}?v=${version}`
navigator.serviceWorker
.register(swUrl)
.then(sw => {
console.log('sw 注册成功', sw);
})
.catch(err => {
console.error('sw 注册错误:', err);
});
}
}
function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(sw => {
sw.unregister(result => {
if (result) {
console.log('sw 注销成功');
}
});
});
}
}
// 注册
register({
version: 1,
path: '',
name: 'sw.js'
});
// 注销
// unregister();
上面代码中,当 sw.js 文件修改后,只需修改上面 initSw.js 文件中 version 字段就能完成更新注册。