Service Worker の更新を即時に処理する

デフォルトでは、Service Worker のライフサイクルでは、更新された Service Worker が検出およびインストールされたとき、現在の Service Worker が制御しているすべての開いているタブを閉じるか、ナビゲーションを行ってから、更新された Service Worker がアクティブになって制御されるようにする必要があります。

多くの場合、時間の経過とともにこの処理が行われることに問題はありませんが、保留中の Service Worker 更新があることをユーザーに知らせて、新しい Service Worker への切り替えプロセスを自動化したい場合もあります。そのためには、ページにコードをいくつか追加し、Service Worker を使用します。

ページに配置するコード

次のコードは、CDN でホストされるバージョンの workbox-window からインポートした JavaScript モジュールを使用して、インライン <script> 要素で実行されます。workbox-window を使用して Service Worker を登録し、Service Worker が待機フェーズで行き詰った場合に対処します。待機中の Service Worker が見つかると、サイトの更新版が利用できることをユーザーに通知し、再読み込みするよう促します。

<!-- This script tag uses JavaScript modules, so the proper `type` attribute value is required -->
<script type="module">
  // This code sample uses features introduced in Workbox v6.
  import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-window.prod.mjs';

  if ('serviceWorker' in navigator) {
    const wb = new Workbox('/sw.js');
    let registration;

    const showSkipWaitingPrompt = async (event) => {
      // Assuming the user accepted the update, set up a listener
      // that will reload the page as soon as the previously waiting
      // service worker has taken control.
      wb.addEventListener('controlling', () => {
        // At this point, reloading will ensure that the current
        // tab is loaded under the control of the new service worker.
        // Depending on your web app, you may want to auto-save or
        // persist transient state before triggering the reload.
        window.location.reload();
      });

      // When `event.wasWaitingBeforeRegister` is true, a previously
      // updated service worker is still waiting.
      // You may want to customize the UI prompt accordingly.

      // This code assumes your app has a promptForUpdate() method,
      // which returns true if the user wants to update.
      // Implementing this is app-specific; some examples are:
      // https://open-ui.org/components/alert.research or
      // https://open-ui.org/components/toast.research
      const updateAccepted = await promptForUpdate();

      if (updateAccepted) {
        wb.messageSkipWaiting();
      }
    };

    // Add an event listener to detect when the registered
    // service worker has installed but is waiting to activate.
    wb.addEventListener('waiting', (event) => {
      showSkipWaitingPrompt(event);
    });

    wb.register();
  }
</script>

リクエストを受け入れると、messageSkipWaiting() は待機中の Service Worker に、self.skipWaiting() を呼び出すよう伝えます。つまり、有効化を開始します。有効にすると、新しい Service Worker が既存のクライアントを制御し、workbox-windowcontrolling イベントをトリガーします。この場合、現在のページは、事前キャッシュに保存されたすべてのアセットの最新バージョンと、更新された Service Worker で見つかった新しいルーティング ロジックを使用して再読み込みされます。

Service Worker に入れるコード

ページの前のセクションのコードを取得したら、待機フェーズをスキップするタイミングを知らせるコードを Service Worker に追加する必要があります。workbox-build から generateSW を使用していて、skipWaiting オプションを false(デフォルト)に設定している場合は、生成された Service Worker ファイルに以下のコードが自動的に含まれるため、問題はありません。

Workbox のビルドツール(injectManifest モード)と組み合わせて独自の Service Worker を作成する場合は、次のコードを自分で追加する必要があります。

addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
  }
});

これにより、type 値が SKIP_WAITINGworkbox-window から Service Worker に送信されたメッセージをリッスンし、発生したときに self.skipWaiting() を呼び出します。前のコードサンプルで示した workbox-windowmessageSkipWaiting() メソッドが、このメッセージを送信します。

プロンプトを表示する必要があるか

これは、Service Worker をデプロイするすべてのアプリケーションが従う必要があるパターンではありません。これは、Service Worker のアップデートでページを再読み込みできない一部のシナリオを対象としています。これにより、予期しない動作が発生する可能性があります。再読み込みプロンプトを表示する必要があるかどうかについて厳格なルールはありませんが、次のような場合に意味がある場合があります。

  • プレキャッシュを幅広く使用します。静的アセットに関しては、ナビゲーション リクエストにネットワーク ファーストまたはネットワークのみの戦略を使用し、静的アセットを遅延読み込みすると、後で問題が発生する可能性があります。これにより、バージョニングされたアセットが変更され、Service Worker が事前キャッシュしていない場合があります。ここに再読み込みボタンを配置すると、予期しない動作を回避できます。
  • 事前キャッシュされた HTML を配信する場合この場合、Service Worker の更新時に再読み込みボタンを提供することを強く検討する必要があります。なぜなら、更新された Service Worker が制御を開始するまで、その HTML の更新は認識されないためです。
  • ランタイム キャッシュを主に必要としない場合。実行時にリソースをキャッシュに保存する場合、再読み込みの必要があることをユーザーに伝える必要はありません。バージョニングされたアセットが変更されると、ナビゲーション リクエストでネットワーク ファーストまたはネットワークのみの戦略が使用されていることが前提となります。
  • stale-while-revalidate 戦略を使用する場合は、workbox-broadcast-update モジュールを使用して、ユーザーに Service Worker の更新を通知することを検討してください。

Service Worker のアップデートをユーザーに通知する必要があるかどうかは、アプリケーションやその固有の要件によって異なります。新しい Service Worker をプッシュする際に、ユーザーが通常とは異なる動作をしていることに気付いたら、それを通知することをおすすめします。