SP用メニューボタン
ソレゾレブログ

技術的な事だったり日常の気になる事だったり

PWA(Progressive web apps) とはいったいどんな機能なのか?サービスワーカー編①

スポンサーリンク

予備知識

サービスワーカーとは何か

サービスワーカーは、ブラウザとWebサーバーの間に介入するプロキシのような役割をします。サービスワーカーが介入することで、クライアントからのフェッチ処理、サーバーからのプッシュ通知やバックグラウンド同期などが行えるようになります。サービスワーカーは独立して動作することもできため、ユーザーがブラウザを立ち上げていなくてもサーバーからのプッシュ通知を受け取り、バックグラウント同期を行うことが出来ます。サービスワーカーはPWA機能の核たる部分となります。

サービスワーカーの概念や詳細については、下記ページに詳しく書いてありますのでそちらを参照してください。

ここではサービスワーカーの設定方法と動作検証について触れていきます。

サービスワーカーはマルチスレッド処理を可能にする

もっと詳しく知りたい方は下記を参照してみてください。

スポンサーリンク

サービスワーカーはDOMを直接操作できない

「Web Workers」はDOMを直接操作できません。「Web Workers」の一種である「ServiceWorker」も同様です。そのため、「ServiceWorker」はメインスレッドのDOMを操作するために「postMessage」を利用します。「postMessage」は、オブジェクト間で安全にクロスドメイン通信を可能にするためのメソッドです。詳しくは下記参照。

サービスワーカーのステータス遷移

サービスワーカーには6つのステータスが存在します。これらステータスの遷移をライフサイクルイベントと言うそうです。

parsed

サービスワーカーが未だインストールされていない初期状態。サービスワーカースクリプトの構文が解析済みの状態です。

installing

サービスワーカーが登録されていない時にアクセスすると初回1度だけ発生します。 このタイミング(installイベント)で任意のスクリプトを実行することが出来ます。キャッシュの初期化やキャッシュしたいコンテンツの設定を読み込ませます。installイベントが完了してactivatedになるまでfetch や push などのイベントを受信しません。

installed

installingが完了した状態です。waiting状態とも呼ばれます。

activating

2回目のアクセスでは、installingとinstalledステータスをスキップして一気にこのステータスまでスキップされます。

サービスワーカーが有効化され稼働始めた状態です。このタイミング(activateイベント)で任意のスクリプトを実行することができます。installイベントは初回しか実行されないため、サービスワーカーのキャッシュファイル名の変更や、古いキャッシュファイルの削除など、2回目以降のアクセスで必要となる処理はここに書きます。

activated

サービスワーカーが有効化され稼働始めた状態且つ、activatingイベントで設定したスクリプト処理が終わった状態です。サイトが完全にサービスワーカーの管理下に入ったことを意味します。fetch や push などのイベントを受け付けます。

redundant

サービスワーカーのinstallやactivateなどで失敗してサービスワーカーが無効となっている状態です。

サービスワーカーのインストール

Webアプリをインストール(サービスワーカーのインストール)可能にするには下記の設定が必要です。

  • ウェブマニフェストファイルの作成と読み込み
  • ブラウザへのサービスワーカー登録
  • サービスワーカーJavascriptファイルの編集(install イベントとfetchイベントの設定)

ウェブマニフェストファイルの作成と読み込み

サービスワーカーの登録設定

サービスワーカーを登録するには、下記Javascriptコードを追記します。body要素にscriptタグで追記しても良いですし、Javascript用の外部ファイルに追記しても良いです。

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
     navigator.serviceWorker
     .register('sw.js')
     .then(registration => console.log('registered success', registration))
     .catch(error => console.log('error', error));
  });
}

1行目は、Service Worker APIが対象のブラウザで利用可能かチェックしています。

3行目~4行目は、サービスワーカーの登録(サービスワーカーJavascriptファイルの登録)をしています。.register('/sw.js', {scope: './'})のように記載すると、サービスワーカーが管理するURLの範囲を指定できます。これにより、複数のサービスワーカーに管理を分離することが出来ます。詳しくは下記参照。

5行目は、サービスワーカー登録成功時にコンソールにメッセージを表示させています。registration変数は、サービスワーカー登録時にサービスワーカーの登録情報が格納される「ServiceWorkerRegistration」が入っている変数で、参考までにその中身を表示させています。「ServiceWorkerRegistration」について詳しくは下記を参照。

6行目は、サービスワーカー登録失敗時のメッセージ出力です。error変数は、サービスワーカーが出力したエラーが格納されます。

5行目,6行目は必須設定ではありませんが、サービスワーカーの登録が成功したか失敗したか確認する手段が無いため、わかりやすいように付け加えています。

サービスワーカーの登録に成功するとコンソールにメッセージが表示されます。

スポンサーリンク

サービスワーカーJavascriptファイルの作成と編集

「sw.js」ファイルに、installイベントとfetchイベントを追記します。installイベントだけでよさそうなものですが、なぜかfetchイベントが設定されていないと下記のようにエラーが出ます。fetchイベントも設定してください。

サービスワーカー制御Javascriptの編集1

「sw.js」ファイルに下記を追記します。必要最低限の設定です。

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

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

ChromeでWebページを読み込んでみると、URL欄右側にインストールアイコンが表示されます。アイコンをクリックします。

サービスワーカー制御Javascriptの編集2

インストールが促されますので「はい」をクリックします。

サービスワーカー制御Javascriptの編集3

アプリが開きました。写真は私が撮った真冬の北海道です。笑

サービスワーカー制御Javascriptの編集4

アプリ起動用のアイコンがデスクトップに作成されていれば成功です。

サービスワーカー制御Javascriptの編集5

この設定はあくまでWebアプリをインストール可能にするための必要最低限の設定です。PWAの肝であるfetchやpushについては以降で触れていきます。

スポンサーリンク

サービスワーカーのキャッシュ登録

installイベント

サービスワーカー(ServiceWorkerGlobalScope)のinstallイベントでは、キャッシュするコンテンツとキャッシュ名などを設定します。キャッシュの登録だけではなく他の任意の処理も設定可能です。設定したキャッシュ名は、Chorme開発者モードの下記箇所に表示されるものです。

installリスナーの設定

installイベントに設定を追加していきます。コードの参考例は下記です。

const CACHE_NAME = 'sw-cache-v1';
const urlsToCache = [
  '/',
  '/wp-content/themes/wpdev_pwa/style.css',
  '/wp-content/themes/wpdev_pwa/js/main.js',
  '/img/icon-maskable_192x192.png',
  '/img/icon-maskable_512x512.png',
  '/img/icon-transparent-maskable-192x192.png',
  '/img/icon-transparent-maskable_512x512.png',
  '/img/icon-transparent_192x192.png',
  '/img/icon-transparent_512x512.png',
  '/img/icon_192x192.png',
  '/img/icon_512x512.png',
  '/img/screenshot1.png',
  '/img/screenshot2.png',
  '/img/screenshot3.png'
];

self.addEventListener('install', function(event) {
    event.waitUntil(
        caches
            .open(CACHE_NAME)
            .then(function(cache) {
                console.log('Opened cache');
                return cache.addAll(urlsToCache);
        })
    );
});

1行目は、キャッシュの名前を設定しています。変数名とキャッシュ名は任意です。バージョン管理できるようにキャッシュ名にはバージョンを含めると便利です。キャッシュ名は本番運用してしまったら変更することは滅多にありませんが、検証では頻繁に変える可能性があるので付与した方が便利です。

2行目~17行目は、キャッシュするコンテンツのパスを配列で変数に格納しています。変数名は任意です。キャッシュしたいファイルを個別に指定しても良いですし、任意ディレクトリ内のすべてのコンテンツをfor文で拾って配列に追加するのもアリです。

19行目は、installイベントをリッスンしています。

20行目は、installイベントが進行中または完了したか監視するものです。installイベントが進行中の間サービスワーカーは終了されません。

21行目~22行目は、対象のキャッシュストレージを開いてコンテンツファイルのキャッシュが可能な状態にします。

25行目は、URL の配列を受け取りコンテンツをキャッシュしています。

24行目は、コンテンツが正しくキャッシュされたことを知るために設定しています。

正しくキャッシュされた場合は、下記ようにキャッシュストレージが新たに作成され、キャッシュコンテンツが一覧されます。尚、ひとつのコンテンツでもキャッシュに失敗すると、すべてのキャッシュが失敗する点に留意してください。

installリスナーの設定

古いキャッシュの削除

activateイベント

サービスワーカー(ServiceWorkerGlobalScope)のactivateイベントでは、サービスワーカーのキャッシュファイルが更新された場合に古いキャッシュを削除する設定を記載します。古いキャッシュを削除するだけではなく、他の任意の処理も設定可能です。

この検証例では、設定を追記後「my-site-cache-v20」という既存キャッシュ名を「my-site-cache-v21」に更新し、その挙動を確認しています。

検証直前の状態は、キャッシュ名「my-site-cache-v20」のみがキャッシュストレージに入っています。

activateイベント1

installイベントで「sw.js」に設定した「CACHE_NAME」変数に格納されるキャッシュ名を、「my-site-cache-v21」に変更します。その後ページを再読み込みすると、二つのキャッシュストレージが表示されます。

activateイベント2

このようにキャッシュ名を変更して「sw.js」を再配信して認識させると、何も対処をしないと使わなくなった古いキャッシュがどんどんたまっていくことになります。キャッシュを右クリックすると現れる「削除」メニューから手動で削除することも可能ですが、それでは手間ですので、自動的に削除する設定を追加します。コードの参考例は下記です。activateイベントに設定を追加していきます。

self.addEventListener('activate', function(event) {
  let cacheAllowlist = ['残したいキャッシュストレージ名1', '***2',・・・];
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheAllowlist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

1行目は、activateイベントをリッスンしています。

3行目は、activateイベントが進行中または完了したか監視するものです。activateイベントが進行中の間サービスワーカーは終了されません。

4行目は、既存のキャッシュストレージ情報一覧を取得しています。

5行目は、キャッシュストレージ名を取得しています。cacheNamesとcacheNameの中身をコンソールで確認すると、確かに取得できています。

activateイベント3

6行目は、Promise.allで配下の処理を並列に非同期処理させています。配下の7~8行目の処理を並列実行して、それが全て完了するまで次の処理に進むのを待ちます。

7行目は、キャッシュストレージから、「残したいキャッシュ名リスト」に無いキャッシュを判定しています。

8行目は、7行目の判定で「残したいキャッシュ名リスト」に無かったキャッシュを削除しています。

設定後、ページを開きなおすと下記のように古いキャッシュが削除されています。

activateイベント4

fetchによるコンテンツのキャッシュとSWオフライン化

fetchイベント

fetch(フェッチ)とは、情報を外部から取得してくることです。具体的には、クライアント側(ブラウザ)からサーバー側(Webサーバー)に情報を取りに行くことを言います。ページを移動したり更新する度に発動します。このfetchが発動したタイミングをリッスンして、サービスワーカーは任意の処理を行うことが出来ます。

このサービスワーカー(ServiceWorkerGlobalScope)のfetchベントを利用して、取得するコンテンツがキャッシュにある場合はキャッシュから返し、キャッシュに無い場合はサーバーからコンテンツを取得することが出来ます。サーバーから取得してきた場合はコンテンツが新たにキャッシュに保存され、次回はキャッシュから返すことが出来ます。

この仕組みによって、オフラインでもコンテンツの読み込みが可能になります。また、サイトの表示が高速化されます。これがPWAのメリットの一つと言えます。

下記に、fetchベントのコード例を記載します。このコードを下記サイトを参考にしました。

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        if (response) {
          return response;
        }

        let fetchRequest = event.request.clone();
        return fetch(fetchRequest).then(
          function(response) {
            if(!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }

            let responseToCache = response.clone();

            caches.open(CACHE_NAME)
              .then(function(cache) {
                cache.put(event.request, responseToCache);
              });

            return response;
          }
        );
      })
    );
});

1行目は、fetchイベントをリッスンしています。

2行目は、ブラウザーのfetchイベントを抑止してサービスワーカーがリクエストに対するレスポンスを操作できるようにしています。

3行目~4行目は、リクエスト対象のコンテンツがキャッシュに存在するか判定して結果responseに格納しています。存在しない場合はundefinedを返します。

5行目~6行目は、responseの中身を確認してレスポンスが存在すればそのレスポンスをユーザーに返して処理を終えます。undefinedの場合は以降の処理に進みます。

9行目は、リクエストをクローンして変数に格納しています。リクエストをユーザーに返すために使うとそこでリクエストを消費してしまいキャッシュに保存できなくなってしまう為、キャッシュするためにリクエストのクローンを作成しています。

10行目~11行目は、フェッチにより実際のサーバーにコンテンツをリクエストして、レスポンスをresponseに格納しています。

12行目~13行目は、不正なレスポンス(undefined?)である、HTTPレスポンスコードが200以外、HTTPレスポンスタイプがbasic以外、いずれかにマッチした場合はキャッシュ不能なレスポンスと判断してそのままレスポンスをユーザーに返して処理を終えます。キャッシュ可能なレスポンスだった場合は以降の処理に進みます。

16行目は、レスポンスをクローンして変数に格納しています。ユーザーに返すレスポンスを返すとレスポンスを消費してしまう為、キャッシュするためにクローンしています。

18行目は、コンテンツをキャッシュするキャッシュストレージを開いています。

20行目 は 、対象のキャッシュストレージにコンテンツをキャッシュしています。

23行目は 、ユーザーにレスポンスを返して処理を終了しています。

スポンサーリンク

fetchのキャッシュ動作確認

fetchが問題なく動作しているかChromeで確認してみました。

初回アクセス時のfetchによるキャッシュ動作確認

初回読み込み時(すべてのコンテンツがサーバーから読み込まれる)の状態を確認します。Chromeの開発者モードで[ネットワーク]タブを開いた状態で、Webサイトにアクセスします。すでにアクセスしてしまっている場合は、ctrl + shift + R キーを押下すればキャッシュを読まずにサーバーからコンテンツを読み込んでくれます。[サイズ]欄を確認すると、読み込んだコンテンツの容量が表示されています。

初回アクセス時はコンテンツがどこにもキャッシュされていないので、サーバーからコンテンツが読み込まれ、読み込んだコンテンツの容量が[サイズ]欄に表示されます。

初回アクセス時のfetchによるキャッシュ動作確認

2回目以降アクセス時のfetchによるキャッシュ動作確認

次に、2回目以降のアクセスで確認します。開発者モードを開いた状態で再度サイトにアクセスするか、F5キーを押下してください。

2回目以降アクセス時のfetchによるキャッシュ動作確認

[ネットワーク]タブの[サイズ]欄を確認すると、「メモリキャッシュ」「ディスクキャッシュ(ブラウザのキャッシュ)」「ServiceWorker」「容量表示」などが混在していると思います。いずれかのキャッシュに偏って表示されている方もいると思います。アクセス元の状態や条件によってキャッシュの状態が変わるので、これが正常な状態です。

基本的には、「メモリキャッシュ」→「ServiceWorker」→「ディスクキャッシュ」の順にキャッシュが使われ、キャッシュに無い場合はサーバーから読み込みまれますが、上述したようにアクセス元の状態や条件によってキャッシュの状態が変わるので、サイズ表示が検証環境によって異なると思います。また、「メモリキャッシュ」の優先度についてはブラウザによって仕様が異なるようなので、そのあたりも影響します。

キャッシュの優先度について詳しくは下記を参照してください。

2回目以降アクセス時にサービスワーカーのキャッシュだけで動作確認する方法

どうやら「ServiceWorker」からコンテンツを返しているっぽいことはわかりましたが、まだ曖昧ですね。ここでChromeの開発者モードの本領発揮です。「メモリキャッシュ」「ディスクキャッシュ」を無視して、「ServiceWorker」のキャッシュだけ認識してくれることが出来ます。

[ネットワーク] タブで [キャッシュを無効化] にチェックを入れます。この状態で、サイトを再表示してみてください。この状態で、「ServiceWorker」か「容量」で表示されれば問題なくサービスワーカーから読み込んでいるということになります。「sw.js」でキャッシュ対象にしていないコンテンツはサーバーから取得してくるので、「容量」で表示されていると思います。

2回目以降アクセス時にサービスワーカーのキャッシュだけで動作確認する方法

2回目以降アクセス時にサービスワーカーをスキップして動作確認する方法

このほか、「ServiceWorker」のキャッシュを無効化(サービスワーカー無効化)して表示する検証もできます。[アプリケーションタブ] – [Service Worker] で「ネットワークのバイパス」にのみチェックを入れます。

2回目以降アクセス時にサービスワーカーをスキップして動作確認する方法1

[ネットワークタブ] を表示して「キャッシュを無効化」のチェックをオフにします。この状態でサイトに再アクセスしてみてください。下記のように「ディスクキャッシュ」「メモリキャッシュ」のみが表示されたと思います。

2回目以降アクセス時にサービスワーカーをスキップして動作確認する方法2

2回目以降アクセス時にオフラインで動作確認する方法

PWAの目玉機能であるオフラインの動作確認もできます。[アプリケーションタブ] – [Service Worker] で「オフライン」にのみチェックを入れます。「ネットワークのバイパス」のチェックはオフにしてください。「オフライン」にチェックを入れると[ネットワーク]タブに下記のようなアイコンが挿入されます。これはオフライン状態である証です。

尚、この状態でサイトに再アクセスすると、[ソース]欄に×マークがつきます。これは、「sw.js」のスクリプトで外部へのアクセスが必要なものがあった場合アクセス不能となるため、当然発生するエラーです。想定されるエラーなので無視してください。オンラインに戻してページを閉じて再表示すると回復します。

2回目以降アクセス時にオフラインで動作確認する方法1

設定後、Webサイトに再アクセスして[ネットワーク]タブを表示すると、キャッシュ対象ではないコンテンツはサーバーから取得できなくなるため、サイズが「0B」で全体的に赤文字で表示されると思います。そのほかキャッシュされているコンテンツは各キャッシュから返され、ブラウザにはコンテンツが正しく表示されているはずです。

2回目以降アクセス時にオフラインで動作確認する方法2

2回目以降アクセス時にオフラインでサービスワーカーのキャッシュのみで動作確認する方法

更に踏み込んだ確認をします。これでは、「ServiceWorker」にキャッシュされるべくコンテンツがオフライン状態で「ServiceWorker」から返されるか確認できません。そこで、先程の 「キャッシュを無効化」の設定をオンにします。そうすることで、他のキャッシュは使われず 「ServiceWorker」のキャッシュからのみ返されることになります。これでただしくコンテンツが返却されれば設定は正しいということになります。

以上でFetchについては終了となります。