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

tl;dr

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 であり、Service Worker 内で実行される追加のコードを読み込む 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 分)以内の更新はネットワークに送信されないため、ユーザーは最新バージョンの Service Worker を受信できない可能性があります。ただし、max-age が 86,400(24 時間)を上回った場合は、ユーザーが特定のバージョンに永続的に留まることを避けるため、86,400 の場合と同様に扱われます。

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

updateViaCache

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

これらの値により、更新された Service Worker リソースをチェックする HTTP リクエストを行うときに、ブラウザの標準 HTTP キャッシュが動作するかどうか、またどのように動作するかが決まります。

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

  • 'all' に設定すると、最上位の /service-worker.js スクリプトと、Service Worker 内にインポートされたスクリプト(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 スクリプトのコンテンツが変更されるという副作用があります。それによって、更新フローがトリガーされます。

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

インポートされたスクリプトの更新チェックで、Service Worker が以前に保存したものと 1 バイトごとの差異が生じると、最上位の Service Worker ファイルが同じでも、Service Worker の完全な更新フローがトリガーされます。

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

デベロッパーが行うべきこと

Cache-Control: max-age=0(または同様の値)を指定して /service-worker.js スクリプトの HTTP キャッシュを実質的に無効にした場合は、新しいデフォルト動作による変化はありません。

意図的に、またはホスティング環境のデフォルトであるという理由で、HTTP キャッシュを有効にして /service-worker.js スクリプトを提供すると、サーバーに対して /service-worker.js に対する追加の HTTP リクエストが増加することがあります。これは、以前は HTTP キャッシュによって処理されていたリクエストです。引き続き Cache-Control ヘッダー値が /service-worker.js の鮮度に影響を与えるようにするには、Service Worker の登録時に updateViaCache: 'all' を明示的に設定する必要があります。

古いバージョンのブラウザを使用しているロングテールが存在する可能性があるため、新しいブラウザでは無視される可能性があるとしても、Service Worker スクリプトに Cache-Control: max-age=0 HTTP ヘッダーを設定し続けることをおすすめします。

デベロッパーはこの機会を利用して、インポートしたスクリプトで HTTP キャッシュから明示的にオプトアウトするかどうかを指定し、必要に応じて updateViaCache: 'none' を Service Worker 登録に追加できます。

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

Chrome 78 以降では、importScripts() を介して読み込まれたリソースに対する HTTP 受信リクエストが多くなる可能性があります。これは、更新の有無がチェックされるためです。

このような追加の HTTP トラフィックを回避するには、URL に semver または hash を含むスクリプトを提供する際に、有効期間の長い Cache-Control ヘッダーを設定し、'imports' のデフォルトの updateViaCache 動作を使用します。

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

参考資料

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