デフォルトでより鮮度の高い Service Worker

要約

Chrome 68 以降、Service Worker スクリプトの更新を確認する HTTP リクエストは、デフォルトで HTTP キャッシュによって実行されなくなります。これは、Service Worker スクリプトに誤って Cache-Control ヘッダーを設定すると更新が遅れるというデベロッパーの一般的な問題を回避できます。

Cache-Control: max-age=0/service-worker.js スクリプトを配信して、すでに HTTP キャッシュをオプトアウトしている場合は、新しいデフォルト動作による変更は発生しません。

また、Chrome 78 以降では、バイト単位の比較が importScripts() を介して Service Worker に読み込まれるスクリプトに適用されます。インポートされたスクリプトに変更を加えると、トップレベルの Service Worker の変更と同様に、Service Worker の更新フローがトリガーされます。

背景

Service Worker のスコープに含まれる新しいページに移動するたびに、JavaScript から registration.update() を明示的に呼び出すか、push イベントまたは sync イベントによって Service Worker が「起動」されたときに、ブラウザは navigator.serviceWorker.register() 呼び出しに最初に渡された JavaScript リソースを並行してリクエストし、Service Worker スクリプトの更新を探します。

この記事では、URL が /service-worker.js で、サービス ワーカー内で実行される追加のコードを読み込む importScripts() への呼び出しが 1 回含まれているとします。

// Inside our /service-worker.js file:
importScripts('path/to/import.js');

// Other top-level code goes here.

変更内容

Chrome 68 より前は、/service-worker.js の更新リクエストは(ほとんどの取得と同様に)HTTP キャッシュを介して行われていました。つまり、スクリプトが最初に Cache-Control: max-age=600 で送信された場合、次の 600 秒(10 分)以内の更新はネットワークに送信されず、ユーザーが最新バージョンのサービス ワーカーを受信できない可能性があります。ただし、max-age が 86400(24 時間)より大きい場合は、ユーザーが特定のバージョンに永続的に固定されないように、86400 として扱われます。

68 以降、Service Worker スクリプトの更新をリクエストする際に HTTP キャッシュが無視されるため、既存のウェブアプリでは Service Worker スクリプトのリクエスト頻度が増加する可能性があります。importScripts のリクエストは引き続き HTTP キャッシュを経由します。ただし、これはデフォルト設定にすぎません。この動作を制御する新しい登録オプション updateViaCache が利用できます。

updateViaCache

デベロッパーは、navigator.serviceWorker.register() を呼び出すときに新しいオプション(updateViaCache パラメータ)を渡せるようになりました。'imports''all''none' のいずれかの値を指定します。

この値は、更新された Service Worker リソースを確認する HTTP リクエストを実行する際に、ブラウザの標準の HTTP キャッシュが使用されるかどうか、またどのように使用されるかを決定します。

  • 'imports' に設定すると、/service-worker.js スクリプトの更新を確認するときに HTTP キャッシュは参照されませんが、インポートされたスクリプト(この例では path/to/import.js)を取得するときに参照されます。これはデフォルトであり、Chrome 68 以降の動作と同じです。

  • 'all' に設定すると、トップレベルの /service-worker.js スクリプトと、サービス ワーカー内にインポートされたスクリプト(path/to/import.js など)の両方に対してリクエストを行うときに、HTTP キャッシュが参照されます。このオプションは、Chrome 68 より前の Chrome の以前の動作に対応しています。

  • 'none' に設定すると、トップレベルの /service-worker.js またはインポートされたスクリプト(仮想の path/to/import.js など)のリクエストを行うときに HTTP キャッシュは参照されません。

たとえば、次のコードは Service Worker を登録し、/service-worker.js スクリプトまたは /service-worker.js 内の importScripts() を介して参照されるスクリプトの更新を確認するときに、HTTP キャッシュが参照されないようにします。

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js', {
    updateViaCache: 'none',
    // Optionally, set 'scope' here, if needed.
  });
}

インポートされたスクリプトの更新を確認する

Chrome 78 より前は、importScripts() 経由で読み込まれた Service Worker スクリプトは 1 回だけ取得されていました(updateViaCache の構成に応じて、まず HTTP キャッシュで確認するか、ネットワーク経由で確認します)。最初の取得後、ブラウザによって内部に保存され、再取得されることはありません。

すでにインストールされている Service Worker にインポートされたスクリプトの変更を強制的に反映させる唯一の方法は、スクリプトの URL を変更することでした。通常は、semver 値(例: importScripts('https://example.com/v1.1.0/index.js'))を追加するか、コンテンツのハッシュを含める(例: importScripts('https://example.com/index.abcd1234.js'))ことで変更します。インポートされた URL を変更すると、トップレベルの Service Worker スクリプトの内容が変更され、Service Worker の更新フローがトリガーされます。

Chrome 78 以降では、トップレベルのサービス ワーカー ファイルの更新チェックが実行されるたびに、インポートされたスクリプトの内容が変更されていないかどうかを同時に確認します。使用される Cache-Control ヘッダーに応じて、これらのインポートされたスクリプト チェックは、updateViaCache'all' または 'imports'(デフォルト値)に設定されている場合は HTTP キャッシュによって実行される場合があります。また、updateViaCache'none' に設定されている場合は、ネットワークに対して直接実行される場合があります。

インポートされたスクリプトの更新チェックで、Service Worker によって以前に保存されたものとバイト単位で差異が検出されると、トップレベルの Service Worker ファイルが同じままでも、Service Worker の完全な更新フローがトリガーされます。

Chrome 78 の動作は、Firefox が数年前の Firefox 56 で実装したものと一致します。Safari では、すでにこの動作が実装されています。

デベロッパーが行う必要があることは何ですか?

Cache-Control: max-age=0(または同様の値)で /service-worker.js スクリプトを配信することで、/service-worker.js スクリプトの HTTP キャッシュを事実上無効にしている場合、新しいデフォルト動作による変更は発生しません。

意図的に、またはホスティング環境のデフォルトであるために HTTP キャッシュを有効にして /service-worker.js スクリプトを配信している場合、サーバーに送信される /service-worker.js の追加の HTTP リクエストが増加する可能性があります。これは、以前は HTTP キャッシュによって処理されていたリクエストです。Cache-Control ヘッダー値が /service-worker.js の更新頻度に影響を与え続けるようにするには、サービス ワーカーの登録時に updateViaCache: 'all' を明示的に設定する必要があります。

古いバージョンのブラウザを使用しているユーザーが長く残る可能性があることを考えると、新しいブラウザでは無視される可能性がありますが、サービス ワーカー スクリプトに Cache-Control: max-age=0 HTTP ヘッダーを設定し続けることをおすすめします。

デベロッパーは、この機会に、インポートされたスクリプトを HTTP キャッシュから明示的に除外するかどうかを決定し、必要に応じてサービス ワーカーの登録に updateViaCache: 'none' を追加できます。

インポートされたスクリプトの提供

Chrome 78 以降、importScripts() 経由で読み込まれるリソースのアップデートが確認されるため、リソースの HTTP リクエストが増加する可能性があります。

この追加の HTTP トラフィックを回避するには、URL に semver またはハッシュを含むスクリプトを配信するときに、長時間の Cache-Control ヘッダーを設定し、'imports' のデフォルトの updateViaCache 動作に依存します。

インポートされたスクリプトの頻繁な更新を確認したい場合は、Cache-Control: max-age=0 でスクリプトを提供するか、updateViaCache: 'none' を使用するようにしてください。

関連情報

Jake Archibald による「The Service Worker Lifecycle」と「Caching best practices & max-age gotchas」は、ウェブに何かをデプロイするすべてのデベロッパーにおすすめの読み物です。