本章我们将通过以下几个方面对 Workbox 的基本配置进行阐述说明,并以此开启 Workbox 的学习之旅:
- 加载本地
workbox-sw.js文件。 - 模块异步加载问题。
- 调试配置。
- 缓存名称配置。
- 启用
skipWaiting和clients.claim。
# 加载本地 workbox-sw.js 文件
由于
workbox-sw.js是Workbox的入口文件,所以在使用相关功能之前,我们必须加载该文件,比如:
importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js');
由于网络环境因素导致 Google 的 CDN 往往无法访问,因此我们一般通过本地来加载该文件:
importScripts('/third_party/workbox/workbox-sw.js');
workbox.setConfig({
modulePathPrefix: '/third_party/workbox/',
modulePathCb: (moduleName, debug) => {
return `/third_party/workbox/${moduleName}`
}
});
上述代码中,除了将
importScripts中的参数由绝对网络地址转换为相对地址外,我们还调用了workbox.setConfig方法进行了一些设置,之所以如此,是因为我们在使用workbox.strategies等模块的时候,workbox内部会首先调用importScripts方法来加载相关模块代码文件,其参数的构造规则为:
- 如果设置了
modulePathCb,则调用该方法并将其返回值作为importScripts的参数,否则进入下一步, - 如果设置了
modulePathPrefix,则将该属性值与模块名称进行拼接后作为importScripts的参数,否则将 CDN 的根地址与模块名称进行拼接后作为 importScripts 的参数。
其中 modulePathCb 的参数为:
moduleName:所调用的模块名称。debug:是否使用调试版本,其值取自workbox配置中的 debug 属性(将在下文介绍)。
# 模块异步加载问题
上文我们说过,当使用
workbox.strategies等模块的时候,workbox 内部会首先调用 importScripts 方法来加载相关模块代码文件,由于 Service Worker 中的importScripts方法只能在install事件中或在 Service Worker 脚本的全局作用域内调用,因此以下调用将会导致问题:
self.addEventListener('fetch', event => {
if (event.request.url.endsWith('.png')) {
const cacheFirst = new workbox.strategies.CacheFirst();
event.respondWith(cacheFirst.makeRequest({request: event.request}));
}
});
可通过以下方式来修复该问题:
workbox.loadModule('workbox-strategies');
self.addEventListener('fetch', event => {
if (event.request.url.endsWith('.png')) {
const cacheFirst = new workbox.strategies.CacheFirst();
event.respondWith(cacheFirst.makeRequest({request: event.request}));
}
});
或:
const { strategies } = workbox;
self.addEventListener('fetch', event => {
if (event.request.url.endsWith('.png')) {
const cacheFirst = new strategies.CacheFirst();
event.respondWith(cacheFirst.makeRequest({request: event.request}));
}
});
那在 install 事件中加载相关模块又如何呢?比如:
self.addEventListener('install', event => {
workbox.loadModule('workbox-strategies');
});
self.addEventListener('fetch', event => {
if (event.request.url.endsWith('.png')) {
const cacheFirst = new workbox.strategies.CacheFirst();
event.respondWith(cacheFirst.makeRequest({request: event.request}));
}
});
这样做的后果是肯定会出问题但我们却不知道它何时出现,这是因为:
- 在 Service Worker 没有任何更新的情况下,install 事件只会被调用一次
- 而 Service Worker 线程会在空闲时自行关闭(可能发生在两个事件之间或其他时机),并且线程关闭后,全局变量可能会被销毁(此处为 workbox),
- 当 Service Worker 线程再次启动后,workbox 对象会被重新初始化,而此时在 fetch 事件中调用 workbox.strategies 会因为违反了 importScripts 的调用规则而导致异常。
基于以上原因,且由于 workbox 中的各模块已经为我们悄悄处理了 fetch 等事件,因此使用 workbox.strategies 等模块的最佳实践便是在 Service Worker 脚本的全局作用域内使用。因此以上代码可修改为:
workbox.routing.registerRoute(
new RegExp('\\.png$'),
new workbox.strategies.CacheFirst()
);
# 调试配置
Workbox 的所有模块代码均包含调试模式和线上模式,前者相对于后者多了日志输出及参数类型检测功能,通过配置项的 debug 属性来控制加载何种模式的代码,其默认值的设置规则为:如果应用运行在 localhost 上,为 true,否则为 false。
当然,我们也通过以下配置来强行指定需要加载何种模式的代码:
workbox.setConfig({
debug: <true or false>
});
必须保证
workbox.setConfig在使用workbox模块(比如 workbox.routing)之前调用,否则将抛出Config must be set before accessing workbox.* modules异常。
# 缓存名称配置
在 Workbox 中,缓存名称的格式为:
<prefix>-<cache id (precache | runtime | googleAnalytics)>-<suffix>,默认值分别为:
- 预缓存:
workbox-precache-v2-${scope} - 运行时缓存:
workbox-runtime-${scope} - Google 分析:
workbox-googleAnalytics-${scope}
其中 scope 的值为我们在 UI 线程中调用
navigator.serviceWorker.register时传递的 scope 参数。可通过以下方式修改其默认值:
workbox.core.setCacheNameDetails({
prefix: 'my-app',
suffix: 'v1',
precache: 'install-time',
runtime: 'run-time',
googleAnalytics: 'ga',
});
# 启用 skipWaiting 和 clients.claim
默认情况下,
Workbox在install、activate事件中不会调用skipWaiting、clients.claim方法。不启用skipWaiting的原因我们已经在实战篇:Service Worker更新中讨论过,此处不再重述,而不启用clients.claim的主要原因是,该方法只有在 Service Worker 被首次注册时才起作用,并且:
- 如果页面在 Service Worker 取得控制权前后执行不同的逻辑,那么便没有启用 clients.claim 的必要。
- 如果页面在 Service Worker 取得控制权前后执行相同的逻辑,由于页面已经渲染完成,且在刷新或跳转后 Service Worker 会自动取得控制权,因此启用 clients.claim 并不会带来多大的性能改善。
当然,如果你想要 Workbox 在
install、activate事件中调用skipWaiting、clients.claim方法,可通过以下方式启用:
workbox.core.skipWaiting();
workbox.core.clientsClaim();
# 总结
本章我们对 Workbox 的基本配置进行详细说明,那么接下来,就让我们一起进入 Workbox 预缓存的学习。