キャッシュ ストレージは強力なツールです。アプリがネットワーク状態に依存しにくくなります。キャッシュを有効活用することで、ウェブアプリをオフラインで使用可能にして、どのようなネットワーク状態でもできるだけ早くアセットを提供できます。アセットとデータで説明したように、必要なアセットをキャッシュに保存するための最適な戦略を決定できます。キャッシュを管理するために、Service Worker は Cache Storage API を使用します。
Cache Storage API は、さまざまなコンテキストから使用できます。
- ウィンドウ コンテキスト(PWA のメインスレッド)。
- Service Worker。
- 使用する他のワーカー。
Service Worker を使用してキャッシュを管理する利点の 1 つは、ライフサイクルがウィンドウに関連付けられていないため、メインスレッドをブロックしないことです。Cache Storage API を使用するには、これらのコンテキストのほとんどが TLS 接続下でなければならないことに注意してください。
キャッシュに保存する内容
キャッシュについて最初に考えるべきことは、何をキャッシュに保存するかということです。この質問に対する唯一の答えはありませんが、ユーザー インターフェースのレンダリングに最低限必要なすべてのリソースから始めることができます。
これらのリソースには、以下を含める必要があります。
- メインページの HTML(アプリの start_url)。
- メインのユーザー インターフェースに必要な CSS スタイルシート
- ユーザー インターフェースで使用される画像。
- ユーザー インターフェースのレンダリングに必要な JavaScript ファイル。
- 基本的なエクスペリエンスをレンダリングするために必要な JSON ファイルなどのデータ。
- ウェブフォント。
- マルチページ アプリケーションで、高速またはオフラインで配信するその他の HTML ドキュメント。
オフライン対応
プログレッシブ ウェブアプリの要件の 1 つはオフライン対応ですが、クラウドゲーム ソリューションや暗号資産アプリなど、すべての PWA が完全なオフライン エクスペリエンスを必要としないことを理解しておくことが重要です。したがって、そのような状況においてユーザーを案内する基本的なユーザー インターフェースを提供しても問題ありません。
ウェブ レンダリング エンジンがページを読み込めなかったことを示すブラウザのエラー メッセージを PWA で表示しないようにする必要があります。代わりに、Service Worker を使用して独自のメッセージを表示することで、一般的でわかりにくいブラウザ エラーを回避できます。
PWA のニーズに応じて、さまざまなキャッシュ戦略があります。そのため、高速で信頼性の高いエクスペリエンスを提供するようにキャッシュの使用方法を設計することが重要です。たとえば、すべてのアプリアセットが高速でダウンロードされ、スペースをあまり消費せず、リクエストごとに更新する必要がない場合は、すべてのアセットをキャッシュに保存することが有効な戦略となります。一方、最新バージョンにする必要があるリソースがある場合は、そのアセットをまったくキャッシュに保存しないことを検討できます。
API の使用
Cache Storage API を使用して、送信元内の一連のキャッシュを定義します。各キャッシュは、定義可能な文字列名で識別されます。caches
オブジェクトを使用して API にアクセスすると、open
メソッドにより、作成済みのキャッシュの作成または表示が可能になります。open メソッドは、キャッシュ オブジェクトに対する Promise を返します。
caches.open("pwa-assets")
.then(cache => {
// you can download and store, delete or update resources with cache arguments
});
アセットのダウンロードと保存
アセットをダウンロードして保存するようブラウザに指示するには、add
または addAll
メソッドを使用します。add
メソッドはリクエストを実行して 1 つの HTTP レスポンスを保存し、addAll
はリクエストまたは URL の配列に基づくトランザクションとしての HTTP レスポンスのグループを保管します。
caches.open("pwa-assets")
.then(cache => {
cache.add("styles.css"); // it stores only one resource
cache.addAll(["styles.css", "app.js"]); // it stores two resources
});
キャッシュ ストレージ インターフェースには、すべてのヘッダーと本文を含むレスポンス全体が保存されます。そのため、後で HTTP リクエストまたは URL をキーとして使用して、その URL を取得できます。方法については、サービングの章で説明します。
キャッシュに保存するタイミング
PWA では、ファイルをキャッシュに保存するタイミングを決定する必要があります。Service Worker のインストール時にできるだけ多くのアセットを保存するのも 1 つの方法ですが、通常は最適な方法ではありません。不要なリソースをキャッシュすると、帯域幅と保存容量が浪費され、アプリから意図しない古いリソースが提供される可能性があります。
すべてのアセットを一度にキャッシュに保存する必要はありません。次のように、PWA のライフサイクル中にアセットを何度でもキャッシュに保存できます。
- Service Worker のインストール時。
- 最初のページの読み込み後
- ユーザーがセクションやルートに移動したとき。
- ネットワークがアイドル状態のとき。
新しいファイルのキャッシュ保存は、メインスレッドまたは Service Worker コンテキスト内でリクエストできます。
Service Worker でのアセットのキャッシュ保存
最も一般的なシナリオの 1 つは、Service Worker のインストール時に最小限のアセットをキャッシュに保存することです。これを行うには、Service Worker の install
イベント内でキャッシュ ストレージ インターフェースを使用します。
Service Worker スレッドはいつでも停止できるため、addAll
Promise が完了するまで待つようブラウザにリクエストすることで、すべてのアセットを保存し、アプリの一貫性を保つ機会を増やすことができます。次の例は、Service Worker のイベント リスナーで受け取ったイベント引数の waitUntil
メソッドを使用して、これを行う方法を示しています。
const urlsToCache = ["/", "app.js", "styles.css", "logo.svg"];
self.addEventListener("install", event => {
event.waitUntil(
caches.open("pwa-assets")
.then(cache => {
return cache.addAll(urlsToCache);
});
);
});
waitUntil()
メソッドは Promise を受け取り、ブラウザに Promise のタスクが解決される(完了するか失敗する)まで待機してから、Service Worker プロセスを終了します。1 つの結果が waitUntil()
メソッドに到達するように、Promise を連結して add()
または addAll()
の呼び出しを返すことが必要になる場合があります。
async/await 構文を使用して Promise を処理することもできます。その場合は、次の例に示すように、await
を呼び出すことができ、呼び出された後に waitUntil()
に Promise を返す非同期関数を作成する必要があります。
const urlsToCache = ["/", "app.js", "styles.css", "logo.svg"];
self.addEventListener("install", (event) => {
let cacheUrls = async () => {
const cache = await caches.open("pwa-assets");
return cache.addAll(urlsToCache);
};
event.waitUntil(cacheUrls());
});
クロスドメイン リクエストと不透明レスポンス
PWA は、送信元とクロスドメインからアセット(サードパーティの CDN のコンテンツなど)をダウンロードしてキャッシュに保存できます。クロスドメイン アプリの場合、キャッシュの操作は同一オリジンのリクエストと非常によく似ています。リクエストが実行され、レスポンスのコピーがキャッシュに保存されます。他のキャッシュに保存されたアセットと同様に、これはアプリの送信元でのみ使用できます。
アセットは不透明レスポンスとして保存されます。つまり、コードでそのレスポンスのコンテンツやヘッダーを参照したり変更したりすることはできません。また、不透明なレスポンスは Storage API に実際のサイズを公開しないため、割り当てに影響が及びます。一部のブラウザでは、ファイルサイズがわずか 1 KB であっても、7 MB などの大きいサイズが公開されます。
アセットの更新と削除
アセットの更新は cache.put(request, response)
で、削除は delete(request)
で行えます。
詳しくは、キャッシュ オブジェクトのドキュメントをご覧ください。
キャッシュ ストレージのデバッグ
多くのブラウザには、DevTools の [Application] タブ内でキャッシュ ストレージの内容をデバッグする方法が用意されています。ここで、現在のオリジン内のすべてのキャッシュの内容を確認できます。これらのツールについては、ツールとデバッグの章で詳しく説明します。