使用插件

使用 Workbox 时,您可能需要在请求和响应提取或缓存时对其进行操作。借助 Workbox 插件,您可以向 Service Worker 添加其他行为,而无需额外添加大量样板代码。您可以将其打包并在自己的项目中重复使用,也可以公开发布以供他人使用。

Workbox 提供了许多开箱即用的插件,如果您是手艺精湛的开发者,还可以根据应用的要求编写自定义插件。

可用的 Workbox 插件

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

如需将 Workbox 插件(无论是上述插件之一,还是自定义插件)与 Workbox 策略搭配使用,只需将该插件的实例添加到策略的 plugins 属性中即可:

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 插件需要实现一个或多个回调函数。将插件添加到策略后,系统会在适当的时间自动运行回调函数。策略会将与当前请求和/或响应相关的信息传递给回调函数,为您的插件提供执行操作所需的上下文。支持以下回调函数:

  • 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)!