概要
这篇文章主要描述了作者在处理网站缓存策略时遇到的问题和解决过程:
- 最初遇到的问题:网站缓存更新不及时, 需要强制刷新才能获取最新资源, 手机端尤其明显
- 解决方案:
- 从自写的sw.js转向使用workbox框架
- workbox提供了现成的缓存策略, 使用更简单
- 详细介绍了workbox的主要配置:
- urlPattern:URL匹配规则
- handler:缓存策略类型(如CacheFirst、NetworkFirst等)
- options:详细配置项(如缓存名称、过期时间等)
- 最终发现:
- 问题可能与URL匹配规则有关
- Cloudflare的缓存也可能造成影响, 清除CF缓存后问题得到解决
这是一篇技术经验分享文章, 主要涉及前端缓存策略的优化和调试.
折磨的缓存
前言
我的缓存一直有问题, 本应该是带刷新的缓存优先但是需要等很久才刷新(也可能是压根就不会刷新, 只是我刷新次数多了?), 只有我强制刷新才能立刻更新资源. 最后的结果就是手机上看, 明明已经更新了但是死活获取不到最新的资源, 刷新多少次也没用. 只有刷新之后停一段时间才有可能拿到最新的资源, 改了很多次sw.js都没啥用.
从sw.js转到workbox
感觉实在写不好缓存策略, 干脆不写把sw.js删了, 改写workbox去了.
workbox也是使用sw, 但是提供了一些已经写好的缓存策略, 开发者只需要选择哪些资源使用哪种缓存策略就可以了, 很友好, 反正我用的很开心(因为不用自己写了).
// 我的部分config.mjs
workbox: {
// 定制缓存策略
runtimeCaching: [
{
// 匹配文章相关的js文件
urlPattern: /posts.+\.js$/,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'article-content',
expiration: {
maxEntries: 100, // 最多缓存100篇文章
maxAgeSeconds: 7 * 24 * 60 * 60, // 缓存一周
},
},
},
{
urlPattern: ({ request }) =>
request.mode === 'navigate',
handler: 'NetworkFirst',
options: {
cacheName: 'offline-pages',
plugins: [
{
handlerDidError: async () => {
// 当网络请求失败时返回离线页面
return caches.match('/offline.html');
},
},
],
},
},
{
// 其他静态资源
urlPattern: '/',
handler: 'NetworkFirst',
options: {
cacheName: 'index-resources',
expiration: {
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60, // 缓存一个月
},
},
},
],
// 预缓存资源
globPatterns: ['offline.html'],
},
urlPattern匹配
workbox的urlPattern
匹配支持字符串, 正则或者使用函数. 使用函数的话有这些参数可以使用:
/**
* request: 请求的 Request 对象, 包含有关请求的信息
* url: 请求的 URL 对象, 便于访问路径名、查询参数等
* event: 触发请求的事件, 例如 FetchEvent
*/
urlPattern: ({ request, url, event }) => {
// do something
return bool //一个布尔值
}
request:
- request.method:请求方法
- request.headers:请求头
- request.url 请求的完整 URL
- request.mode 请求模式, 例如 navigate, cors, no-cors
url: (以https://asd.com/posts/123?id=123
为例)
- url.pathname:URL 的路径部分,
/posts/123
- url.search:URL 的查询参数部分
?id=123
- url.origin:URL 的源部分
https://asd.com
- url.href 完整的URL字符串
https://asd.com/posts/123?id=123
event:
- event.request:关联的 Request 对象
- event.clientId:触发事件的客户端 ID
handler 缓存策略
- CacheFirst: 缓存优先, 请求资源先在缓存里找有没有. 如果有直接返回缓存, 如果没有则进行网络请求.
- NetworkFirst: 网络优先, 优先进行网络请求获取资源, 如果网络请求失败(离线或者网不好)则在缓存里找.
- CacheOnly: 仅缓存. 只从缓存里找, 如果没有就没有了. 一般结合预缓存使用.
- NetworkOnly: 仅网络. 只进行网络请求, 失败就失败了.
- StaleWhileRevalidate: 请求时立刻返回缓存中资源, 同时在后台异步进行网络请求获取最新资源.
options配置
cacheName: 缓存的名称
expiration:
- maxEntries: 最大缓存数量
- maxAgeSeconds: 缓存时间
- purgeOnQuotaError: 超过配额时是否清除
plugins: 放一些插件, 是一个数组
- handlerDidError: 处理请求错误的回调
- cacheWillUpdate: 缓存之前的回调
- cacheDidUpdate: 缓存之后的回调
- cachedResponseWillBeUsed: 使用缓存响应前的回调
plugins: [
{
handlerDidError: async () => {
// 当网络请求失败时返回离线页面
return caches.match('/offline.html');
},
},
],
预缓存配置
globPatterns里放需要先缓存的资源, 进入页面时会在sw安装的时候缓存这些资源. 这里尽量只缓存一些基本一定会加载的东西. 对于vitepress搭建的博客的话, 缓存所有js文件会让博客有离线访问的功能, 即使没网也能看任意文章, 因为加载文章都只加载对应的js文件而不是html. 但是代价是首次进入页面非常非常慢, 如果文章数量多的话.
globPatterns: ['/','**/*.{css,ico,webp}','index.html','offline.html'],
workbox的配置大概就这样吧, 有兴趣的可以去翻文档.
遇到的问题
在改成workbox之后问题依旧没有解决, 最后发现疑似是我的url匹配有问题, 我之前匹配的是/assets/posts_...js, 之后改成了匹配posts...js, 还把预缓存资源请了一下还是不行,,,
这就很神奇. 去开发者工具看, 发现sw.js里预缓存还是一大坨, 下面的url匹配也没改过来. 然后我想起来cf还有一个缓存, 把cf的缓存清了之后果然好了.