一、前言
Service Worker本质上也是浏览器缓存资源用的,只不过他不仅仅是Cache,也是通过worker的方式来进一步优化。
他基于h5的web worker,所以绝对不会阻碍当前js线程的执行,sw最重要的工作原理就是:
1、后台线程:独立于当前网页线程;
2、网络代理:在网页发起请求时代理,来缓存文件。
二、调试方法
被Service Worker缓存的文件,可以在Network中看到Size项为from Service Worker:
也可以在Application的Cache Storage中查看缓存的具体内容:
如果是具体的断点调试,需要使用对应的线程,不再是main线程了,这也是webworker的通用调试方法:
三、使用条件
sw 是基于 HTTPS 的,因为Service Worker中涉及到请求拦截,所以必须使用HTTPS协议来保障安全。如果是本地调试的话,localhost是可以的。
而我们刚好全站强制https化,所以正好可以使用。
四、生命周期
注册
"serviceWorker" in navigator && window.addEventListener("load",
function() {
var e = location.pathname.match(/\/news\/[a-z]{1,}\//)[0] + "article-sw.js?v=08494f887a520e6455fa";
navigator.serviceWorker.register(e).then(function(n) {
n.onupdatefound = function() {
var e = n.installing;
e.onstatechange = function() {
switch (e.state) {
case "installed":
navigator.serviceWorker.controller ?
console.log("New or updated content is available.") :
console.log("Content is now available offline!");
break;
case "redundant":
console.error("The installing service worker became redundant.")
}
}
}
}).
catch(function(e) {
console.error("Error during service worker registration:", e)
})
})
installing
//service worker安装成功后开始缓存所需的资源
var CACHE_PREFIX = 'cms-sw-cache';
var CACHE_VERSION = '0.0.20';
var CACHE_NAME = CACHE_PREFIX+'-'+CACHE_VERSION;
var allAssets = [ './main.css'];
self.addEventListener('install', function(event) {
//调试时跳过等待过程
self.skipWaiting();
// Perform install steps
//首先 event.waitUntil 你可以理解为 new Promise,
//它接受的实际参数只能是一个 promise,因为,caches 和 cache.addAll 返回的都是 Promise,
//这里就是一个串行的异步加载,当所有加载都成功时,那么 SW 就可以下一步。
//另外,event.waitUntil 还有另外一个重要好处,它可以用来延长一个事件作用的时间,
//这里特别针对于我们 SW 来说,比如我们使用 caches.open 是用来打开指定的缓存,但开启的时候,
//并不是一下就能调用成功,也有可能有一定延迟,由于系统会随时睡眠 SW,所以,为了防止执行中断,
//就需要使用 event.waitUntil 进行捕获。另外,event.waitUntil 会监听所有的异步 promise
//如果其中一个 promise 是 reject 状态,那么该次 event 是失败的。这就导致,我们的 SW 开启失败。
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('[SW]: Opened cache');
return cache.addAll(allAssets);
})
);
});
activated
activated阶段可以做很多有意义的事情,比如更新存储在Cache中的key和value:
var CACHE_PREFIX = 'cms-sw-cache';var CACHE_VERSION = '0.0.20';
/**
* 找出对应的其他key并进行删除操作
* @returns {*}
*/
function deleteOldCaches() {
return caches.keys().then(function (keys) {
var all = keys.map(function (key) {
if (key.indexOf(CACHE_PREFIX) !== -1 && key.indexOf(CACHE_VERSION) === -1){
console.log('[SW]: Delete cache:' + key);
return caches.delete(key);
}
});
return Promise.all(all);
});
}
//sw激活阶段,说明上一sw已失效
self.addEventListener('activate', function(event) {
event.waitUntil(
// 遍历 caches 里所有缓存的 keys 值
caches.keys().then(deleteOldCaches)
);
});
idle
这个空闲状态一般是不可见的,这种一般说明sw的事情都处理完毕了,然后处于闲置状态了。
浏览器会周期性的轮询,去释放处于idle的sw占用的资源。
fetch
该阶段是sw最为关键的一个阶段,用于拦截代理所有指定的请求,并进行对应的操作。
所有的缓存部分,都是在该阶段,这里举一个简单的例子:
//监听浏览器的所有fetch请求,对已经缓存的资源使用本地缓存回复
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
//该fetch请求已经缓存
if (response) {
return response;
}
return fetch(event.request);
})
);
});
五、缓存未更新问题解决
1、 什么是Service Worker缓存?
Service Worker缓存主要通过Cache API实现,允许开发者在客户端存储网络资源(如HTML、CSS、JavaScript、图片等)。缓存的资源可以在离线状态下提供访问,或在网络状况不佳时提升加载速度。缓存策略的选择直接影响资源的更新与获取方式。
2、常见的缓存策略
Cache First:优先从缓存中获取资源,若缓存不存在则从网络获取并缓存。
Network First:优先从网络获取资源,若网络请求失败则从缓存中获取。
Stale-While-Revalidate:立即从缓存中提供资源,同时在后台更新缓存。
Cache Only:仅使用缓存。
Network Only:仅使用网络。
3、Service Worker 缓存未更新的常见原因
3.1 缓存版本管理不当
未能有效管理缓存版本,导致旧的缓存持续存在,新资源未被缓存更新。
3.2 Service Worker 更新流程不正确
Service Worker的生命周期包括安装(install)、激活(activate)和事件监听。如果未正确处理这些阶段,可能导致新版本的Service Worker无法激活,从而旧缓存未更新。
3.3 缓存策略选择不当
选择了不适合的缓存策略,导致新资源未被及时缓存。例如,使用了Cache First策略而忽略了网络更新。
3.4 缓存清理不彻底
未能在激活阶段清理旧缓存,导致新资源无法正确覆盖旧缓存。
3.5 浏览器缓存干扰
浏览器自身的缓存机制可能与Service Worker的缓存策略冲突,导致资源未更新。
3.6 资源请求路径问题
请求资源的URL路径发生变化或不一致,导致Service Worker无法正确匹配和更新缓存。
3.7 缓存API使用错误
在Service Worker中错误地使用了Cache API,如未正确打开缓存、添加或删除资源。
4、如何检测缓存未更新的问题
4.1 使用浏览器开发者工具
大多数现代浏览器(如Chrome、Firefox、Edge)提供了强大的开发者工具,帮助开发者检测和调试Service Worker及其缓存。
步骤:
打开开发者工具:按F12或右键选择“检查”。
切换到“Application”(应用)面板: 在Chrome中,选择“Application” > “Service Workers”。 在Firefox中,选择“Storage” > “Service Workers”。
检查Service Worker状态: 确认Service Worker是否已注册并处于激活状态。 查看是否有新版本的Service Worker等待激活。
查看缓存内容: 在“Cache Storage”下,查看各个缓存的名称和存储的资源。 检查是否有最新的资源已被缓存。
监控网络请求: 切换到“Network”(网络)面板,观察资源加载来源(缓存或网络)。 强制刷新(Shift + F5)并观察Service Worker如何处理请求。
4.2 添加日志输出
在Service Worker的各个生命周期事件和缓存操作中添加console.log语句,帮助跟踪执行流程和资源处理情况。
示例:
self.addEventListener('install', event => {
console.log('[Service Worker] Install Event');
// 其他安装逻辑
});
self.addEventListener('activate', event => {
console.log('[Service Worker] Activate Event');
// 其他激活逻辑
});
self.addEventListener('fetch', event => {
console.log(`[Service Worker] Fetch Event for: ${event.request.url}`);
// 其他抓取逻辑
});
4.3 使用断点调试
在Service Worker的脚本中设置断点,逐步执行代码,检查缓存更新过程中的变量和状态。
步骤:
在开发者工具中打开Service Worker脚本。
**设置断点:**点击行号设置断点。
触发事件:如刷新页面,触发安装或激活事件。
**逐步执行:**使用调试工具逐步执行,观察变量和缓存状态的变化。
5、解决Service Worker 缓存未更新的方法
5.1 正确管理缓存版本
通过为缓存命名添加版本号,确保在更新Service Worker时清理旧缓存,缓存新资源。
示例:
const CACHE_NAME = 'my-app-cache-v2';
const urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js',
'/images/logo.png'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('[Service Worker] Caching all: app shell and content');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys()
.then(cacheNames => {
return Promise.all(
cacheNames.map(cache => {
if (cache !== CACHE_NAME) {
console.log('[Service Worker] Deleting old cache:', cache);
return caches.delete(cache);
}
})
);
})
);
});
解释:
**缓存命名:**CACHE_NAME包含版本号,便于管理不同版本的缓存。
**安装事件:**在安装阶段打开并缓存指定资源。
**激活事件:**在激活阶段清理旧版本的缓存,仅保留当前版本的缓存。
5.2 实现适当的缓存策略
根据不同资源的特性选择合适的缓存策略,确保新资源能够及时更新。
示例:Cache First 策略
适用于不经常变化的资源,如图标、样式表等。
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response; // 从缓存中返回资源
}
return fetch(event.request)
.then(networkResponse => {
return caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
});
})
);
});
示例:Network First 策略
适用于经常更新的数据,如API请求。
self.addEventListener('fetch', event => {
if (event.request.url.includes('/api/')) {
event.respondWith(
fetch(event.request)
.then(networkResponse => {
return caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
})
.catch(() => {
return caches.match(event.request);
})
);
} else {
// 其他资源使用不同的策略
}
});
5.3 使用skipWaiting和clients.claim
确保新版本的Service Worker能够立即激活,并控制所有客户端,从而实现缓存的即时更新。
示例:
self.addEventListener('install', event => {
self.skipWaiting(); // 强制等待中的Service Worker立即激活
});
self.addEventListener('activate', event => {
event.waitUntil(
self.clients.claim() // 使新的Service Worker立即控制所有页面
);
});
注意: 使用skipWaiting和clients.claim需要谨慎,确保不会破坏用户的当前会话或导致未保存的数据丢失。
5.4 清理旧缓存
在激活阶段,删除所有不再需要的旧缓存,避免缓存膨胀和资源冲突。
示例:
const CACHE_VERSION = 'v2';
const CURRENT_CACHES = {
main: `my-app-cache-${CACHE_VERSION}`
};
self.addEventListener('activate', event => {
const expectedCacheNames = Object.values(CURRENT_CACHES);
event.waitUntil(
caches.keys()
.then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (!expectedCacheNames.includes(cacheName)) {
console.log('[Service Worker] Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
5.5 实现缓存更新通知
通过向客户端发送消息,通知用户有新内容可用,并提示用户刷新页面以获取最新资源。
示例:
self.addEventListener('activate', event => {
event.waitUntil(
(async () => {
// 清理缓存逻辑
// ...
// 通知客户端有更新
const clientsList = await self.clients.matchAll();
clientsList.forEach(client => {
client.postMessage({ type: 'SW_UPDATED' });
});
})()
);
});
在客户端监听消息:
navigator.serviceWorker.addEventListener('message', event => {
if (event.data.type === 'SW_UPDATED') {
// 提示用户刷新页面
alert('有新版本可用,请刷新页面以获取最新内容。');
}
});
5.6 使用工具和库辅助管理Service Worker
利用成熟的工具和库,如Workbox,可以简化Service Worker的配置和管理,自动处理缓存更新和版本管理。
示例:使用Workbox生成Service Worker
安装Workbox CLI:
npm install workbox-cli --global
生成Service Worker配置文件:
workbox wizard
构建和部署:
workbox generateSW workbox-config.js
Workbox示例配置(workbox-config.js):
module.exports = {
"globDirectory": "dist/",
"globPatterns": [
"**/*.{html,js,css,png,jpg}"
],
"swDest": "dist/service-worker.js",
"clientsClaim": true,
"skipWaiting": true,
"runtimeCaching": [{
"urlPattern": /\/api\//,
"handler": "NetworkFirst",
"options": {
"cacheName": "api-cache",
"networkTimeoutSeconds": 10,
"expiration": {
"maxEntries": 50,
"maxAgeSeconds": 300
},
"cacheableResponse": {
"statuses": [0, 200]
}
}
}]
};
优点:
- 自动处理缓存版本管理。
- 提供丰富的缓存策略选项。
- 减少手动配置和维护的工作量。
6、最佳实践
6.1 明确缓存策略
根据资源的特性和更新频率,选择合适的缓存策略。将不常变化的静态资源使用Cache First策略,将频繁变化的数据使用Network First策略。
6.2 版本化缓存
通过为缓存命名添加版本号,确保在Service Worker更新时清理旧缓存,避免资源冲突和缓存污染。
6.3 及时更新Service Worker
确保在部署新版本应用时,Service Worker能够及时更新并激活,推送最新的资源到客户端
6.4 提供用户反馈
在Service Worker更新或缓存更新时,向用户提供明确的反馈和操作提示,如刷新页面以获取最新内容。
6.5 使用工具简化管理
利用Workbox等工具,自动化Service Worker的配置和管理,减少手动操作和配置错误的可能性。
6.6 定期清理缓存
避免缓存膨胀和资源冗余,定期清理不再需要的缓存,保持缓存的高效和整洁。
6.7 监控和记录
在生产环境中,使用错误监控和性能分析工具(如Sentry、LogRocket),实时监控Service Worker的运行状态和缓存行为,及时发现和解决问题。
7、实战案例
7.1 React应用中Service Worker缓存未更新的解决方案
场景:
在一个使用Create React App构建的React应用中,部署新版本后,用户仍然加载旧的静态资源,导致功能或样式未更新。
问题原因:
- Service Worker未正确激活新版本。
- 缓存策略导致旧资源被优先加载。
- 缓存版本管理不当。
解决方案步骤:
检查Service Worker配置:
确认Service Worker在src/index.js中正确注册。
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// 注册Service Worker
serviceWorkerRegistration.register({
onUpdate: registration => {
if (window.confirm("新版本可用,是否刷新页面?")) {
registration.waiting.postMessage({ type: 'SKIP_WAITING' });
window.location.reload();
}
}
});
更新Service Worker脚本:
修改public/service-worker.js或相关配置,确保新版本的Service Worker能够跳过等待并立即激活。
self.addEventListener('install', event => {
self.skipWaiting();
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cache => {
if (cache !== CURRENT_CACHE_NAME) {
return caches.delete(cache);
}
})
);
})
);
self.clients.claim();
});
更新缓存版本:
在Service Worker中,更新缓存名称,确保旧缓存被清理,新缓存被创建。
const CACHE_NAME = 'my-app-cache-v3'; // 更新版本号
const urlsToCache = [
'/',
'/index.html',
'/static/js/main.js',
'/static/css/main.css',
// 其他资源
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cache => {
if (cache !== CACHE_NAME) {
return caches.delete(cache);
}
})
);
})
);
self.clients.claim();
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request);
})
);
});
部署并测试:
- 部署新版本应用:确保新的Service Worker脚本和缓存配置已部署。
- 测试更新流程:
在浏览器中打开应用,查看Service Worker的注册状态。
刷新页面,确保新资源被加载。
检查旧缓存是否已被清理,新缓存是否已创建。
- 处理用户提示:在用户确认更新后,刷新页面获取最新资源。
7.2 使用Workbox管理缓存更新
场景:
在一个复杂的Web应用中,手动管理Service Worker的缓存更新和版本控制较为繁琐,决定使用Workbox简化流程。
解决方案步骤:
1.安装Workbox:
npm install workbox-webpack-plugin --save-dev
2.配置Webpack使用Workbox插件:
在webpack.config.js中,添加Workbox的生成Service Worker的配置。
const WorkboxPlugin = require('workbox-webpack-plugin');
module.exports = {
// 其他Webpack配置
plugins: [
// 其他插件
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true,
runtimeCaching: [{
urlPattern: /\.(?:png|jpg|jpeg|svg|gif)$/,
handler: 'CacheFirst',
options: {
cacheName: 'images',
expiration: {
maxEntries: 50,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 天
},
},
},
{
urlPattern: new RegExp('/api/'),
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
networkTimeoutSeconds: 10,
expiration: {
maxEntries: 50,
maxAgeSeconds: 5 * 60, // 5 分钟
},
cacheableResponse: {
statuses: [0, 200],
},
},
}],
}),
],
};
3.构建项目并部署:
npm run build
Workbox将自动生成一个优化的Service Worker脚本,处理缓存策略和更新流程。
4.测试缓存更新:
- 检查生成的Service Worker:在
build目录下查看生成的service-worker.js。 - 验证缓存策略:通过开发者工具检查不同资源的缓存策略是否按预期工作。
- 模拟更新:修改资源文件,重新构建并部署,确保新资源被正确缓存并替换旧缓存。
优点:
- 自动处理缓存策略和版本控制。
- 提供丰富的缓存策略选项,适应不同资源类型。
- 简化Service Worker的配置和管理。
8、结论
Service Worker 是提升Web应用性能和用户体验的重要工具,合理管理其缓存机制至关重要。然而,由于缓存策略、版本管理和浏览器特性的复杂性,Service Worker缓存未更新的问题在实际开发中较为常见。通过以下关键措施,开发者可以有效地预防和解决缓存未更新的问题:
- 正确管理缓存版本:通过为缓存命名添加版本号,确保在Service Worker更新时清理旧缓存,缓存新资源。
- 实施合适的缓存策略:根据资源的特性选择合适的缓存策略,如Cache First、Network First等,确保资源的及时更新和高效加载。
- 使用
skipWaiting和clients.claim:确保新版本的Service Worker能够立即激活并控制所有客户端,推动缓存的即时更新。 - 提供用户反馈和控制:在Service Worker更新或缓存更新时,向用户提供明确的反馈和操作提示,提升用户体验。
- 利用工具和库:使用如Workbox等成熟的工具和库,简化Service Worker的配置和管理,自动处理缓存策略和版本控制。
- 定期清理缓存:避免缓存膨胀和资源冗余,定期清理不再需要的缓存,保持缓存的高效和整洁。
- 监控和记录:在生产环境中,使用错误监控和性能分析工具,实时监控Service Worker的运行状态和缓存行为,及时发现和解决问题。
- 代码审查与测试:通过代码审查和编写单元测试,确保Service Worker和缓存策略在不同浏览器中的一致性和稳定性。