使用插件

使用 Workbox 时,您可能希望在提取或缓存请求和响应时对其进行操作。借助 Workbox 插件,您可以通过最少的额外样板向 Service Worker 添加其他行为。你可以将它们打包在自己的项目中重复使用,也可以公开发布以供他人使用。

Workbox 为我们提供了一些开箱即用的插件,如果您比较擅长,也可以根据应用的要求编写自定义插件。

可用的 Workbox 插件

Workbox 提供以下可在 Service Worker 中使用的官方插件:

借助 Workbox 策略,您可以将 Workbox 插件实例添加到策略的 plugins 属性中,从而与 Workbox 策略一起使用,无论它们是上面列出的插件之一还是自定义插件:

import {registerRoute} from 'workbox-routing';
import {CacheFirst} from 'workbox-strategies';
import {ExpirationPlugin} from 'workbox-expiration';

registerRoute(
  ({request}) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 60,
        maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
      }),
    ],
  })
);

适用于自定义插件的方法

Workbox 插件需要实现一个或多个回调函数。将插件添加到 Strategy 后,回调函数会在适当的时间自动运行。Strategy 会向您的回调函数传递有关当前请求和/或响应的相关信息,从而为您的插件提供执行操作所需的上下文。支持以下回调函数:

  • cacheWillUpdate:在使用 Response 更新缓存之前调用。在此方法中,可以在将响应添加到缓存之前对其进行更改,也可以返回 null 以避免彻底更新缓存。
  • cacheDidUpdate:在缓存中添加了新条目或更新现有条目时调用。如果您想在缓存更新后执行操作,那么使用此方法的插件可能会很有用。
  • cacheKeyWillBeUsed:在请求用作缓存键之前调用。缓存查询(当 mode'read' 时)和缓存写入时(mode'write' 时)都会发生这种情况。如果您需要替换或规范化网址,然后再使用网址访问缓存,则此回调非常方便。
  • cachedResponseWillBeUsed:在使用来自缓存的响应之前调用此方法,以便您检查该响应。此时,您可以返回不同的响应或返回 null
  • requestWillFetch:每当请求要发送到网络时都会调用。如果您需要在 Request 即将其连接到网络之前更改它,那么这会非常有用。
  • fetchDidFail:在网络请求失败时调用,这很可能是由于没有网络连接而调用;如果浏览器已连接到网络但收到错误(例如 404 Not Found),则不会触发。
  • fetchDidSucceed:每当网络请求成功时调用,无论 HTTP 响应代码如何。
  • handlerWillStart:在任何处理程序逻辑开始运行之前调用,如果您需要设置初始处理程序状态,则该方法非常有用。例如,如果您想知道处理程序生成响应所花的时间,可以在此回调中记录开始时间。
  • handlerWillRespond:在策略的 handle() 方法返回响应之前调用。如果您需要先修改响应,然后再将其返回到 RouteHandler 或其他自定义逻辑,那么该方法会非常实用。
  • handlerDidRespond:在策略的 handle() 方法返回响应后调用。这时,记录任何最终响应详细信息(例如,在其他插件进行更改后)可能很有用。
  • handlerDidComplete:在通过策略调用调用向事件添加的所有延长生命周期 promise 后调用。如果您需要为需要等到处理程序完成运行的任何数据生成报告,以便计算缓存命中状态、缓存延迟时间、网络延迟时间及其他实用信息,则此功能非常有用。
  • handlerDidError:在处理程序无法提供来自任何来源的有效响应时调用,此时正是提供某种回退响应(作为完全失败的替代方案)的最佳时机。

所有这些回调都是 async,因此每当缓存或提取事件到达相关回调的相关点时,都需要使用 await

如果插件使用了上述所有回调,那么生成的代码如下所示:

const myPlugin = {
  cacheWillUpdate: async ({request, response, event, state}) => {
    // Return `response`, a different `Response` object, or `null`.
    return response;
  },
  cacheDidUpdate: async ({
    cacheName,
    request,
    oldResponse,
    newResponse,
    event,
    state,
  }) => {
    // No return expected
    // Note: `newResponse.bodyUsed` is `true` when this is called,
    // meaning the body has already been read. If you need access to
    // the body of the fresh response, use a technique like:
    // const freshResponse = await caches.match(request, {cacheName});
  },
  cacheKeyWillBeUsed: async ({request, mode, params, event, state}) => {
    // `request` is the `Request` object that would otherwise be used as the cache key.
    // `mode` is either 'read' or 'write'.
    // Return either a string, or a `Request` whose `url` property will be used as the cache key.
    // Returning the original `request` will make this a no-op.
    return request;
  },
  cachedResponseWillBeUsed: async ({
    cacheName,
    request,
    matchOptions,
    cachedResponse,
    event,
    state,
  }) => {
    // Return `cachedResponse`, a different `Response` object, or null.
    return cachedResponse;
  },
  requestWillFetch: async ({request, event, state}) => {
    // Return `request` or a different `Request` object.
    return request;
  },
  fetchDidFail: async ({originalRequest, request, error, event, state}) => {
    // No return expected.
    // Note: `originalRequest` is the browser's request, `request` is the
    // request after being passed through plugins with
    // `requestWillFetch` callbacks, and `error` is the exception that caused
    // the underlying `fetch()` to fail.
  },
  fetchDidSucceed: async ({request, response, event, state}) => {
    // Return `response` to use the network response as-is,
    // or alternatively create and return a new `Response` object.
    return response;
  },
  handlerWillStart: async ({request, event, state}) => {
    // No return expected.
    // Can set initial handler state here.
  },
  handlerWillRespond: async ({request, response, event, state}) => {
    // Return `response` or a different `Response` object.
    return response;
  },
  handlerDidRespond: async ({request, response, event, state}) => {
    // No return expected.
    // Can record final response details here.
  },
  handlerDidComplete: async ({request, response, error, event, state}) => {
    // No return expected.
    // Can report any data here.
  },
  handlerDidError: async ({request, event, error, state}) => {
    // Return a `Response` to use as a fallback, or `null`.
    return fallbackResponse;
  },
};

上述回调中提供的 event 对象是触发提取或缓存操作的原始事件。有时,不会有原始事件,因此您的代码应在引用它之前检查其是否存在。

系统还会向所有插件回调传递一个 state 对象,该对象对于特定插件及其调用的策略来说是唯一的。这意味着您可以编写插件,使一个回调能够根据同一插件中另一个回调所执行的操作有条件地执行某项任务(例如,计算运行 requestWillFetch() 与运行 fetchDidSucceed()fetchDidFail() 之间的差异)。

第三方插件

如果您开发了一个插件并认为它可在您的项目之外使用,我们建议您将其作为模块发布!以下是社区提供的 Workbox 插件的简短列表:

您可以在 npm 的代码库中搜索,找到更多社区提供的 Workbox 插件

最后,如果您已构建好要共享的 Workbox 插件,请在发布该插件时添加 workbox-plugin 关键字。如果是,请在 Twitter 上告诉我们 @WorkboxJS