knowledge base

マークアップ/フロントエンドエンジニアのWEB制作における備忘録です。平日はWEB屋、休日は社会人劇団の主宰・劇作家をしています。

【PWA】シリーズPWA (4) Service Workerを生成して実際に動かす

登録

registerメソッドを使う 一旦インストールしてしまえば、JSに変更がない限りServiceWorkerは更新されない。

このあたりはブラウザがよしなにやってくてる。registerするには後述するserviceworker.jsのパスを引数に指定するだけ。

navigator.serviceWorker.register('./serviceworker.js',  { scope: './' })
    .then(function(registration){
      console.log('Service Workerの登録に成功しました。');
    })
    .catch(function(error){
      console.error('Service Workerの登録に失敗しました。', error);
    });

installイベント

self.addEventListener('install', function(event) {
  console.log('Service Workerのinstallイベントが発生しました。');
});
installイベントのハンドリングは必須ではありません。もしinstallイベントリスナーを設定しなかった場合でもService Workerの登録は問題なく進行します。インストールに関する処理を安全に行うためには、その間、Service Workerの登録が進んでいくのを待っていてもらわなくてはいけません。そのためのメソッドが、installイベントのコールバックが受け取るExtendableEventオブジェクトが持つ.waitUntil()メソッドです。
event.waitUntil() に Promise が渡されると、ブラウザはインストールの完了のタイミングと成功したかどうかを把握できます。

つまるところ何かの条件でインストールが 成功/失敗 したと判断したいときにはwaitUntilメソッドのコールバック関数内でPromiseオブジェクトを生成し、その中で resolve/reject をすればよい。

installイベントにしばしば紐づけられるのものに、キャッシュの生成がある。これについては後ほど詳述する。

取り急ぎ、installイベントにwaitUntilメソッドが紐づけられていることが多いが、これはinstallイベントに処理を紐づけるお作法だと思っておいて差し支えない。

waitUntilメソッドとは

waitUntilは自分自身が呼ばれたイベント内に置いて、イベント終了のライフタイムを自身の処理が終わるまで待つメソッド。

引数に渡された promise (正確には引数に渡された関数内で作られたpromise)が resolve されるまでイベントのライフタイムを延長する。

引数に渡された関数内の処理にはpromiseが終わってから次のライフタイムに移るようにするメソッドのようです。

activateイベント

install後すぐにactivateにならない場合がある。

ServiceWorkerを更新した時など、データの不整合等を防ぐために waiting 状態に移行し安全な状態になるのを待つ時がそれである。

すぐに active 状態にしても問題ない場合は、skipWaiting()を呼ぶことで active 状態にすることができる install後すぐにactivateさせるには

self.addEventListener('install', function(event) {
  self.skipWaiting();
});

skipWaitingメソッドとは

先述の通り、データの不整合などないか確かめるために(主にServiceWorker更新時)waiting状態を経てactive状態になる。

この時に、データの不整合がないと確実にいえる場合などに、即時active状態に遷移させるためのメソッド。

waiting状態は場合によってはけっこう長いので、データの不整合チェックが不要なアプリは、このメソッドを実行しておいた方が良い。

また、仮にactivateしたとしても、初回訪問時は何も起こらず、2回目にそのページを開いた時にならないと、fetchイベントやpushイベントに紐付けた関数が実行されないそうです。

そこで、activateイベントにclients.claim()というメソッドを紐付けることで、初回訪問時でも動かせることができるそうです。

self.addEventListener('activate', function(event) {
  clients.claim();
});
Service Worker がインストールされ、 fetch等のイベントを受け取れるようになると activate イベントが発火されます。 ただし、デフォルトでは、ページリクエスト自体が Service Worker を通過するまでそのページの fetch イベント等は処理されません。 ややこしいですが、簡単に言うとはじめにService Workerを登録したページ(register('./serviceworker.js')したページ)は、再度読み込むまで Service Workerの処理(fetchイベントの処理等)が走りません。 もしregisterしたページをすぐに有効にしたい場合は clients.claim()を activate で実行すればいいです。

claimメソッドとは

まだコントロールされていないページをコントロール状態にするメソッド。

Service Worker 側のスクリプトで使う。

Service Worker の実行コンテキスト ServiceWorkerGlobalScope は clients というフィールドを持っており、これは現在ブラウザで開いているページを保持する。

ページの visibilityState を取得したり、postMessage で通信したりできる。

こちらに詳しく解説されている。

nhiroki.jp

破棄

機会はほとんどないと思いますが、ServiceWorkerの破棄はunregisterメソッドを用いて、以下の通りで行えます。

navigator.serviceWorker.getRegistrations()
  .then(function(registrations) {
    for(let registration of registrations) {
      registration.unregister();
    }
});

ついでに(前回の復習)

本来であれば上記のみでPWAとして最低限の機能が利用できていたので、2019年2月時点で、若干仕様が変更になりました。

それはPWAとしての最低限の機能であるミニ情報バーの表示をするために、fetchイベントにハンドリングをしていなければいけなくなりました。

ということでServiceWorker側に以下を追記します。おまじないのようなものです。

self.addEventListener('fetch', function(event) {});

まとめると

ServiceWorkerを登録して動かせる状態にするための必要最小限なテンプレートを作ってみました。 main.js(ブラウザ側)

//ServiceWorkerの登録
navigator.serviceWorker.register('./serviceworker.js',  { scope: './' })
    .then(function(registration){
      console.log('Service Workerの登録に成功しました。');
    })
    .catch(function(error){
      console.error('Service Workerの登録に失敗しました。', error);
    });

serviceworker.js(ServiceWorker側)

// インストール時
self.addEventListener('install', function(event) {
   event.waitUntil(self.skipWaiting());
});

// フェッチ時
self.addEventListener('fetch', function(event) {});

// アクティブ時(開いているページをコントロールできるようになったら)
self.addEventListener('activate', function(event) {
  event.waitUntil(clients.claim());
});

次回予告

この上で、更にServiceWorkerに行わせたいことを記述してゆくことになります。