Service Worker を使用してネットワーク要求を管理する

Service Worker は、API を使用してネットワーク要求をインターセプト、変更、および応答できる特殊な種類の FetchWeb Worker です。 Service Worker は、API や非同期クライアント側データ ストア (などIndexedDB) にアクセスCacheしてリソースを格納できます。

Service Worker は、リソースをローカルにキャッシュすることで PWA をより高速にし、ネットワークに依存しない PWA を作成することで、PWA の信頼性を高めることもできます。

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

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

アプリとネットワークとキャッシュ ストレージの間に Service Worker が存在することを示すアーキテクチャの概要図

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

他の Web Worker と同様に、Service Worker は別のファイルに存在する必要があります。 次のコードに示すように、Service Worker を登録するときにこのファイルを参照します。

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

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

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

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

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

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

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

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

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

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

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

Service Worker ライフサイクル

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

  1. Service Worker を登録します。

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

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

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

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

事前キャッシュ リソース

ユーザーが初めてアプリにアクセスすると、アプリの Service Worker がインストールされます。 Service Worker のイベントを 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 注意してください。 Service Worker のコードを更新するには、「 Service Worker の更新」を参照してください。

これで、 イベントを 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());
  }
});

Service Worker を更新する

新しい Service Worker バージョンをインストールする

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

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

実際には、2 つの Service Worker を同時に実行できますが、アプリのネットワーク要求をインターセプトするのは 1 つだけであることを意味します。 アプリが閉じられると、既存の Service Worker の使用が停止します。 次回アプリが開かれると、新しい Service Worker がアクティブになります。 activateイベントがトリガーされ、新しい Service Worker によってイベントのインターセプトがfetch開始されます。

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

Service Worker の更新方法の詳細については、「web.dev での Service Worker の更新 」を参照してください。

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

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

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

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

次に、 の間に新しい静的リソースをキャッシュするように Service Worker コードを 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 値を比較します。 この新しい Service Worker がインストールされると、新しいキャッシュが作成され、新しい静的リソースがダウンロードされてキャッシュされます。 Service Worker がアクティブ化されると、古いキャッシュが削除されます。 この時点で、ユーザーはアプリの新しいバージョンを持つことになります。

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

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

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

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

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

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

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

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

window.addEventListener("online",  function(){
    console.log("You are online!");
});
window.addEventListener("offline", function(){
    console.log("Oh no, you lost your network connection.");
});

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

その他の機能

Service Worker のメインの責任は、ネットワーク接続が不安定な場合にアプリをより迅速かつ信頼性の高くすることです。 Service Worker は、ほとんどの場合、 fetch イベントと Cache API を使用してこれを行いますが、次のような高度なシナリオでは他の API を使用できます。

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

バックグラウンド同期

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

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

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

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

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

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

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

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

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

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

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

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

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

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

DevTools を使用したデバッグ

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

詳細については、「 Service Worker をデバッグする」を参照してください。

関連項目