プログレッシブ ウェブアプリの重要な側面は、信頼性が高いことです。ネットワーク環境が悪い場合でも、アセットをすばやく読み込み、ユーザーの関心を維持し、すぐにフィードバックを提供できます。これは以下のようにして可能になります。サービス ワーカーの 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 オブジェクトを返す必要があります。ただし、非同期呼び出し内など、fetch イベント ハンドラが終了した後に 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 インターフェースを使用してアセットをデバイスに保存します。
キャッシュ ストレージ API を使用して、PWA から受信したリクエストがキャッシュで使用可能かどうかを確認し、使用可能な場合は、そのリクエストで respondWith()
に応答できます。そのためには、まずキャッシュ内を検索する必要があります。トップレベルの caches
インターフェースで使用できる match()
関数は、オリジンのすべてのストアまたは単一の開いているキャッシュ オブジェクトを検索します。
match()
関数は、HTTP リクエストまたは URL を引数として受け取り、対応するキーに関連付けられた Response で解決される 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 アセット用の戦略、API 呼び出し用の戦略、画像とデータ URL 用の戦略をそれぞれ用意できます。これを行うには、ServiceWorkerGlobalScope.onfetch
の event.request.url
を読み取り、正規表現または URL パターンで解析します。(執筆時点では、URL パターンはすべてのプラットフォームでサポートされていません)。
最も一般的な戦略は次のとおりです。
- キャッシュ優先
- 最初にキャッシュに保存されたレスポンスを検索し、見つからない場合はネットワークにフォールバックします。
- ネットワーク ファースト
- 最初にネットワークからレスポンスをリクエストし、レスポンスが返されなかった場合は、キャッシュでレスポンスを確認します。
- Stale While Revalidate
- キャッシュからレスポンスを配信し、バックグラウンドで最新バージョンをリクエストして、次回アセットがリクエストされたときに備えてキャッシュに保存します。
- ネットワークのみ
- 常にネットワークからのレスポンスを返すか、エラーを返します。キャッシュは参照されません。
- キャッシュのみ
- 常にキャッシュからのレスポンスを返信するか、エラーを返します。ネットワークが参照されることはありません。この戦略を使用して配信されるアセットは、リクエストされる前にキャッシュに追加する必要があります。
キャッシュ優先
この戦略を使用すると、Service Worker はキャッシュ内で一致するリクエストを探し、キャッシュに保存されている場合は対応する Response を返します。それ以外の場合は、ネットワークからレスポンスを取得します(必要に応じて、今後の呼び出しのためにキャッシュを更新します)。キャッシュ レスポンスもネットワーク レスポンスもない場合、リクエストはエラーになります。ネットワークにアクセスせずにアセットを配信する方が高速になる傾向があるため、この戦略では鮮度よりもパフォーマンスが優先されます。
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);
}
)
)
});
ネットワーク ファースト
この戦略は Cache First 戦略のミラーです。リクエストをネットワークから処理できるかどうかを確認し、処理できない場合はキャッシュから取得しようとします。キャッシュ ファーストなど。ネットワーク レスポンスもキャッシュ レスポンスもない場合、リクエストはエラーになります。通常、ネットワークからレスポンスを取得する方がキャッシュから取得するよりも遅いため、この戦略ではパフォーマンスよりも更新されたコンテンツが優先されます。
self.addEventListener("fetch", event => {
event.respondWith(
fetch(event.request)
.catch(error => {
return caches.match(event.request) ;
})
);
});
再検証中に古いコンテンツを配信する
stale while revalidate 戦略は、キャッシュに保存されたレスポンスをすぐに返し、ネットワークで更新を確認して、更新が見つかった場合はキャッシュに保存されたレスポンスを置き換えます。この戦略では、キャッシュに保存されたリソースが見つかった場合でも、ネットワークから受信したものでキャッシュの内容を更新し、次のリクエストで更新されたバージョンを使用しようとするため、常にネットワーク リクエストが行われます。したがって、この戦略では、キャッシュ優先戦略の迅速なサービングのメリットを享受しつつ、バックグラウンドでキャッシュを更新できます。
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 のキャッシュに保存されたアセットを最新の状態に保つのは難しい場合があります。stale-while-revalidate 戦略は、そのための 1 つの方法ですが、唯一の方法ではありません。更新の章では、アプリのコンテンツとアセットを最新の状態に保つためのさまざまな手法について学習します。