OSSANS Project since 2020

「もし四十のおっさんがプログラミングを始めたらどう人生変わるのか」
このブログのメインテーマです
肩肘張らずにユルく、ときたま厳しくやっていきます
※プログラミング以外のことも書きます

WordPress

2022.10.26(更新)

【コピペ可】WordPressサイトをPWA化するためにService Workerを実装してみた。

アイキャッチ画像
WordPress

サービスワーカーってよく聞くけど正直どんなものかわからない…。どう使ったらいいの?

こんなお悩みに答えていきます。

本記事の内容

  • Service Workerの概要
  • Service Workerの設定方法

本記事を対象とする方

  • フロントエンドエンジニアを目指す方
  • Webディレクターを目指す方
  • Webサイト、Webアプリ担当者

この記事を書いている僕はプログラミング歴2年1ヶ月です。webエンジニアとして”社内転職”しました。

Service Workerとは何か?

まず最初にService Workerとは何かを説明していきます。

Service Workerはブラウザのバックグラウンドで動作してネットワークリソースを制御するものです。

ブラウザにインストールして使用するという概念があり、インストール後であればオフライン状態でもバックグラウンドで動作します。

その役割は、キャッシュデータの管理、サーバーからのプッシュデータの受信、スケジュールされたバックグラウンド処理の実行などなどです。

ダウンロードして実行するにあたり時間のかかるサブリソースファイルや、更新頻度が低いサードパーティーリソースファイルなどを登録しておくと威力を発揮します。

serviceworker動作イメージ ※Service Worker 動作イメージ ブラウザにインストールして使用します

簡単に言うと、ブラウザにインストールされると、Service WorkerがブラウザとWebサーバーの間に立って、リクエストとレスポンスのやり取りの中で色々なことをやってくれるようになります。かなり便利なことが行えるようになるので、各ブラウザもサポートに積極的です。今日の時点でサポートしているブラウザは下記の通りです。

ブラウザサポート ※多くのブラウザでサポートされています

サポートされていないブラウザもありますが、その場合単純にインストールされないだけなので、Webサイト・Webアプリにエラーが発生するようなことはありません。ですので、積極的に実装していきましょう !

Service Workerはそれ自体だけでできることは多くありません。Cache APIやFetch APIといった機能を利用することで、下記のような特徴を得ることができます。

  • イベント駆動型
  • プロキシの役割
  • Webページとは異なるライフサイクル
  • オフラインキャッシュ
  • プッシュ通知
  • DOMアクセス不可
  • HTTPSとlocalhostでのみ稼働

Service Workerのライフサイクル

Service Workerはブラウザから独立したバッググラウンド環境で動作するため、通常のWebサイトのライフサイクルとは異なります。Webサイトのライフサイクルは、サイトに訪問しページを開き、他のページに遷移もしくはページを閉じるまでです。一方でService Workerのライフサイクルは、ブラウザにインストールされ、それが破棄されるまでです。Service Workerのライフサイクルは以下6つの状態があります。

  1. parsed
  2. installing
  3. installed
  4. activating
  5. activated
  6. redundant

parsed

Service Workerがまだインストールされていない初期状態です。この状態では、すぐに次の状態に移るため始点の意味で使われます。

installing

Service Workerをインストールしている状態です。Service Workerが未登録状態での新規インストール(register)または、登録済みでの更新インストール(update)の状態となります。この際、Service Workerのライフサイクルイベントであるinstallイベントが発生します。ただし、更新インストールにおいて何も更新する必要がない場合は、activatedまでスキップされます。

installed

Service Workerのインストールが正常に終了した状態です。新規インストールの場合は、次のactivatingに遷移します。更新インストールの場合は、古いService WorkerがまだWebサイトを制御しているため、更新によるデータ不整合を防ぐために新しいService Workerはこの状態で待機(waiting)します。ユーザーがサイトから離脱するなどして、古いService Workerが安全に破棄された後にユーザーが再訪問すると、次のactivating状態に遷移します。

activating

新しいService Workerを有効にしている状態です。この際Service Workerのライフサイクルイベントであるactivatingイベントが発生します。

activated

新しいService Workerが正常に有効になった状態です。この状態になるとService Workerはfetchイベントや、messageイベントを待つアイドル状態になります。

redundant

以下の理由でService Workerが無効となった状態です。

  • installing中にエラーが発生した
  • activating中にエラーが発生した
  • 新しいService Workerと置き換えられた

具体的な設定方法

ではService Workerを具体的に設定してみましょう。

今回説明するのは必要最低限の設定です。詳しく知りたい方は、MDN サービスワーカーの使用、をご覧ください

まずは、JavaScriptファイルを用意して、Service Workerの登録を行います。このファイルは登録のみを行うファイルなので、sw-regi.jsとでもしておきましょう。sw-regi.jsが用意できたら、以下コードを記述してください。

  1. const registerServiceWorker = async() => {
  2.   if ('serviceWorker' in navigator) {
  3.     try {
  4.       const registration = await navigator.serviceWorker.register(
  5.         'https://elon-task.com/sw.js',
  6.         {
  7.           scope:'/',
  8.         }
  9.       );
  10.       if (registration.installing) {
  11.         console.log('Service worker installing');
  12.       } else if (registration.waiting) {
  13.         console.log('Service worker installed');
  14.       } else if (registration.active) {
  15.         console.log('Service worker active');
  16.       }
  17.     } catch (error) {
  18.       console.error(`Registration failed with ${error}`);
  19.     }
  20.   };
  21. };
  22. registerServiceWorker();

登録の際に気をつけることはスコープの設定です。スコープはService Workerが機能する範囲のことです。Service Workerは自分がいるディレクトリまたはそれ以下の階層のディレクトリにしか適用されません。

スコープのイメージ ※ワーカー用jsのスコープに注意する 登録用jsの置き場所はどこでも良い

当サイトの場合、WordPressでサイト制作していることもあり、ルート(public_html)ディレクトリにsw.jsを置いています。ということはそれと同等または以下の範囲ならService Workerが適用されるということになります。

では、実際にService Workerを動かしていきます。sw.jsというファイルを用意して、その中にService Workerの処理を書いていきます。最初はインストールイベントとキャッシュの登録です。

  1. const addResourcesToCache = async (resources) => {
  2.   const cache = await caches.open('v1');
  3.   await cache.addAll(resources);
  4. };
  5. self.addEventListener('install', (event) => {
  6.   event.waitUntil(
  7.     addResourcesToCache([
  8.       'https://elon-task.com/',
  9.       'https://elon-task.com/profile/',
  10.       'https://elon-task.com/blog/',
  11.       'https://elon-task.com/works/',
  12.       'https://elon-task.com/contact/',
  13.       '/wp-content/theme/elon_task_ver2/css/single-style.css',
  14.       '/wp-content/theme/elon_task_ver2/js/main.js',
  15.       '/wp-includes/fonts/Quicksand-Bold.woff2',
  16.       '/wp-includes/fonts/Quicksand-Regular.woff2',
  17.       '/wp-includes/fonts/Roboto-Bold.woff2',
  18.       '/wp-includes/fonts/Roboto-Regular.woff2',
  19.       '/wp-includes/js/mediaelement/mediaelementplayer-legacy.min.css',
  20.       '/wp-content/plugins/contact-form-7/includes/css/styles.css',
  21.     ])
  22.   );
  23. });

キャッシュの登録にはCache APIを使用します。Cache APIとは、リソースをキャッシュストレージに格納するAPIのことです。v1(バージョン1)と言う名前のキャッシュを作り、caches.openメソッドでキャッシュを開きます。開いたcacheにaddAll()メソッドでリソースを追加します。リソースファイルはCSSやスクリプト、画像などの静的ファイルが該当しますが、URLでも機能するようです。現に当サイトではURLを入れて機能していて、サブリソースファイルがキャッシュされています。なお、selfはService Workerのことを指し、eventはService Workerのインストールイベントのことを指しています。Chromeであれば、DevToolsで確認できます。

キャッシュストレージ ※キャッシュストレージにv1キャッシュが追加されリソースが確認できます

キャッシュされたリソースはFetch APIを利用することでオフライン環境でも取得できるようになります。例えば以下のようなことができます。

  1. const putInCache = async (request, response) => {
  2.   const cache = await caches.open("v1");
  3.   await cache.put(request, response);
  4. }
  5. const cacheFirst = async (request) => {
  6.   const responseFromCache = await caches.match(request);
  7.   if (responseFromCache) {
  8.     return responseFromCache;
  9.   }
  10.   const responseFromNetwork = await fetch(request);
  11.   putInCache(request, responseFromNetwork.clone())
  12.   return responseFromNetwork;
  13. };
  14. self.addEventListener('fetch', (event) => {
  15.   event.respondWith(cacheFirst(event.request));
  16. });

要点を解説していきます。

  1. const responseFromCache = await caches.match(request);
  2. if (responseFromCache) {
  3.   return responseFromCache;
  4. }

上記部分ですが、caches.match()メソッドにて、リソースがリクエストのキャッシュとして格納済みであるかを確認し、格納済みであればそのリソースをレスポンスとして返しています。

  1. const responseFromNetwork = await fetch(request);
  2. putInCache(request, responseFromNetwork.clone())
  3. return responseFromNetwork;

そして、キャッシュにリソースがなければサーバーにリクエストを送り、レスポンスはキャッシュに格納してからクライアントに返しています。ここで重要な点は、リクエストとレスポンスはストリーム形式であるためデータを使い回すことができない点です(一度だけ読み込まれます)。そのためfetchやcacheを行う場合は、clone(複製)を作成する必要があります。ここではクローンをキャッシュに格納して、オリジナルはブラウザに返されます。

下記の流れで、オフラインでも動くかどうか検証できます。また、Service Workerからリソースを取得できているかどうかはサイズの列で確認できます。

  1. GoogleのDevtoolsを開く
  2. ネットワークパネルの「スロットリングなし」をクリック
  3. オフラインを選択
オフライン動作 ※オフライン動作ができているか、サイズの列が「Service Worker」となっているか確認しましょう

もう一つ使用例を紹介します。それは古いキャッシュの削除です。これはactivatedの状態で行います。

  1. const deleteCache = async key => {
  2.   await caches.delete(key)
  3. }
  4. const deleteOldCaches = async () => {
  5.   const cacheKeepList = ['v2'];
  6.   const keyList = await caches.keys();
  7.   const cachesToDelete = keyList.filter(key => !cacheKeepList.includes(key))
  8.   await Promise.all(cachesToDelete.map(deleteCache));
  9. }
  10. self.addEventListener('activate', (event) => {
  11.   event.waitUntil(deleteOldCaches());
  12. });

最初にv2という[空の配列]を用意します。keyListという変数に現在のキャッシュのリストを入れます。v2に現在と同じキャッシュがない場合に限り、keyListから新しい配列cachesToDeleteを作ります。cachesToDeleteには現在のキャッシュが入るので、deleteCache関数式で削除し、プロミスに返します。あとは、waitUntil()メソッドを使って、activateイベントが発生した時に古いキャッシュが削除されるようにするだけです。

Service WorkerとPWAはどんな関係にあるのか?

Service Workerが出てくるところに必ずPWAが出てきますが、どんな関係なのでしょうか?

PWAとはProgressive Web Appsの略でして、GoogleやMozillaが推進するプロジェクトでWebサイト・Webアプリをネイティブモバイルアプリのように提供する仕組みです。

PWAと通常のWebアプリとの違いは、オフラインでもアクセスできたり、まるでモバイルアプリかのようにスマホのホーム画面にアイコンを追加できたりします。

その機能を果たすのが、Service Workerであり、Cache APIであり、Fetch APIなのです。

ですので、Service Workerが実装されていなければPWAは実現出来ず、切っても切れない関係ということができます。

当サイトはWordPressでサイト構築をしており、PWA化の一部始終を下記記事にまとめてありますので是非ご覧ください。

Service Workerを実装したWebサイト、Webアプリは何が違うのか?

通常のWebサイトとService Workerを実装したサイト、両者の違いを見ていきましょう。

Service Workerを実装すると以下のことが行えるようになります。

  • オフライン接続
  • スマホのホーム画面にインストール(アイコンが表示)
  • プッシュ通知
  • HTTPSレスポンスの横取り

Service Workerを取り入れたWebサイト、Webアプリはまるでネイティブのモバイルアプリのように機能するので多くのことができるようになります。先程説明した通りそれがPWA化したサイトと呼ぶことができます。しかしながら、コーポレートサイトやポートフォリオサイトなど、通常のWebサイトであればほとんど必要ないように思います。

当WebサイトもService Workerを実装しておりますが、正直今の時点での内容では不要な機能が多いと感じています。例えばプッシュ通知やオフライン通信です。通知するほどのニュースがありませんし、ユーザーはオフラインの時にわざわざ当サイトを訪問しないでしょう。唯一体感したこととしては、はページロードのスピードです。実装後は段違いに速く感じました。

これはキャッシュされたサブリソースファイルがCache APIを通して動作しているからですね。

ページロードのスピードはSEOにも直結してきますし、Service Worker導入において、私が最も期待していたことであるので大変満足しています。

今後、Fetch APIを使ったレスポンスの横取りなどで行う機能についてはじっくり考えて実装していこうかと思っています。

その時はまた改めてその様子を記事にしたいと思いますので、ぜひお楽しみに !

今回のまとめ

今回はService Workerについて紹介しました。最後まで読んで頂きありがとうございました。

今回の記事をまとめるにあたり下記サイトを参照させて頂きました。

Service Workerの導入については登録用のスクリプトファイルとワーカー用のスクリプトファイルを用意する

Service Workerを適用する範囲(スコープ)に注意する

オフラインで使用できるようにするにはCache APIを利用する

関連記事

  • WordPress
    ブログアイキャッチ画像
    WordPress
    2022.09.01(更新)

    【プラグインなし】WordPressで制作したサイトをPWA化してみた話。

  • WordPress
    ブログアイキャッチ画像
    WordPress
    2022.09.01(更新)

    ページロード遅延が改善 !レンダリングブロックを防ぐ方法【WordPress / JavaScript編】

  • WordPress
    ブログアイキャッチ画像
    WordPress
    2022.09.01(更新)

    ページロード遅延が改善 !レンダリングブロックを防ぐ方法【WordPress / CSS編】

CATEGORY カテゴリー別最新記事

  • CSS
    アイキャッチ画像
    CSS

    【CSS】margin?position?要素の中央寄せで使うプロパティとは?

  • Docker
    アイキャッチ画像
    Docker
    2022.10.03(更新)

    【簡単設定!】DockerでWordPressをローカル開発する方法

  • HTML
    アイキャッチ画像
    HTML
    2023.02.18(更新)

    【図解あり】aタグの入れ子ルールとHTMLの入れ子ルールを覚える方法

  • JavaScript
    アイキャッチ画像
    JavaScript
    2023.03.04(更新)

    【実例コードあり】覚えておきたいパララックスデザインの3つの基本

  • Python
    アイキャッチ画像
    Python
    2023.02.09(更新)

    【やらなきゃ損!】2日でできるPythonのExcelコピペ自動化!

  • Study
    アイキャッチ画像
    Study
    2023.02.16(更新)

    稼ぐために始めたプログラミングのこれまでの振り返り。勉強方法や役立った本も紹介。

  • web site
    アイキャッチ画像
    web site
    2022.11.26(更新)

    ページ表示速度アップ!npmでソースファイルを軽量化する方法【サクッと簡単!】

  • WordPress
    アイキャッチ画像
    WordPress
    2022.10.26(更新)

    【コピペ可】WordPressサイトをPWA化するためにService Workerを実装してみた。