本章我们将通过以下几个方面对 Workbox 的基本配置进行阐述说明,并以此开启 Workbox 的学习之旅:

  • 加载本地 workbox-sw.js 文件。
  • 模块异步加载问题。
  • 调试配置。
  • 缓存名称配置。
  • 启用 skipWaitingclients.claim

# 加载本地 workbox-sw.js 文件

由于 workbox-sw.jsWorkbox 的入口文件,所以在使用相关功能之前,我们必须加载该文件,比如:

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

默认情况下,Workboxinstallactivate 事件中不会调用 skipWaitingclients.claim 方法。不启用 skipWaiting 的原因我们已经在实战篇:Service Worker 更新中讨论过,此处不再重述,而不启用 clients.claim 的主要原因是,该方法只有在 Service Worker 被首次注册时才起作用,并且:

  • 如果页面在 Service Worker 取得控制权前后执行不同的逻辑,那么便没有启用 clients.claim 的必要。
  • 如果页面在 Service Worker 取得控制权前后执行相同的逻辑,由于页面已经渲染完成,且在刷新或跳转后 Service Worker 会自动取得控制权,因此启用 clients.claim 并不会带来多大的性能改善。

当然,如果你想要 Workbox 在 installactivate 事件中调用 skipWaitingclients.claim 方法,可通过以下方式启用:

workbox.core.skipWaiting();
workbox.core.clientsClaim();

# 总结

本章我们对 Workbox 的基本配置进行详细说明,那么接下来,就让我们一起进入 Workbox 预缓存的学习。

阅读全文