プラグインの使用

Workbox を使用する場合は、フェッチまたはキャッシュされるときにリクエストとレスポンスを操作することをおすすめします。ワークボックス プラグインを使用すると、最小限のボイラープレートで Service Worker に動作を追加できます。パッケージ化して独自のプロジェクトで再利用することも、他のユーザーに公開して使用することもできます。

Workbox にはすぐに使えるプラグインが多数用意されています。アプリケーションの要件に合わせてカスタム プラグインを作成することも可能です。

利用可能なワークボックス プラグイン

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

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

ワークボックス プラグイン(上記のプラグインまたはカスタム プラグインのいずれでもかまいません)は、プラグインのインスタンスを戦略の 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
      }),
    ],
  })
);

カスタム プラグインのメソッド

ワークボックス プラグインには 1 つ以上のコールバック関数を実装する必要があります。Strategy にプラグインを追加すると、コールバック関数は適切なタイミングで自動的に実行されます。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: 戦略の呼び出しによってイベントに追加されたすべてのライフタイム 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 オブジェクトも渡されます。このオブジェクトは、特定のプラグインとそれが呼び出す戦略に固有の state オブジェクトです。つまり、同じプラグイン内の別のコールバックの実行内容に基づいて、1 つのコールバックが条件付きでタスクを実行できるようにするプラグインを作成できます(たとえば、requestWillFetch() の実行と fetchDidSucceed() または fetchDidFail() の実行の差を計算できます)。

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

開発したプラグインがプロジェクト外で使用されると思われる場合は、モジュールとして公開することをおすすめします。コミュニティ提供の Workbox プラグインを以下にいくつか示します。

npm のリポジトリを検索すると、コミュニティ提供の Workbox プラグインが他にもある可能性があります。

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