プラグインの使用

Workbox を使用する場合は、リクエストとレスポンスを取得またはキャッシュに保存するときに操作する場合があります。Workbox プラグインを使用すると、余分なボイラープレートを最小限に抑えながら、サービス ワーカーに追加の動作を追加できます。これらはパッケージ化して独自のプロジェクトで再利用することも、他の人が使用できるように一般公開することもできます。

Workbox には、すぐに使用できるさまざまなプラグインが用意されています。また、アプリの要件に合わせてカスタム プラグインを作成することもできます。

利用可能な Workbox プラグイン

Workbox には、Service Worker で使用できる次の公式プラグインが用意されています。

  • BackgroundSyncPlugin: ネットワーク リクエストが失敗した場合、このプラグインを使用すると、バックグラウンド同期キューに追加して、次の同期イベントがトリガーされたときに再度リクエストできます。
  • BroadcastUpdatePlugin: キャッシュが更新されるたびに、ブロードキャスト チャンネルまたは postMessage() 経由でメッセージをディスパッチできます。
  • CacheableResponsePlugin: 特定の条件を満たすリクエストのみをキャッシュに保存します。
  • ExpirationPlugin: キャッシュ内のアイテムの数と最大経過時間を管理します。
  • RangeRequestsPlugin: Range HTTP リクエスト ヘッダーを含むリクエストに応答します。

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 プラグインには、1 つ以上のコールバック関数を実装する必要があります。Strategy にプラグインを追加すると、コールバック関数が適切なタイミングで自動的に実行されます。戦略は、現在のリクエストやレスポンスに関する関連情報をコールバック関数に渡し、アクションを実行するために必要なコンテキストをプラグインに提供します。次のコールバック関数がサポートされています。

  • cacheWillUpdate: Response を使用してキャッシュを更新する前に呼び出されます。この方法では、レスポンスはキャッシュに追加される前に変更できます。また、null を返してキャッシュ全体を更新しないようにすることもできます。
  • cacheDidUpdate: 新しいエントリがキャッシュに追加されたとき、または既存のエントリが更新されたときに呼び出されます。このメソッドを使用するプラグインは、キャッシュの更新後にアクションを実行する場合に便利です。
  • cacheKeyWillBeUsed: リクエストがキャッシュキーとして使用される前に呼び出されます。これは、キャッシュ検索(mode'read' の場合)とキャッシュ書き込み(mode'write' の場合)の両方で発生します。このコールバックは、URL を使用してキャッシュにアクセスする前に、URL をオーバーライドまたは正規化する必要のある場合に便利です。
  • cachedResponseWillBeUsed: キャッシュからのレスポンスが使用される直前に呼び出され、そのレスポンスを調べることができます。この時点では、別のレスポンスを返すか、null を返すことができます。
  • requestWillFetch: リクエストがネットワークに送信される直前に呼び出されます。ネットワークに送信する直前に Request を変更する必要がある場合に便利です。
  • fetchDidFail: ネットワーク リクエストが失敗したときに呼び出されます(ネットワーク接続がない場合が多い)。ブラウザにネットワーク接続があるにもかかわらずエラー(404 Not Found など)が返された場合はトリガーされません。
  • fetchDidSucceed: HTTP レスポンス コードに関係なく、ネットワーク リクエストが成功するたびに呼び出されます。
  • handlerWillStart: ハンドラ ロジックの実行が開始される前に呼び出されます。初期ハンドラ状態を設定する場合に便利です。たとえば、ハンドラがレスポンスを生成するのに要した時間を把握したい場合は、このコールバックで開始時間をメモできます。
  • handlerWillRespond: ストラテジーの handle() メソッドがレスポンスを返す前に呼び出されます。これは、レスポンスを RouteHandler などのカスタム ロジックに返す前に変更する必要がある場合に便利です。
  • handlerDidRespond: 戦略の handle() メソッドがレスポンスを返した後に呼び出されます。このような場合は、最終的なレスポンスの詳細を記録しておくと便利です(他のプラグインによる変更の後など)。
  • handlerDidComplete: 戦略の呼び出しからイベントに追加されたすべての存続期間延長プロミスが決済された後に呼び出されます。これは、キャッシュヒット ステータス、キャッシュ レイテンシ、ネットワーク レイテンシなどの有用な情報を計算するために、ハンドラが完了するまで待機する必要があるデータをレポートする必要がある場合に便利です。
  • 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 オブジェクトも渡されます。つまり、1 つのコールバックが、同じプラグイン内の別のコールバックが行った処理に基づいて、条件付きでタスクを実行できるプラグインを作成できます(たとえば、requestWillFetch()fetchDidSucceed() または fetchDidFail() の実行結果の差を計算するなど)。

サードパーティ プラグイン

開発したプラグインがプロジェクト以外でも使用されていると思われる場合は、モジュールとして公開することをおすすめします。以下は、コミュニティが提供するワークボックスプラグインの簡単なリストです。

npm のリポジトリで検索すると、コミュニティ提供の Workbox プラグインをさらに見つけられる場合があります。

最後に、作成した Workbox プラグインを共有する場合は、公開時に workbox-plugin キーワードを追加します。問題が発生した場合は、Twitter の @WorkboxJS までお知らせください。