Help us understand the problem. What is going on with this article?

既存サイトのPWA化とPush通知基盤を作る(1) - PWA化とService Worker + Workboxでキャッシュをコントロールする

はじめに

既存サイトをPWA化しServiceWorkerでキャッシュのコントロールとプッシュ通知を受け取れるプロトタイプを作ったのでそれについてまとめました。
Firebaseを利用したプッシュ通知の配信基盤も作ったのでそれについても後日書きます。

前提

NuxtやGatsbyなどのフレームワークを使っていないプロジェクトへの導入です。

目標

以下の実装をゴールとします。
この記事では上の3つまでをゴールとして進めていきます。

  • 既存サイトをPWA化する
  • Workboxを使ってキャッシュをコントロールする
  • オフラインで閲覧できるようにする
  • プッシュ通知を受信できるようにする
  • プッシュ通知の配信基盤をFirebaseで構築する

PWA化する

まず既存サイトをPWA化していきます。

PWAとは

  • 「Progressive Web Apps」の略
  • ネイティブアプリのようなUXを提供する技術
  • ホーム画面へ追加するとプッシュ通知を受け取れる
  • オフラインでも動作する
  • スプラッシュスクリーンを設定したりフルスクリーンで表示などが可能
  • ストアを介さずにインストール可能

詳しくは以下を参照してください。
はじめてのプログレッシブウェブアプリ | Web Fundamentals | Google Developers

前提としてやること

  • manifest.jsonを用意してhead内で読み込む
  • Service Workerを設定・登録する
    • Service Workerは段階的に機能を追加していくことが可能
  • SSL対応
    • Service WorkerはHTTPSを介して提供されるページでしか登録できない
  • アイコン用画像の用意
    • アイコン画像が読み込まれない状態だとService Workerが有効にならないので注意

manifest.json

アプリケーションの情報(名前、説明、アイコン)やカラーや初期表示ページのURL等を記述し設定するjsonファイルです。
詳しくは以下を参照してください。
ウェブアプリマニフェスト | MDN

導入する

サイトのルートディレクトリにmanifest.jsonを作成します。

manifest.json
{
  "name": "pwa_prototype",
  "short_name": "pwa_prototype",
  "description": "PWAのプロトタイプサイト",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#000000",
  "theme_color": "#4DBA87",
  "icons": [
    {
      "src": "/img/icons/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/img/icons/android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "gcm_sender_id": "103953800507"
}

設定した属性の内容は次の通りです。

属性 説明
name アプリケーションの名前
short_name 短縮表示用のアプリケーションの名前
description アプリケーションの説明
start_url アプリケーションを起動したときに読み込むURL
display 表示モードを定義
background_color スプラッシュ画面の背景色を指定
theme_color アプリケーションの既定のテーマ色
icons アプリケーションのアイコンとして機能する画像ファイルの配列を指定
gcm_sender_id FCMを利用する場合、値は103953800507で固定

Google Analytics用のパラメータを埋め込みたい場合は、start_urlで指定する。
また、Firebase Cloud Messaging(FCM)を利用する場合は、gcm_sender_idの値は103953800507で固定になるので注意。
この他にも設定できる項目があるので、詳しくは以下を参照してください。
ウェブアプリマニフェスト | MDN

manifest.jsonを作成したらheadタグで以下のように読み込みます。

<link rel="manifest" href="manifest.json">

手で書くのがめんどくさかったらこんな便利なものもある。

参考

ウェブアプリマニフェスト | MDN
Webアプリマニフェスト| Webの基礎| Google Developers
PWAのmanifest.jsonとiconsの各サイズのアイコン画像を自動生成してくれるApp Manifest Generatorの紹介

「ホーム画面に追加」を表示するには

  • 訪問回数が2回以上あり、訪問の間隔が5分以上ある
  • 適切に設定された(short_nameまたはnameiconsstart_urldisplayを含む)manifest.jsonが読み込まれている
  • Service Workerにfetchイベントを記述する(中身は空でも良い)
sw.js
self.addEventListener('fetch', function(e) {
  // 中身は空でもOK
})

この程度の単純な作業で済みます。デメリットも特になさそうなので、とりあえず実装しておきます。

参考

Add to Home Screen | Web Fundamentals | Google Developers

Service Workerの準備をする

以下を実現するために準備をしていきます。

  • Workboxを使ってキャッシュをコントロールする
  • オフラインで閲覧できるようにする

Service Workerとは

  • Webページとは別にバックグラウンドで実行するJavaScript
  • キャッシュ管理、プッシュ通知、バックグラウンド同期などを行えるようにする基盤技術
  • HTTPSを介して提供されるページかlocalhostでしか登録できない

詳しくは以下を参照してください。
Service Worker の紹介 | Web Fundamentals | Google Developers

Service Workerの登録

サイトのルートディレクトリにsw.jsを作成します。
作成したら下記のようにService Workerとして登録します。

index.html
<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function () {
    navigator.serviceWorker.register('./sw.js', { scope: './' })
      .then(function (registration) {
        console.log('SW.js registration successful with scope: ', registration.scope);
      }, function (err) {
        console.log('Sw.js registration failed: ', err);
      });
  });
}
</script>

Workboxによるキャッシュのコントロール

それでは実際にsw.jsにキャッシュのコントロールの処理を実装していきます。

sw.js
// workboxを読み込む
importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js');

// Service Workerライフサイクルをスキップする
workbox.core.skipWaiting();
workbox.core.clientsClaim();

// preload
workbox.navigationPreload.enable();

// Pre cache - Service Workerのインストール時に指定したファイルをキャッシュする
// https://developers.google.com/web/tools/workbox/modules/workbox-precaching
workbox.precaching.precacheAndRoute([
  "/css/style.css",
  "/js/main.js",
  "/js/libs/slick.min.js",
  // リビジョンをつけることも可能
  { url: '/index.html', revision: 'abcd1234' },
]);

// Runtime cache - アクセスしたタイミングで取得したファイルをキャッシュする
// https://developers.google.com/web/tools/workbox/modules/workbox-routing
// font
workbox.routing.registerRoute(
  ({event}) => event.request.destination === 'font',
  new workbox.strategies.StaleWhileRevalidate({
    cacheName: 'font',
    plugins: [
      new workbox.expiration.Plugin({
        maxEntries: 50,
        maxAgeSeconds: 7 * 24 * 60 * 60,
      }),
    ],
  })
);

// image
workbox.routing.registerRoute(
  ({event}) => event.request.destination === 'image',
  new workbox.strategies.StaleWhileRevalidate({
    cacheName: 'image',
    plugins: [
      new workbox.expiration.Plugin({
        maxEntries: 50,
        maxAgeSeconds: 7 * 24 * 60 * 60,
      }),
    ],
  })
);

// js
workbox.routing.registerRoute(
  ({event}) => event.request.destination === 'script',
  new workbox.strategies.StaleWhileRevalidate({
    cacheName: 'js',
    plugins: [
      new workbox.expiration.Plugin({
        maxEntries: 50,
        maxAgeSeconds: 7 * 24 * 60 * 60,
      }),
    ],
  })
);

// css
workbox.routing.registerRoute(
  ({event}) => event.request.destination === 'style',
  new workbox.strategies.StaleWhileRevalidate({
    cacheName: 'css',
    plugins: [
      new workbox.expiration.Plugin({
        maxEntries: 50,
        maxAgeSeconds: 7 * 24 * 60 * 60,
      }),
    ],
  })
);

// ページごと
workbox.routing.registerRoute(
  new RegExp('/item/'),
  new workbox.strategies.StaleWhileRevalidate({
      networkTimeoutSeconds: 3,
      cacheName: 'itempage',
      plugins: [
        new workbox.expiration.Plugin({
          maxEntries: 50,
        }),
      ],
  })
);

self.addEventListener('install', function (event) {
  // empty
});
self.addEventListener('activate', function (event) {
  // empty
});
// インストールバナーを表示する
self.addEventListener('fetch', function (event) {
  // empty
})

キャッシュ戦略

キャッシュ戦略(サービスワーカーのレスポンスの返し方)を設定するために、Workbox Strategiesを使います。
以下のようなパターンがあるので、それぞれ用途によって使い分ける必要があります。

  • Stale-While-Revalidate
    • キャッシュを優先して返す。キャッシュがなければネットワークから返す。さらにネットワークへリクエストしてキャッシュを更新する。
  • Cache First
    • キャッシュを優先して返す。キャッシュがあればネットワークへリクエストしない。キャッシュはない場合、ネットワークから返す。
  • Network First
    • ネットワークから優先して返す。失敗した場合はキャッシュを返す。
  • Network Only
    • ネットワークからのみ返す。
  • Cache Only
    • キャッシュのみ返す。

今回の場合は、基本的にStaleWhileRevalidateで設定しています。

詳しくは以下を参照。
Workbox Strategies | Google Developers
PWA: Workbox Strategies 和訳

キャッシュができているか確認する

chromeのデベロッパーツールのApplicationタブのCache Storageを確認。
ちゃんとキャッシュできていることが確認できます。
cacheNameで指定した名前にファイルが格納されています。
cash_devtool.png

リロードしてNetworkタブを開くと、Service Workerからキャッシュを返せていることが確認できます。
cache_network.png

ネットワークをoffline状態にしてリロードをしても、Service Workerからキャッシュを返せています。
workbox02.png

ここまでのまとめ

これでPWA化とServiceWorkerを登録してWorkboxでキャッシュのコントロールをすることまでが実現できました。
この記事では基本的なキャッシュの処理しかしていませんが、要件によっては処理が複雑になることもあると思います。
とりあえず既存サイトをPWA化させることができました。

続き

既存サイトのPWA化とPush通知基盤を作る(2) - Firebase Cloud Messaging(FCM) + Cloud Functions + Cloud Firestoreで配信基盤を作る(後日公開)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした