この Codelab は、Google Developers トレーニング チームが開発した「Developing Progressive Web Apps」トレーニング コースの一部です。Codelab を順番に扱うことで、このコースを最大限に活用できます。
コースの詳細については、プログレッシブ ウェブアプリ開発の概要をご覧ください。
はじめに
このラボでは、単純な Service Worker の作成方法と Service Worker のライフサイクルについて説明します。
学習内容
- 基本的な Service Worker スクリプトを作成してインストールし、簡単なデバッグを行う
必要な予備知識
- 基本的な JavaScript と HTML
- ES2015 Promise のコンセプトと基本的な構文
- デベロッパー コンソールを有効にする方法
始める前に
- ターミナル/シェルにアクセスできるパソコン
- インターネットへの接続
- Service Worker をサポートするブラウザ
- テキスト エディタ
GitHub から pwa-training-labs リポジトリをダウンロードまたは複製し、必要に応じて LTS バージョンの Node.js をインストールします。
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 で [Application] タブの [Clear storage] にある [Clear site data] をクリックして、この処理を行うことができます。
任意のテキスト エディタで service-worker-lab/app/
フォルダを開きます。app/
フォルダは、ラボを作成する場所です。
このフォルダには次のものが含まれます。
below/another.html
、js/another.js
、js/other.js
、other.html
は、Service Worker スコープをテストするために使用するサンプル リソースです。styles/
フォルダには、このラボのカスケード スタイルシートが含まれています。test/
フォルダには、進行状況をテストするためのファイルが含まれています。index.html
は、サンプル サイト/アプリケーションのメインの HTML ページです。service-worker.js
は、Service Worker の作成に使用される JavaScript ファイルですpackage.json
とpackage-lock.json
は、このプロジェクトで使用されるノード パッケージを追跡しますserver.js
は、アプリのホストに使用するシンプルな Express サーバーです。
テキスト エディタで service-worker.js
を開きます。このファイルは空です。Service Worker 内で実行するコードはまだ追加されていません。
テキスト エディタで index.html
を開きます。
<script>
タグ内に次のコードを追加して、Service Worker を登録します。
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);
});
});
}
スクリプトを保存してページを更新します。コンソールに、Service Worker が登録されていることを示すメッセージが表示されます。Chrome では、DevTools(Windows と Linux では Ctrl+Shift+I、Mac では ⌘+alt+I)を開き、[Application] タブをクリックし、[Service Workers] をクリックすることで、Service Worker が登録されていることを確認できます。出力は次のようになります。
省略可: サポートされていないブラウザでサイトを開き、サポート チェックの条件付き機能が機能することを確認します。
説明
上記のコードは、service-worker.js
ファイルを Service Worker として登録します。まず、ブラウザが Service Worker をサポートしているかどうかを確認します。一部のブラウザでは Service Worker がサポートされていない可能性があるため、Service Worker を登録するたびにこの操作を行う必要があります。次に、ウィンドウの Navigator
インターフェースに含まれる ServiceWorkerContainer
API の register
メソッドを使用して、Service Worker を登録します。
navigator.serviceWorker.register(...)
は、Service Worker が正常に登録されると registration
オブジェクトで解決される Promise を返します。登録に失敗した場合、Promise は拒否されます。
Service Worker のステータスが変化すると、Service Worker でイベントがトリガーされます。
イベント リスナーを追加する
テキスト エディタで service-worker.js
を開きます。
次のイベント リスナーを Service Worker に追加します。
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 はページと同時に実行されるため、ログの順序は保証できません(登録ログはページから取得され、インストール ログとアクティベーション ログは 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
イベントを発行します。上記のコードは、メッセージをログに記録しますが、キャッシュを更新するためによく使用されます。
特定のスコープで一度にアクティブにできる Service Worker は 1 つだけです(既存の Service Worker が使用できなくなるまで、新しくインストールされた Service Worker は有効化されません)。このため、新しい Service Worker を引き継ぐ前に、Service Worker で制御されるすべてのページを閉じる必要があります。既存の Service Worker の登録を解除したため、新しい Service Worker がすぐにアクティブになりました。
注: 現在のページがアンロードされる前に新しいページがリクエストされ、古い Service Worker が使用されていないため、ページを更新するだけでは新しい Service Worker に制御を転送することはできません。
注: 一部のブラウザとデベロッパー ツールを使用して、プログラムで skipWaiting()
を使用して新しい Service Worker を手動でアクティブにすることもできます(3.4 章をご覧ください)。
Service Worker を更新する
service-worker.js
内の任意の場所に次のコメントを追加します。
// I'm a new service worker
ファイルを保存してページを更新します。コンソールのログを確認します。新しい Service Worker はインストールされていますが、アクティベートされていません。Chrome の場合、DevTools の [Application] タブで、待機している Service Worker を表示できます。
Service Worker に関連付けられているすべてのページを閉じます。次に、localhost:8081/
を再度開きます。コンソールログには、新しい Service Worker が有効化されていることがわかります。
注: 予期しない結果が発生した場合は、デベロッパー ツールで HTTP キャッシュが無効になっていることを確認してください。
説明
ブラウザで、新しい Service Worker ファイルと既存の Service Worker ファイルの間に(コメントが追加されたために)バイトの違いが検出され、新しい Service Worker がインストールされます。(特定のスコープでは)一度に 1 つの Service Worker のみをアクティブにできるため、新しい Service Worker がインストールされている場合でも、既存の Service Worker が使用できなくなるまでアクティブになりません。古い Service Worker の制御下にあるページをすべて閉じることで、新しい Service Worker を有効化できます。
待機フェーズのスキップ
既存の Service Worker が存在する場合でも、待機フェーズをスキップして、新しい Service Worker をすぐに有効化できます。
service-worker.js
で、install
イベント リスナーに skipWaiting
の呼び出しを追加します。
self.skipWaiting();
ファイルを保存してページを更新します。以前の Service Worker が制御していた場合でも、新しい Service Worker はすぐにインストールされて起動されます。
説明
skipWaiting()
メソッドを使用すると、Service Worker はインストールが完了したらすぐにアクティブ化できます。インストール イベント リスナーは、skipWaiting()
呼び出しを行う一般的な場所ですが、待機フェーズの途中または前に呼び出すことができます。skipWaiting()
を使用するタイミングと方法については、こちらのドキュメントをご覧ください。ラボの残りの部分では、Service Worker を手動で登録解除しなくても、新しい Service Worker のコードをテストできます。
詳細情報
Service Worker は、ウェブアプリとネットワークの間のプロキシとして機能します。
ここで、ドメインからのリクエストをインターセプトするフェッチ リスナーを追加してみましょう。
service-worker.js
に次のコードを追加します。
self.addEventListener('fetch', event => {
console.log('Fetching:', event.request.url);
});
スクリプトを保存してページを更新し、更新された Service Worker をインストールして有効にします。
コンソールで、フェッチ イベントがログに記録されていないことを確認します。ページを更新し、もう一度コンソールを確認します。今回はページとそのアセット(CSS など)の取得イベントが表示されます。
[その他のページ]、[別のページ]、[戻る] のリンクをクリックします。
各ページとそのアセットについて、コンソールにフェッチ イベントが表示されます。すべてのログが合っているか。
注: HTTP キャッシュを無効にしていないページにアクセスしたときに、ローカルでキャッシュに保存された CSS アセットと JavaScript アセットがキャッシュされることがあります。この場合、これらのリソースの取得イベントは表示されません。
説明
Service Worker は、そのスコープ内でブラウザから送信された HTTP リクエストごとにフェッチ イベントを受け取ります。フェッチ イベント オブジェクトにはリクエストが含まれています。Service Worker でフェッチ イベントをリッスンすることは、DOM でクリック イベントをリッスンする場合と同様です。コードでは、フェッチ イベントが発生したときに、リクエストされた URL をコンソールにログ出力します(実際には、任意のリソースを含む独自のカスタム レスポンスを作成して返すこともできます)。
初回更新時に取得イベントが記録されなかったのはなぜですか?デフォルトでは、ページ リクエスト自体が Service Worker を通過しない限り、ページからのフェッチ イベントは Service Worker を通過しません。これにより、サイトの整合性が確保されます。Service Worker なしでページが読み込まれる場合は、そのサブリソースも読み込まれます。
詳細情報
解答コード
実際のコードのコピーを入手するには、04-intercepting-network-requests/
フォルダに移動します。
Service Worker にはスコープがあります。Service Worker のスコープによって、Service Worker がリクエストをインターセプトするパスが決まります。
スコープを見つける
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 ファイルのパスで、すべての下位ディレクトリに適用されます。そのため、アプリのルート ディレクトリにある Service Worker が、アプリ内のすべてのファイルからのリクエストを制御します。
Service Worker を移動する
service-worker.js
を below/
ディレクトリに移動し、index.html
の登録コードで Service Worker URL を更新します。
ブラウザで現在の Service Worker の登録を解除して、ページを更新します。
コンソールには、Service Worker のスコープが http://localhost:8081/below/
と表示されます。Chrome では、DevTools の [Application] タブで Service Worker のスコープを確認することもできます。
メインページに戻り、[その他のページ]、[別のページ]、[戻る] をクリックします。ログに記録されるフェッチ リクエストその他
説明
Service Worker のデフォルト スコープは、Service Worker ファイルのパスです。Service Worker ファイルが below/
にあるため、そのスコープになります。これで、another.html
、another.css
、another.js
のフェッチ イベントのみが Service Worker のスコープ内の唯一のリソースとなるため、コンソールではログ取得イベントがログに記録されるようになりました。
任意のスコープを設定する
Service Worker をプロジェクトのルート ディレクトリ(app/
)に戻し、index.html
の登録コードで Service Worker の URL を更新します。
MDN のリファレンスを使用して、register()
のオプション パラメータを使用して Service Worker のスコープを below/
ディレクトリに設定します。
Service Worker の登録を解除してページを更新します。[その他のページ]、[別のページ]、[戻る] の順にクリックします。
Console に、Service Worker のスコープが http://localhost:8081/below/
に設定されていることが表示され、another.html
、another.css
、another.js
のイベントのみがログに記録されます。
説明
登録時に追加のパラメータを渡すことで、任意のスコープを設定できます。次に例を示します。
navigator.serviceWorker.register('/service-worker.js', {
scope: '/kitten/'
});
上の例では、Service Worker のスコープが /kitten/
に設定されています。Service Worker は、/kitten/
と /kitten/lower/
のページから送信されたリクエストをインターセプトしますが、/kitten
や /
などのページからはインターセプトしません。
注: Service Worker の実際の場所を超える任意のスコープを設定することはできません。ただし、Service-Worker-Allowed
ヘッダーがあるクライアントでサーバー ワーカーが有効になっている場合は、Service Worker の場所の上にその Service Worker の最大スコープを指定できます。
詳細情報
解答コード
実際のコードのコピーを入手するには、solution/
フォルダに移動します。
これで、単純な Service Worker が稼働し、Service Worker のライフサイクルを理解できました。
詳細情報
PWA トレーニング コースのすべての Codelab については、コースのようこそ Codelab をご覧ください。