官方文档原文:
At this point, service workers may seem tricky. There’s lots of complex interactions that are hard to get right. Network requests! Caching strategies! Cache management! Precaching! It’s a lot to remember. This doesn’t make service worker an ill-designed technology; it works as intended, and solves hard problems.
Good abstractions make complex APIs easier to use. That’s where Workbox comes in. Workbox is a set of modules that simplify common service worker routing and caching. Each module available addresses a specific aspect of service worker development. Workbox aims to make using service workers as easy as possible, while allowing the flexibility to accommodate complex application requirements where needed.
官方介绍文档链接:What is Workbox?
简单的来说 Workbox 是由 Google 开发的一个基于 Service Worker 的js库,提供了很多高效好用的工具和策略,其目的在于更加高效的实现应用缓存和离线优先应用(PWA),不再需要繁杂的编写SW相关的代码
推荐在SW文件中引入Workbox cdn使用
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
在使用 Workbox 之前先然我们了解一下其提供的几种常用缓存策略
tips: 此处只介绍了业务中比较能用到的策略,像 Cache only(仅使用缓存)等不大可能使用的策略就不在这里展开了
该策略将在页面发出请求时优先使用缓存中的数据作为响应,在缓存中无响应时才会发起网络请求
这种策略适合缓存一些不经常变动的资源,例如在 hoyolab 主站 pc 中使用了这种策略缓存了图片与svga等静态资源
该策略将在页面发出请求时优先进行网络请求,请求成功后将响应存至缓存,当请求失败,若缓存中有内容响应则使用缓存响应
这种策略适合对稳定性要求较高的业务场景,保证在服务器错误时,前端应用不至于完全不可用
该策略将在页面发出请求时先尝试使用缓存作为响应,然后在后台发起网络请求,将请求的响应更新至缓存,当然若缓存一开始就响应失败则直接使用网络请求
这种策略适合有变动的需求,但能接受当前使用过期缓存,后续访问更新的场景,或是对一些第三方资源的缓存
例如在 hoyolab 主站 pc 业务中就使用了该种策略缓存了语言列表、编辑器分割线资源、第三方播放器脚本(YouTube, twitch),多语言资源等请求
运行时缓存 Runtime Caching 支持我们更加灵活和动态的去指定我们的缓存方案,我们可以指定一些路由来配置相应的缓存策略,来捕获一些动态内容,根据我们的策略来做缓存
使用方式
workbox.routing.registerRoute(matchCallback, handlerCallback, method)
参数 | 作用 |
---|---|
matchCallback | 可以是一个函数或者正则表达式用来匹配请求路由 |
handlerCallback | 处理匹配请求的策略函数,一般而言直接使用 Workbox 提供的策略new workbox.strategies[‘策略’] |
method(可选) | 请求方法POST/GET若未指定则会默认匹配所有http方法 |
以下是运行时缓存的代码示例
workbox.routing.registerRoute(
new RegExp('\\.(?:svg|svga)$'), // 路由匹配正则
new workbox.strategies.CacheFirst({ // 上文提到的缓存策略
cacheName: 'svgCache', // 可以为你的缓存起个自定义名字方便后续的管理
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 50, // 设置最大请求条数
maxAgeSeconds: 7 * 24 * 60 * 60, // 设置最长请求时间7天
}),
],
})
);
预缓存是在SW安装的阶段就根据指定的缓存列表将资源存至缓存中,使其在离线状态下也可以进行访问
使用方式
workbox.precaching.precacheAndRoute(cacheArr)
该方法接受一个数组,我们可以在数组中指定我们想要缓存的资源
workbox.precaching.precacheAndRoute([
{url: '/A.html', revision: '1.0.0' },
{url: '/B.css', revision: '1.0.0'},
{url: '/C.js', revision: '1.0.0'}
]);
revision字段主要用于标识我们的缓存版本,当改标识变更时会触发缓存更新
当然提供workbox的一些工具和构建流程结合我们也可以将这个过程自动化,这里先不展开了
后台同步允许你在某个时机或周期性的在后台对后端数据进行同步
比如用户请求了数据,但是当前网络环境不稳定导致用户离线,这时我们可以使用 workbox 将失败的请求收集起来,在用户网络环境恢复时再次请求
下面是一个简单的实例
注册待重试的请求
const bgSyncPlugin = new workbox.backgroundSync.BackgroundSyncPlugin('retryQueue', {
maxRetentionTime: 24 * 60 // 最多重试时间
});
workbox.routing.registerRoute(
/\/api/, // 随便写了个正则
new workbox.strategies.NetworkOnly({
plugins: [bgSyncPlugin]
}),
'POST'
);
该实例中我们设置了一个同步队列 retryQueue,然后匹配所有的/api请求,在这些请求失败时将请求加入到我们的重试队列中
注册待执行的函数
workbox.backgroundSync.registerRoute('retryQueue', async ({queue}) => {
let entry;
while (entry = await queue.shiftRequest()) {
try {
const response = await fetch(entry.request);
const responseData = await response.json();
} catch (error) {
await queue.unshiftRequest(entry);
return;
}
}
});
该实例函数会在用户重新在线时触发,从 retryQueue 队列中取出请求并执行,请求成功我们可以同步这些数据到业务侧,若请求失败则放回请求队列
除此之外SW的后台同步还能有更多的使用方式如表单后台同步,备份,轮询状态等场景这里就不展开了
其实就正如介绍中所说的Workbox基于SW提供了非常多的工具和实现,简化了SW的使用,开发者能使用workbox更快的实现业务上的缓存功能,而不必编写底层的缓存策略
比如前文介绍的对svg资源的缓存,我们可以对比一下原生SW与workbox的实现差异
workbox:
workbox.routing.registerRoute(
new RegExp('\\.(?:svg|svga)$'), // 路由匹配正则
new workbox.strategies.CacheFirst({ // 上文提到的缓存策略
cacheName: 'svgCache', // 可以为你的缓存起个自定义名字方便后续的管理
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 50, // 设置最大请求条数
maxAgeSeconds: 7 * 24 * 60 * 60, // 设置最长请求时间7天
}),
],
})
);
SW原生实现demo(生产环境策略指定需要更加的细化)
const CACHE_NAME = 'svga-cache';
const MAX_ENTRIES = 50;
const MAX_AGE = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
// Clean old cache entries
async function cleanCache() {
const cache = await caches.open(CACHE_NAME);
const keys = await cache.keys();
const entries = keys.filter(request => request.url.endsWith('.svga'));
// Sort entries by age
entries.sort((a, b) => a.url < b.url ? 1 : -1);
// Delete old or excess entries
for(let i = entries.length - 1; i >= 0; i--) {
if(i > MAX_ENTRIES - 1 || (Date.now() - entries[i].url) > MAX_AGE) {
await cache.delete(entries[i]);
}
}
}
// Install event
self.addEventListener('install', event => {
event.waitUntil(cleanCache());
});
// Fetch event
self.addEventListener('fetch', event => {
if (event.request.url.endsWith('.svga')) {
event.respondWith(caches.open(CACHE_NAME).then(cache => {
return cache.match(event.request).then(response => {
if (response) {
// Cache hit
return response;
}
// Cache miss
return fetch(event.request.clone()).then(networkResponse => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
});
}));
}
});
// Activate event
self.addEventListener('activate', event => {
event.waitUntil(cleanCache());
});
很容易看出workbox具备更强大的易用性
当然选择workbox也并不是完全抛弃SW的原生使用,两者其实可以根据业务需要去并存