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 ファイルに以下のコードが自動的に含まれているため、そのまま問題ありません。

injectManifest モードの Workbox のビルドツールのいずれかと組み合わせて独自の 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 の更新時に再読み込みボタンを提供することを強く検討してください。HTML の更新は、更新された Service Worker が制御するまで認識されないためです。
  • ランタイム キャッシュをほとんど使用しない場合。実行時にリソースをキャッシュに保存する場合は、再読み込みする必要があることをユーザーに知らせる必要はありません。バージョニングされたアセットが変更されると、ナビゲーション リクエストでネットワーク ファーストまたはネットワークのみの戦略が使用されていると仮定して、時間の経過とともにランタイム キャッシュに追加されます。
  • stale-while-revalidate 戦略を使用する場合、workbox-broadcast-update モジュールを使用して、ユーザーに Service Worker の更新を通知することもできます。

Service Worker のアップデートをユーザーに通知する必要があるかどうかは、アプリケーションとその要件によって異なります。新しい Service Worker をプッシュする際に通常とは異なる動作がユーザーで発生することがわかったら、それがユーザーに通知すべき合図になるでしょう。