サービス ワーカーは強力であり、学ぶ価値が絶対にあります。 サービス ワーカーを使用すると、まったく新しいレベルのエクスペリエンスをユーザーに提供することができます。 サイトは瞬時にロードできます。 オフラインで動作させることもできます。 ネイティブ アプリとしてインストールされ、洗練された感じでありながら、Web のリーチと自由を感じることができます。
Google Developers と私は最近、サービス ワーカーを理解するための無料のゲームである Service Workies というプロジェクトで共同作業を行いました。 このゲームを作成し、サービス ワーカーの複雑な内部と外部を扱う中で、いくつかの問題にぶつかりました。 最も役に立ったのは、描写力のあるメタファーをいくつか思いついたことです。 この投稿では、これらのメンタル モデルを探求し、サービス ワーカーを厄介で素晴らしいものにしている逆説的な特徴について、頭を整理します。 お気に入りの新しい JavaScript 言語機能を使用することができます。 UI イベントと同じようにライフサイクル イベントをリッスンします。
しかし、他のサービス ワーカーの動作は、混乱で頭をかきむしる原因となります。 特に、ページを更新してもコードの変更が適用されていない場合です。
A new layer #
通常、サイトを構築する場合、クライアントとサーバーという 2 つの層について考える必要があります。
サービス ワーカーを、ユーザーのブラウザにインストールできるブラウザ拡張機能のようなものだと考えてください。 いったんインストールされると、サービス ワーカーは強力な中間層でサイトのブラウザを拡張します。 このサービスワーカー層は、サイトが行うすべてのリクエストを傍受し処理できます。
サービスワーカー層は、ブラウザ タブから独立した独自のライフサイクルを持ちます。 単純なページの更新では、サービスワーカーを更新できません。ちょうど、サーバー上に展開されたコードを更新するためにページの更新を期待しないのと同じです。
Service Workies Game では、サービス ワーカーのライフサイクルの多くの詳細について説明し、サービス ワーカーを使用した演習を数多く提供します。
- ユーザーがオフラインのときでも完璧に動作する
- キャッシュによりパフォーマンスが大幅に向上する
- プッシュ通知の使用
- PWA としてインストールする
サービスワーカーができることは多くありますが、デザインによる制約があります。 サービス ワーカーは、サイトと同じスレッドで同期的に何かを行うことはできません。
- localStorage
- the DOM
- the window
良いニュースは、直接 postMessage
、1対1のメッセージ チャネル、1対多数のブロードキャスト チャネルなど、ページとサービス ワーカーが通信できる方法が少しあることです。
Long-lived, but short-lived #
アクティブなサービスワーカーは、ユーザーがサイトを離れたりタブを閉じたりした後でも生き続けることができます。 ブラウザはこのサービス ワーカーを保持し、ユーザーが次にサイトに戻ったときに準備できるようにします。 最初のリクエストが行われる前に、サービスワーカーはそれを傍受し、ページを制御する機会を得ます。 サービスワーカーは、ユーザーがインターネットに接続していなくても、ページ自体のキャッシュされたバージョンを提供できます。
Service Workies では、リクエストを傍受して処理するコロヘ(フレンドリーなサービスワーカー)でこの概念を視覚化しています。 ブラウザは現在何もしていないサービスワーカーにリソースを浪費したくありません。 停止は終了とは違います。サービスワーカーはインストールされ、起動されたままです。 サービスワーカーはインストールされ、起動したままです。 次に必要になったとき (たとえばリクエストを処理するとき)、 ブラウザはそれを再び起こします。
waitUntil #
常にスリープ状態になる可能性があるので、 サービスワーカーは、何か重要なことをしており仮眠する気がないときにブラウザに知らせる方法が必要です。 ここで event.waitUntil()
が登場します。 このメソッドは、使用されているライフサイクルを拡張し、準備が整うまで停止させず、ライフサイクルの次のフェーズに移行させないようにします。
この例では、assets
キャッシュが作成され、剣の画像が入力されるまで、サービス ワーカーのインストールは完了しないとブラウザに伝えています:
self.addEventListener("install", event => {
event.waitUntil(
caches.open("assets").then(cache => {
return cache.addAll();
})
);
});
Watch out for global state #
この開始/停止が起こると、サービス ワーカーのグローバル スコープはリセットされます。
const favoriteNumber = Math.random();
let hasHandledARequest = false;
self.addEventListener("fetch", event => {
console.log(favoriteNumber);
console.log(hasHandledARequest);
hasHandledARequest = true;
});
この例では、グローバル状態を使用しています:
const favoriteNumber = Math.random();
let hasHandledARequest = false;
self.addEventListener("fetch", event => {
console.log(favoriteNumber);
console.log(hasHandledARequest);
hasHandledARequest = true;
});
このサービスワーカーはリクエストごとに番号を記録します。 hasHandledARequest
変数も true
に変化します。 ここでサービスワーカーは少しアイドル状態なので、 ブラウザはそれを停止します。 次にリクエストがあったとき、サービスワーカーは再び必要とされるので、 ブラウザはそれを起こします。 そのスクリプトは再び評価されます。 今度は hasHandledARequest
が false
にリセットされ、favoriteNumber
はまったく別のもの、つまり 0.5907281835659033
になります。
サービス ワーカーの保存状態に依存することはできません。 また、メッセージ チャネルのようなもののインスタンスを作成すると、バグの原因になります:サービス ワーカーが停止/起動するたびに、まったく新しいインスタンスが作成されます。 Chrome DevTools が開いているとき、開始/停止動作は無効になっているので、サービス ワーカー コードで作業している間、この問題を心に留めておくことは特に重要です。
Service Workies の第 3 章では、停止したサービス ワーカーが起動を待つ間、すべての色を失うように視覚化しました。 それは迅速で、忠実で、素晴らしいものです。 何があってもあなたのそばにいてくれるでしょう。 しかし、たいていの場合、それはただ眠たいだけです。 いつも。 でも、その分、眠くなりにくい。 いい子だ!
Together, but separate #
あなたのページは一度にひとつのサービスワーカーによってのみ制御できます。 しかし、一度に 2 つのサービスワーカーをインストールすることはできます。 サービス ワーカーのコードに変更を加え、ページを更新しても、実際にはサービス ワーカーを編集しているわけではありません。 サービスワーカーは不変です。 その代わり、新しいものを作っているのです。 この新しいサービスワーカー(SW2 と呼ぶことにします)はインストールされますが、まだ起動しません。
Messing with another service worker’s caches #
インストール中、SW2 はセットアップを行うことができます。 しかし、注意してください: この新しいサービスワーカーは、現在のサービスワーカーがアクセスできるものすべてにアクセスできます。 もしあなたが注意深くなければ、新しい待機中のサービスワーカーは、現在のサービスワーカーに対して、本当に物事を台無しにすることができます。
- SW2 は SW1 がアクティブに使用しているキャッシュを削除できます。
- SW2 は SW1 が使用しているキャッシュの内容を編集し、SW1 がページが期待していない資産で応答する原因となる可能性があります。
SkipWaiting #
サービスワーカーはまた、インストールが終わるとすぐにページの制御を行うために、危険な skipWaiting()
メソッドを使用することも可能です。 これは、意図的にバグのあるサービスワーカーを置き換えようとしているのでなければ、一般に悪い考えです。 新しいサービスワーカーは、現在のページが期待していない更新されたリソースを使用し、エラーやバグにつながる可能性があります。
const version = 1;
const assetCacheName = `assets-${version}`;
self.addEventListener("install", event => {
caches.open(assetCacheName).then(cache => {
// confidently do stuff with your very own cache
});
});
新しいサービスワーカーをデプロイするときは、version
をバンプして、前のサービスワーカーとは完全に別のキャッシュで必要なことを行うようにします。 不要になった)ことがわかります。 この時点で、古いサービスワーカーの後始末をすることが重要です。 ユーザーのキャッシュ ストレージの制限を尊重するだけでなく、意図しないバグを防ぐこともできます。
caches.match()
メソッドは、一致するキャッシュからアイテムを取得するためによく使用されるショートカットです。 しかし、それはキャッシュを作成された順に繰り返し処理します。 たとえば、あるスクリプトファイル app.js
の二つのバージョンが、assets-1
と assets-2
という二つの異なるキャッシュにあるとします。 ページでは、assets-2
に格納されている新しいスクリプトを期待している。 しかし、古いキャッシュを削除していなければ、caches.match('app.js')
は assets-1
から古いものを返し、おそらくあなたのサイトを壊してしまうでしょう。
以前のサービス ワーカーの後始末に必要なのは、新しいサービス ワーカーが必要としないキャッシュを削除することです。
const version = 2;
const assetCacheName = `assets-${version}`;
self.addEventListener("activate", event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== assetCacheName){
return caches.delete(cacheName);
}
});
);
});
);
});
サービス ワーカーが互いに衝突しないようにするには、少しの作業と規律が必要ですが、それだけの価値があります。 各バージョンは動作するはずです。 各バージョンは他のものとは別物であるべきです。 開発者が誤って、新しいゲーム ロジックと古いアセットを使用するパッチをリリースした場合、ゲームがどれほどバグだらけになるか想像してみてください。 フォーラムで大暴れすることでしょう。 6041>
Service Worker mindset #
サービス ワーカーについて考えるときに、正しい考え方を身につけると、自信を持って自分のアプリケーションを構築することができるようになります。 そのコツをつかめば、ユーザーに素晴らしい体験を提供できるようになります。
ゲームでこのすべてを理解したいのなら、それは幸運なことです! オフラインの獣を倒すために、サービスワーカーの方法を学ぶことができます。