Service Worker のスクリプトを作成する

この Codelab は、Google Developers トレーニング チームが開発した「プログレッシブ ウェブアプリの開発」トレーニング コースの一部です。Codelab を順番に進めると、このコースを最大限に活用できます。

コースの詳細については、プログレッシブ ウェブアプリの開発の概要をご覧ください。

はじめに

このラボでは、簡単なサービス ワーカーを作成する手順を説明し、サービス ワーカーのライフサイクルについて説明します。

学習内容

  • 基本的なサービス ワーカー スクリプトを作成してインストールし、簡単なデバッグを行う

必要な予備知識

  • 基本的な JavaScript と HTML
  • ES2015 の Promise のコンセプトと基本的な構文
  • デベロッパー コンソールを有効にする方法

作業を始める前に、以下の情報をご用意ください。

GitHub から pwa-training-labs リポジトリをダウンロードするか、クローンを作成し、必要に応じて Node.js の LTS バージョンをインストールします。

service-worker-lab/app/ ディレクトリに移動して、ローカル開発用サーバーを起動します。

cd service-worker-lab/app
npm install
node server.js

サーバーは Ctrl-c でいつでも終了できます。

ブラウザを開き、localhost:8081/ に移動します。

注: ラボに干渉しないように、Service Worker の登録を解除し、localhost の Service Worker キャッシュをすべてクリアします。Chrome DevTools でこれを行うには、[アプリケーション] タブの [ストレージを消去] セクションで [サイトデータを消去] をクリックします。

任意のテキスト エディタで service-worker-lab/app/ フォルダを開きます。app/ フォルダは、ラボを構築する場所です。

このフォルダには次のものが含まれています。

  • below/another.htmljs/another.jsjs/other.jsother.html は、サービス ワーカーのスコープを試すために使用するサンプル リソースです。
  • styles/ フォルダには、このラボのカスケード スタイルシートが含まれています。
  • test/ フォルダには、進捗状況をテストするためのファイルが含まれています
  • index.html は、サンプルサイト/アプリケーションのメインの HTML ページです。
  • service-worker.js は、サービス ワーカーの作成に使用される JavaScript ファイルです。
  • package.jsonpackage-lock.json は、このプロジェクトで使用されるノード パッケージを追跡します。
  • server.js は、アプリのホストに使用するシンプルな Express サーバーです。

テキスト エディタで service-worker.js を開きます。ファイルが空であることに注意してください。サービス ワーカー内で実行するコードはまだ追加していません。

テキスト エディタで index.html を開きます。

<script> タグ内に、次のコードを追加してサービス ワーカーを登録します。

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('service-worker.js')
    .then(registration => {
      console.log('Service Worker is registered', registration);
    })
    .catch(err => {
      console.error('Registration failed:', err);
    });
  });
}

スクリプトを保存してページを更新します。コンソールに、サービス ワーカーが登録されたことを示すメッセージが返されます。Chrome では、DevTools(Windows と Linux では Ctrl+Shift+I、Mac では ⌘+alt+I)を開き、[Application] タブをクリックしてから [Service Workers] オプションをクリックすることで、サービス ワーカーが登録されていることを確認できます。出力は次のようになります。

省略可: サポート対象外のブラウザでサイトを開き、サポート チェック条件が機能することを確認します。

説明

上記のコードは、service-worker.js ファイルをサービス ワーカーとして登録します。まず、ブラウザがサービス ワーカーをサポートしているかどうかを確認します。一部のブラウザでは Service Worker がサポートされていない可能性があるため、Service Worker を登録するたびにこれを行う必要があります。次に、コードはウィンドウの Navigator インターフェースに含まれる ServiceWorkerContainer APIregister メソッドを使用して、サービス ワーカーを登録します。

navigator.serviceWorker.register(...) は、サービス ワーカーが正常に登録されると registration オブジェクトで解決される Promise を返します。登録が失敗した場合、Promise は拒否されます。

Service Worker のステータスの変更により、Service Worker でイベントがトリガーされます。

イベント リスナーを追加する

テキスト エディタで service-worker.js を開きます。

サービス ワーカーに次のイベント リスナーを追加します。

self.addEventListener('install', event => {
  console.log('Service worker installing...');
  // Add a call to skipWaiting here
});

self.addEventListener('activate', event => {
  console.log('Service worker activating...');
});

ファイルを保存します。

Service Worker を手動で登録解除し、ページを更新して、更新された Service Worker をインストールして有効にします。コンソールログには、新しいサービス ワーカーが登録、インストール、有効化されたことが示されます。

注: 登録ログは、他のログ(インストールとアクティベーション)と順序が異なる場合があります。サービス ワーカーはページと同時に実行されるため、ログの順序を保証することはできません(登録ログはページから、インストールとアクティベーションのログはサービス ワーカーから取得されます)。インストール、アクティベーション、その他の Service Worker イベントは、Service Worker 内で定義された順序で発生し、常に想定どおりの順序で表示されます。

説明

サービス ワーカーは、登録の最後に install イベントを発行します。上記のコードでは、install イベント リスナー内でメッセージがログに記録されますが、実際のアプリでは、静的アセットをキャッシュに保存するのに適した場所です。

Service Worker が登録されると、ブラウザは Service Worker が新しいかどうかを検出します(以前にインストールされた Service Worker と異なる場合、またはこのサイトに登録された Service Worker がない場合)。Service Worker が新しい場合(このケースのように)、ブラウザは Service Worker をインストールします。

Service Worker は、ページを制御するときに activate イベントを発行します。上記のコードではメッセージがログに記録されますが、このイベントはキャッシュの更新によく使用されます。

特定のスコープで一度にアクティブにできるサービス ワーカーは 1 つだけです(サービス ワーカーのスコープを参照)。そのため、新しくインストールされたサービス ワーカーは、既存のサービス ワーカーが使用されなくなるまでアクティブになりません。そのため、新しい Service Worker が引き継ぐ前に、Service Worker によって制御されているすべてのページを閉じる必要があります。既存の Service Worker の登録を解除したため、新しい Service Worker がすぐに有効になりました。

注: ページを更新するだけでは、新しい Service Worker に制御を移すことはできません。新しいページは現在のページがアンロードされる前にリクエストされるため、古い Service Worker が使用されていない時間がないからです。

注: 一部のブラウザのデベロッパー ツールを使用して、または skipWaiting() を使用してプログラムで、新しいサービス ワーカーを手動で有効にすることもできます。これについては、セクション 3.4 で説明します。

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

service-worker.js の任意の場所に次のコメントを追加します。

// I'm a new service worker

ファイルを保存してページを更新します。コンソールのログを確認します。新しいサービス ワーカーはインストールされますが、有効にはなりません。Chrome では、DevTools の [Application] タブで待機中のサービス ワーカーを確認できます。

サービス ワーカーに関連付けられているすべてのページを閉じます。その後、localhost:8081/ を再度開きます。コンソール ログに、新しいサービス ワーカーがアクティブになったことが示されます。

注: 予期しない結果が表示される場合は、デベロッパー ツールで HTTP キャッシュが無効になっていることを確認してください。

説明

ブラウザは、新しいサービス ワーカー ファイルと既存のサービス ワーカー ファイルの間にバイト単位の差(コメントが追加されたため)があることを検出し、新しいサービス ワーカーがインストールされます。一度にアクティブにできるサービス ワーカーは 1 つだけなので(特定のスコープの場合)、新しいサービス ワーカーがインストールされても、既存のサービス ワーカーが使用されなくなるまでアクティブになりません。古いサービス ワーカーの制御下にあるすべてのページを閉じることで、新しいサービス ワーカーを有効にできます。

待機フェーズをスキップする

既存のサービス ワーカーが存在する場合でも、待機フェーズをスキップすることで、新しいサービス ワーカーをすぐに有効にできます。

service-worker.js で、install イベント リスナーに skipWaiting の呼び出しを追加します。

self.skipWaiting();

ファイルを保存してページを更新します。以前のサービス ワーカーが制御していた場合でも、新しいサービス ワーカーがすぐにインストールされて有効になることに注意してください。

説明

skipWaiting() メソッドを使用すると、Service Worker はインストールが完了するとすぐに有効になります。インストール イベント リスナーは skipWaiting() 呼び出しを配置する一般的な場所ですが、待機フェーズ中または待機フェーズの前に呼び出すこともできます。skipWaiting() をいつどのように使用するかについては、こちらのドキュメントをご覧ください。以降のラボでは、サービス ワーカーを手動で登録解除することなく、新しいサービス ワーカー コードをテストできます。

詳細情報

Service Worker は、ウェブアプリとネットワークの間のプロキシとして機能します。

ドメインからのリクエストをインターセプトする fetch リスナーを追加しましょう。

service-worker.js に次のコードを追加します。

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

スクリプトを保存し、ページを更新して、更新されたサービス ワーカーをインストールして有効にします。

コンソールを確認し、フェッチ イベントが記録されていないことを確認します。ページを更新して、コンソールをもう一度確認します。今回は、ページとそのアセット(CSS など)の fetch イベントが表示されます。

[Other page]、[Another page]、[Back] のリンクをクリックします。

各ページとそのアセットのフェッチ イベントがコンソールに表示されます。すべてのログが理にかなっているか。

注: ページにアクセスしたときに HTTP キャッシュが無効になっていない場合、CSS アセットと JavaScript アセットがローカルにキャッシュに保存されることがあります。この場合、これらのリソースのフェッチ イベントは表示されません。

説明

サービス ワーカーは、スコープ内のブラウザによって行われたすべての HTTP リクエストに対して fetch イベントを受け取ります。フェッチ イベント オブジェクトにはリクエストが含まれています。サービス ワーカーで fetch イベントをリッスンすることは、DOM でクリック イベントをリッスンすることに似ています。このコードでは、フェッチ イベントが発生すると、リクエストされた URL がコンソールに記録されます(実際には、任意のリソースを含む独自のカスタム レスポンスを作成して返すこともできます)。

最初の更新でフェッチ イベントがログに記録されなかったのはなぜですか?デフォルトでは、ページ リクエスト自体が Service Worker を通過しない限り、ページの fetch イベントは Service Worker を通過しません。これにより、サイトの一貫性が確保されます。Service Worker なしでページが読み込まれると、サブリソースも読み込まれます。

詳細情報

解答コード

作業コードのコピーを取得するには、04-intercepting-network-requests/ フォルダに移動します。

サービス ワーカーにはスコープがあります。サービス ワーカーのスコープによって、サービス ワーカーがリクエストをインターセプトするパスが決まります。

スコープを見つける

index.html の登録コードを次のように更新します。

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('service-worker.js')
    .then(registration => {
      console.log('SW registered with scope:', registration.scope);
    })
    .catch(err => {
      console.error('Registration failed:', err);
    });
  });
}

ブラウザを更新します。コンソールに Service Worker のスコープ(この場合は http://localhost:8081/)が表示されていることに注目してください。

説明

register() から返される Promise は、サービス ワーカーのスコープを含む登録オブジェクトに解決されます。

デフォルトのスコープはサービス ワーカー ファイルのパスで、すべての下位ディレクトリに拡張されます。そのため、アプリのルート ディレクトリにあるサービス ワーカーは、アプリ内のすべてのファイルからのリクエストを制御します。

Service Worker を移動する

service-worker.jsbelow/ ディレクトリに移動し、index.html の登録コードにあるサービス ワーカーの URL を更新します。

ブラウザで現在の Service Worker の登録を解除し、ページを更新します。

コンソールに、サービス ワーカーのスコープが http://localhost:8081/below/ になったことが表示されます。Chrome では、DevTools の [アプリケーション] タブでサービス ワーカーのスコープを確認することもできます。

メインページに戻り、[Other page]、[Another page]、[Back] をクリックします。どのフェッチ リクエストがログに記録されますか?どのデバイスが対象外ですか?

説明

サービス ワーカーのデフォルトのスコープは、サービス ワーカー ファイルのパスです。サービス ワーカー ファイルが below/ にあるため、これがスコープになります。コンソールには another.htmlanother.cssanother.js のフェッチ イベントのみが記録されるようになりました。これらはサービス ワーカーのスコープ内のリソースのみであるためです。

任意のスコープを設定する

サービス ワーカーをプロジェクトのルート ディレクトリ(app/)に戻し、index.html の登録コードでサービス ワーカーの URL を更新します。

MDN のリファレンスを使用して、register() のオプション パラメータでサービス ワーカーのスコープを below/ ディレクトリに設定します。

Service Worker の登録を解除して、ページを更新します。[Other page]、[Another page]、[Back] をクリックします。

コンソールに、サービス ワーカーのスコープが http://localhost:8081/below/ になり、another.htmlanother.cssanother.js のフェッチ イベントのみがログに記録されることが示されます。

説明

登録時に追加のパラメータを渡すことで、任意のスコープを設定できます。

navigator.serviceWorker.register('/service-worker.js', {
  scope: '/kitten/'
});

上記の例では、サービス ワーカーのスコープが /kitten/ に設定されています。Service Worker は /kitten//kitten/lower/ のページからのリクエストはインターセプトしますが、/kitten/ などのページからのリクエストはインターセプトしません。

注: サービス ワーカーの実際のロケーションよりも上位の任意のスコープを設定することはできません。ただし、Service-Worker-Allowed ヘッダーで提供されるクライアントでサーバー ワーカーがアクティブな場合は、サービス ワーカーの場所の上にそのサービス ワーカーの最大スコープを指定できます。

詳細情報

解答コード

作業コードのコピーを取得するには、solution/ フォルダに移動します。

これで、簡単なサービス ワーカーが起動し、サービス ワーカーのライフサイクルを理解できました。

詳細情報

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

PWA トレーニング コースのすべての Codelab を確認するには、コースのウェルカム Codelab をご覧ください。