プログレッシブ ウェブアプリの重要な要素は、信頼性です。アセットをすばやく読み込み、ネットワークの状態が悪い場合でもユーザーを引き付け、すぐにフィードバックを提供できます。これは以下のようにして可能になります。サービス ワーカーの fetch
イベントにより、
フェッチ イベント
fetch
イベントを使用すると、サービス ワーカーのスコープ内で PWA によって行われたすべてのネットワーク リクエスト(同一オリジンとクロスオリジンの両方のリクエスト)をインターセプトできます。インストール済みのサービス ワーカーから取得することで、ナビゲーション リクエストやアセット リクエストに加えて、サイトの初回読み込み後のページ訪問をネットワーク呼び出しなしでレンダリングできます。
fetch
ハンドラは、URL や HTTP ヘッダーなど、アプリからのすべてのリクエストを受信し、アプリ デベロッパーがリクエストの処理方法を決定できるようにします。
サービス ワーカーは、リクエストをネットワークに転送したり、以前にキャッシュに保存されたレスポンスで応答したり、新しいレスポンスを作成したりできます。選択はお客様次第です。 たとえば、次のような例が考えられます。
self.addEventListener("fetch", event => {
console.log(`URL requested: ${event.request.url}`);
});
リクエストへの応答
サービス ワーカーにリクエストが届いた場合、無視してネットワークに転送するか、応答するかの 2 つの方法があります。サービス ワーカー内からリクエストに応答することで、ユーザーがオフラインの場合でも、PWA に返す内容と方法を選択できます。
受信したリクエストに応答するには、次のように fetch
イベント ハンドラ内から event.respondWith()
を呼び出します。
// fetch event handler in your service worker file
self.addEventListener("fetch", event => {
const response = .... // a response or a Promise of response
event.respondWith(response);
});
respondWith()
は同期的に呼び出す必要があり、Response オブジェクトを返す必要があります。ただし、非同期呼び出し内など、取得イベント ハンドラの完了後に respondWith()
を呼び出すことはできません。レスポンスが完了するまで待つ必要がある場合は、レスポンスで解決する Promise を respondWith()
に渡すことができます。
レスポンスを作成する
Fetch API を使用すると、JavaScript コードで HTTP レスポンスを作成できます。これらのレスポンスは、Cache Storage API を使用してキャッシュに保存し、ウェブサーバーから送信されたものとして返すことができます。
レスポンスを作成するには、新しい Response
オブジェクトを作成し、本文と、ステータスやヘッダーなどのオプションを設定します。
const simpleResponse = new Response("Body of the HTTP response");
const options = {
status: 200,
headers: {
'Content-type': 'text/html'
}
};
const htmlResponse = new Response("<b>HTML</b> content", options)
キャッシュからのレスポンス
サービス ワーカーから HTTP レスポンスを提供する方法を理解したので、Caching Storage インターフェースを使用してデバイスにアセットを保存しましょう。
cache storage API を使用して、PWA から受信したリクエストがキャッシュで利用可能かどうかを確認し、利用可能な場合は、そのリクエストで respondWith()
に応答できます。そのためには、まずキャッシュ内を検索する必要があります。match()
関数は、最上位の caches
インターフェースで使用でき、オリジン内のすべてのストア、または開いている単一のキャッシュ オブジェクトを検索します。
match()
関数は、HTTP リクエストまたは URL を引数として受け取り、対応するキーに関連付けられたレスポンスで解決される Promise を返します。
// Global search on all caches in the current origin
caches.match(urlOrRequest).then(response => {
console.log(response ? response : "It's not in the cache");
});
// Cache-specific search
caches.open("pwa-assets").then(cache => {
cache.match(urlOrRequest).then(response => {
console.log(response ? response : "It's not in the cache");
});
});
キャッシュ戦略
ブラウザ キャッシュからのみファイルを提供する方法は、すべてのユースケースに適しているわけではありません。たとえば、ユーザーやブラウザがキャッシュを強制排除する場合があります。そのため、PWA のアセットを配信するための独自の戦略を定義する必要があります。キャッシュ戦略は 1 つに限定されません。URL パターンごとに異なるものを定義できます。たとえば、最小限の UI アセット用に 1 つの戦略、API 呼び出し用に別の戦略、画像とデータの URL 用に 3 つ目の戦略を用意できます。これを行うには、ServiceWorkerGlobalScope.onfetch
の event.request.url
を読み取り、正規表現または URL パターンで解析します。(執筆時点では、URL パターンはすべてのプラットフォームでサポートされていません)。
最も一般的な戦略は次のとおりです。
- キャッシュを優先
- 最初にキャッシュに保存されたレスポンスを検索し、見つからない場合はネットワークにフォールバックします。
- ネットワーク ファースト
- まずネットワークからレスポンスをリクエストし、レスポンスが返されなかった場合はキャッシュにレスポンスをチェックします。
- 再検証中の古いコンテンツ
- キャッシュからレスポンスを提供します。バックグラウンドで最新バージョンをリクエストし、次回アセットがリクエストされたときにキャッシュに保存します。
- ネットワークのみ
- 常にネットワークからのレスポンスで応答するか、エラーを返します。キャッシュは使用されません。
- キャッシュのみ
- キャッシュからのレスポンスで常に応答するか、エラーを返します。ネットワークにコンサルトされることはありません。この戦略を使用して配信されるアセットは、リクエストされる前にキャッシュに追加する必要があります。
キャッシュを先に保存する
この戦略では、サービス ワーカーはキャッシュ内で一致するリクエストを検索し、キャッシュに保存されている場合は対応するレスポンスを返します。キャッシュにレスポンスがない場合、ネットワークからレスポンスを取得します(必要に応じて、今後の呼び出し用にキャッシュを更新します)。キャッシュ レスポンスもネットワーク レスポンスもない場合は、リクエストはエラーになります。ネットワークにアクセスせずにアセットを提供する方が速い傾向があるため、この戦略では新しさよりもパフォーマンスを優先します。
self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
// It can update the cache to serve updated content on the next request
return cachedResponse || fetch(event.request);
}
)
)
});
ネットワーク ファースト
この戦略はキャッシュ優先戦略のミラーです。ネットワークからリクエストを処理できるかどうかを確認し、できない場合はキャッシュから取得しようとします。キャッシュを優先するなどです。ネットワーク レスポンスもキャッシュ レスポンスもない場合は、リクエストはエラーになります。通常、ネットワークからレスポンスを取得するよりもキャッシュから取得する方が遅くなります。この戦略では、パフォーマンスではなく更新されたコンテンツを優先します。
self.addEventListener("fetch", event => {
event.respondWith(
fetch(event.request)
.catch(error => {
return caches.match(event.request) ;
})
);
});
再検証中に古い
古いデータの再検証戦略では、キャッシュに保存されたレスポンスをすぐに返します。その後、ネットワークで更新を確認して、更新が見つかった場合はキャッシュに保存されたレスポンスを置き換えます。この方法では、キャッシュに保存されたリソースが見つかった場合でも、キャッシュ内のデータをネットワークから受信したデータで更新し、更新されたバージョンを次のリクエストで使用しようとするため、常にネットワーク リクエストが発生します。したがって、この戦略では、キャッシュファースト戦略の迅速なサービングを活用し、バックグラウンドでキャッシュを更新できます。
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
const networkFetch = fetch(event.request).then(response => {
// update the cache with a clone of the network response
const responseClone = response.clone()
caches.open(url.searchParams.get('name')).then(cache => {
cache.put(event.request, responseClone)
})
return response
}).catch(function (reason) {
console.error('ServiceWorker fetch failed: ', reason)
})
// prioritize cached response over network
return cachedResponse || networkFetch
}
)
)
})
ネットワークのみ
ネットワークのみの戦略は、サービス ワーカーや Cache Storage API のないブラウザの動作に似ています。リクエストは、ネットワークから取得できるリソースのみを返します。これは、オンライン専用の API リクエストなどのリソースに役立ちます。
キャッシュのみ
キャッシュのみの戦略では、リクエストがネットワークに送信されることはありません。受信したすべてのリクエストは、事前に入力されたキャッシュ アイテムで応答されます。次のコードは、キャッシュ ストレージの match
メソッドで fetch
イベント ハンドラを使用して、キャッシュのみに応答します。
self.addEventListener("fetch", event => {
event.respondWith(caches.match(event.request));
});
カスタム戦略
上記は一般的なキャッシュ戦略ですが、サービス ワーカーとリクエストの処理方法はご自身で管理します。これらのいずれもニーズに合わない場合は、独自のレポートを作成します。
たとえば、タイムアウト付きのネットワーク優先戦略を使用して、更新されたコンテンツを優先できます。ただし、レスポンスが設定したしきい値内に収まっている場合に限られます。キャッシュに保存されたレスポンスとネットワーク レスポンスを統合し、サービス ワーカーから複雑なレスポンスを作成することもできます。
アセットの更新
PWA のキャッシュに保存されたアセットを最新の状態に保つことは、簡単ではありません。古いデータの再検証戦略は、その方法の一つですが、唯一の方法ではありません。更新に関する章では、アプリのコンテンツとアセットを常に最新の状態に保つためのさまざまな手法について説明します。