サービス ワーカーを使用してネットワーク要求を管理する

サービス ワーカーは、Fetch API を使用してネットワーク要求をインターセプト、変更、応答できる特殊な種類の Web ワーカーです。 リソースを格納するために、サービス ワーカーは Cache API にアクセスでき、 IndexedDBなどの非同期クライアント側データ ストアにアクセスできます。

サービス ワーカーは、リソースをローカルにキャッシュすることで PWA を高速化でき、また、ユーザーのデバイスがオフラインであったり、ネットワーク接続が断続的であったりした場合でも、PWA の動作を可能にすることで、PWA の信頼性を高めることもできます。

PWA にサービス ワーカーがある場合、ユーザーが初めて PWA にアクセスすると、サービス ワーカーがインストールされます。 その後、サービス ワーカーはアプリと並行して実行され、アプリが実行されていない場合でも作業を続行できます。

サービス ワーカーは、ネットワーク要求の傍受、変更、および応答を担当します。 サービス ワーカーは、アプリがサーバーからリソースを読み込もうとしたとき、またはアプリがサーバーからデータを取得する要求を送信するときにアラートを受け取ることができます。 この場合、サービス ワーカーは、要求をサーバーに送信するか、要求をインターセプトして代わりにキャッシュから応答を返すかを決定できます。

アプリとネットワークとキャッシュ ストレージの間のサービス ワーカーを示す図

サービス ワーカーを登録する

他の Web ワーカーと同様に、サービス ワーカーは別のファイルに存在する必要があります。 ファイルには、1 つのサービス ワーカーが含まれています。 次のコードに示すように、サービス ワーカーを登録するときにこのファイルを参照します。

if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register("/serviceworker.js");
}

PWA を実行している Web ブラウザーは、サービス ワーカーのさまざまなレベルのサポートを提供できます。 また、PWA が実行されているコンテキストはセキュリティで保護されていない可能性があります。 そのため、サービス ワーカー関連のコードを実行する前に、 navigator.serviceWorker オブジェクトの存在をテストすることをお勧めします。 上記のコードでは、サービス ワーカーは、サイトのルートにある serviceworker.js ファイルを使用して登録されます。

サービス ワーカー ファイルは、サービス ワーカーが管理する最上位のディレクトリに配置してください。 このようなディレクトリは、サービス ワーカーの スコープ と呼ばれます。 前のコードでは、ファイルはアプリのルート ディレクトリに格納され、サービス ワーカーはアプリのドメイン名の下にあるすべてのページを管理します。

サービス ワーカー ファイルが js ディレクトリに格納されている場合、サービス ワーカーのスコープは、 js ディレクトリとサブディレクトリに制限されます。 サービス ワーカーのスコープを減らす必要がない限り、ベスト プラクティスとして、サービス ワーカー ファイルをアプリのルートに配置します。

要求をインターセプトする

サービス ワーカーで使用する主なイベントは、 fetch イベントです。 fetch イベントは、アプリが実行するブラウザーがサービス ワーカーのスコープ内のコンテンツにアクセスしようとするたびに実行されます。

次のコードは、 fetch イベントのリスナーを追加する方法を示しています。

self.addEventListener("fetch", event => {
  console.log("Fetching", event.request);
});

fetch ハンドラー内で、要求がネットワークに送信されるか、キャッシュからプルされるかなどを制御できます。 実行するアプローチは、要求されるリソースの種類、更新される頻度、アプリケーションに固有のその他のビジネス ロジックによって異なる可能性があります。

fetch ハンドラー内で実行できる操作の例をいくつか次に示します。

  • 使用可能な場合は、キャッシュから応答を返します。それ以外の場合は、ネットワーク経由でリソースを要求します。
  • ネットワークからリソースをフェッチし、コピーをキャッシュし、応答を返します。
  • ユーザーがデータを保存する設定を指定できるようにします。
  • 特定のイメージ要求のプレースホルダー イメージを指定します。
  • サービス ワーカーで応答を直接生成します。

サービス ワーカーのライフサイクル

サービス ワーカーのライフサイクルは複数のステップで構成され、各ステップでイベントがトリガーされます。 これらのイベントにリスナーを追加して、アクションを実行するコードを実行できます。 次の一覧は、サービス ワーカーのライフサイクルと関連イベントの概要を示しています。

  1. サービス ワーカーを登録します。

  2. ブラウザーは JavaScript ファイルをダウンロードし、サービス ワーカーをインストールし、 install イベントをトリガーします。 install イベントを使用して、重要で有効期間の長いファイル (CSS ファイル、JavaScript ファイル、ロゴ イメージ、オフライン ページなど) をアプリから事前にキャッシュできます。

    self.addEventListener("install", event => {
      console.log("Install event in progress.");
    });
    
  3. サービス ワーカーがアクティブ化され、 activate イベントがトリガーされます。 古いキャッシュをクリーンアップするには、このイベントを使用します。

    self.addEventListener("activate", event => {
      console.log("Activate event in progress.");
    });
    
  4. サービス ワーカーは、ページが更新されたとき、またはユーザーがサイト上の新しいページに移動したときに実行する準備が整います。 待機せずにサービス ワーカーを実行する場合は、次のように、install イベント中に self.skipWaiting() メソッドを使用します。

    self.addEventListener("install", event => {
      self.skipWaiting();
    });
    
  5. サービス ワーカーが実行され、 fetch イベントをリッスンできるようになりました。

事前キャッシュ リソース

ユーザーが初めてアプリにアクセスすると、サービス ワーカーを定義した場合、アプリのサービス ワーカーがインストールされます。 サービス ワーカー コードで install イベントを使用して、これがいつ発生するかを検出し、アプリで必要なすべての静的リソースをキャッシュします。 スタート ページで必要なアプリの静的リソース (HTML、CSS、JavaScript コードなど) をキャッシュすると、ユーザーのデバイスがオフラインの場合でもアプリを実行できます。

アプリのリソースをキャッシュするには、次に示すように、グローバル caches オブジェクトと cache.addAll メソッドを使用します。

// The name of the cache your app uses.
const CACHE_NAME = "my-app-cache";
// The list of static files your app needs to start.
const PRE_CACHED_RESOURCES = ["/", "styles.css", "app.js"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache all static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

初回インストール後、 install イベントは再実行されないことに注意してください。 サービス ワーカーのコードを更新するには、「 サービス ワーカーを更新する」を参照してください。

これで、 fetch イベントを使用して、ネットワークから再度読み込む代わりに、静的リソースをキャッシュから返すことができます。

self.addEventListener("fetch", event => {
  async function returnCachedResource() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Find the response that was pre-cached during the `install` event.
    const cachedResponse = await cache.match(event.request.url);

    if (cachedResponse) {
      // Return the resource.
      return cachedResponse;
    } else {
      // The resource wasn't found in the cache, so fetch it from the network.
      const fetchResponse = await fetch(event.request.url);
      // Put the response in cache.
      cache.put(event.request.url, fetchResponse.clone());
      // And return the response.
      return fetchResponse.
    }
  }

  event.respondWith(returnCachedResource());
});

簡潔にするために、上記のコード例では、ネットワークからの要求 URL の取得に失敗した場合は処理されません。

カスタム オフライン ページを使用する

アプリで複数の HTML ページを使用する場合、一般的なオフライン シナリオは、ユーザーのデバイスがオフラインのときにページ ナビゲーション要求をカスタム エラー ページにリダイレクトすることです。

// The name of the cache your app uses.
const CACHE_NAME = "my-app-cache";
// The list of static files your app needs to start.
// Note the offline page in this list.
const PRE_CACHED_RESOURCES = ["/", "styles.css", "app.js", "/offline"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache all static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

self.addEventListener("fetch", event => {
  async function navigateOrDisplayOfflinePage() {
    try {
      // Try to load the page from the network.
      const networkResponse = await fetch(event.request);
      return networkResponse;
    } catch (error) {
      // The network call failed, the device is offline.
      const cache = await caches.open(CACHE_NAME);
      const cachedResponse = await cache.match("/offline");
      return cachedResponse;
    }
  }

  // Only call event.respondWith() if this is a navigation request
  // for an HTML page.
  if (event.request.mode === 'navigate') {
    event.respondWith(navigateOrDisplayOfflinePage());
  }
});

サービス ワーカーを更新する

新しいサービス ワーカー バージョンをインストールする

サービス ワーカー コードを変更し、新しいサービス ワーカー ファイルを Web サーバーにデプロイすると、ユーザーのデバイスは新しいサービス ワーカーの使用を徐々に開始します。

ユーザーがアプリのいずれかのページに移動するたびに、アプリを実行しているブラウザーは、サービス ワーカーの新しいバージョンがサーバーで使用できるかどうかを確認します。 ブラウザーは、既存のサービス ワーカーと新しいサービス ワーカーの内容を比較することで、新しいバージョンを検出します。 変更が検出されると、新しいサービス ワーカーがインストールされ (その install イベントがトリガーされます)、新しいサービス ワーカーは、既存のサービス ワーカーがデバイスで使用されるのを待ちます。

実際には、2 人のサービス ワーカーを同時に実行できますが、既存の (元の) サービス ワーカーのみがアプリのネットワーク要求をインターセプトすることを意味します。 アプリが閉じられると、既存のサービス ワーカーの使用が停止します。 次回アプリが開かれると、新しいサービス ワーカーがアクティブになります。 activate イベントがトリガーされ、新しいサービス ワーカーがfetch イベントのインターセプトを開始します。

新しいサービス ワーカーがインストールされたらすぐに、サービス ワーカーの install イベント ハンドラーでself.skipWaiting()を使用して、強制的にアクティブ化できます。

サービス ワーカーの更新方法の詳細については、「web.dev での サービス ワーカーの更新 」を参照してください。

キャッシュされた静的ファイルを更新する

「事前キャッシュ リソース」で説明されているように、CSS スタイルシート ファイルなどの静的リソースを 事前にキャッシュする場合、アプリはキャッシュされたバージョンのファイルのみを使用し、新しいバージョンのダウンロードを試みません。

ユーザーがアプリで使用される静的リソースに対する最新の変更を確実に受け取るようにするには、キャッシュバミングの名前付け規則を使用し、サービス ワーカー コードを更新します。

キャッシュ バスト は、各静的ファイルの名前がバージョンに従って行われることを意味します。 これはさまざまな方法で実現できますが、通常は、ファイルのコンテンツを読み取り、コンテンツに基づいて一意の ID を生成するビルド ツールを使用する必要があります。 その ID を使用して、キャッシュされた静的ファイルに名前を付けることができます。

次に、サービス ワーカー コードを更新して、 install中に新しい静的リソースをキャッシュします。

// The name of the new cache your app uses.
const CACHE_NAME = "my-app-cache-v2";
// The list of static files your app needs to start.
const PRE_CACHED_RESOURCES = ["/", "styles-124656.css", "app-576391.js"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache the new static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

// Listen to the `activate` event to clear old caches.
self.addEventListener("activate", event => {
  async function deleteOldCaches() {
    // List all caches by their names.
    const names = await caches.keys();
    await Promise.all(names.map(name => {
      if (name !== CACHE_NAME) {
        // If a cache's name is the current name, delete it.
        return caches.delete(name);
      }
    }));
  }

  event.waitUntil(deleteOldCaches());
});

上記のコード スニペットと事前キャッシュ リソースの値の間で、CACHE_NAME値とPRE_CACHED_RESOURCES値を比較します。 この新しいサービス ワーカーがインストールされると、新しいキャッシュが作成され、新しい静的リソースがダウンロードされてキャッシュされます。 サービス ワーカーがアクティブ化されると、古いキャッシュが削除されます。 この時点で、ユーザーはアプリの新しいバージョンを持つことになります。

サービス ワーカーに変更を加えるのが複雑になる場合があります。 Workbox などのライブラリを使用して、静的リソースのビルド ステップとサービス ワーカー コードを簡略化します。

PWA 内のネットワーク接続をテストする

データを同期したり、ネットワークの状態が変更されたことをユーザーに通知したりするために、ネットワーク接続が使用可能なタイミングを知ると便利です。

次のオプションを使用して、ネットワーク接続をテストします。

navigator.onLine プロパティは、ネットワークの現在の状態を知ることができるブール値です。 値が trueの場合、ユーザーはオンラインです。それ以外の場合、ユーザーはオフラインです。

詳細については、「MDN での navigator.onLine 」を参照してください。

オンラインイベントとオフラインイベント

ネットワーク接続が変更されたときにアクションを実行できます。 ネットワーク イベントに応答して、リッスンしてアクションを実行できます。 イベントは、次に示すように、 windowdocumentdocument.body の各要素で使用できます。

window.addEventListener("online",  function(){
    console.log("You are online!");
});
window.addEventListener("offline", function(){
    console.log("Network connection lost!");
});

詳細については、「MDN の Navigator.onLine 」を参照してください。

その他の機能

サービス ワーカーの主な責任は、ネットワーク接続が不安定な場合にアプリをより高速かつ信頼性の高くすることです。 サービス ワーカーは通常、 fetch イベントと Cache API を使用してこれを行いますが、サービス ワーカーは、次のような特殊なシナリオに他の API を使用できます。

  • データのバックグラウンド同期。
  • データの定期的な同期。
  • 大きなバックグラウンド ファイルのダウンロード。
  • プッシュ メッセージの処理と通知。

バックグラウンド同期

バックグラウンド同期 API を使用して、ユーザーのデバイスがオフラインの場合でも、ユーザーがアプリを引き続き使用し、アクションを実行できるようにします。

たとえば、電子メール アプリを使用すると、ユーザーはいつでもメッセージを作成して送信できます。 アプリ フロントエンドはメッセージをすぐに送信しようとすることができ、デバイスがオフラインの場合、サービス ワーカーは失敗した要求をキャッチし、バックグラウンド同期 API を使用して、接続されるまでタスクを延期できます。

詳細については、「 バックグラウンド同期 API を使用してデータをサーバーと同期する」を参照してください。

期間バックグラウンド同期

Periodic Background Sync API を使用すると、PWA はバックグラウンドで定期的に新しいコンテンツを取得できるため、ユーザーは後でアプリを再度開いたときにすぐにコンテンツにアクセスできます。

定期的なバックグラウンド同期 API を使用することで、ユーザーがアプリを使用している間、PWA は新しいコンテンツ (新しい記事など) をダウンロードする必要はありません。 コンテンツをダウンロードするとエクスペリエンスが遅くなる可能性があるため、代わりに、アプリはより便利なタイミングでコンテンツを取得できます。

詳細については、「 定期的なバックグラウンド同期 API を使用して新しいコンテンツを定期的に取得する」を参照してください。

大きなバックグラウンド ファイルのダウンロード

バックグラウンド フェッチ API を使用すると、PWA は大量のデータのダウンロードをブラウザー エンジンに完全に委任できます。 これにより、ダウンロードの進行中にアプリとサービス ワーカーをまったく実行する必要はありません。

この API は、ユーザーがオフラインのユース ケース用に大きなファイル (音楽、映画、ポッドキャストなど) をダウンロードできるようにするアプリに役立ちます。 ダウンロードはブラウザー エンジンに委任されます。これは、断続的な接続を処理する方法や、接続の完全な損失を処理する方法を認識しています。

詳細については、「 バックグラウンド フェッチ API を使用して、アプリまたはサービス ワーカーが実行されていないときに大きなファイルをフェッチする」を参照してください。

メッセージをプッシュする

プッシュ メッセージは、その時点でアプリを使用しなくても、ユーザーに送信できます。 サービス ワーカーは、アプリが実行されていない場合でもサーバーから送信されたプッシュ メッセージをリッスンし、オペレーティング システムの通知センターに通知を表示できます。

詳細については、「 プッシュ メッセージを使用してユーザーを再エンゲージメントする」を参照してください。

DevTools を使用したデバッグ

Microsoft Edge DevTools を使用すると、サービス ワーカーが正しく登録されているかどうかを確認し、サービス ワーカーが現在どのライフサイクル状態にあるかを確認できます。 また、サービス ワーカーで JavaScript コードをデバッグすることもできます。

詳細については、「 サービス ワーカーをデバッグする」を参照してください。

関連項目