WorkboxでPWAにする
        September 24, 2018
        
        
        
    エレベータを探すアプリを、Workboxを使ってPWAにします。
WorkboxはGoogleによって公開されている、ウェブアプリにオフラインサポートを追加するライブラリです。 アセットのキャッシュ管理を便利にしてくれるもので、PWAを構築するのに使えます。
このライブラリを使って、以前作成したエレベータを探すアプリをPWA化します。
実をいうと、create-react-appを使ってアプリを作ると、html,jsやcssなどのアセットは自動的にキャッシュされます。 オフラインでもアプリを開けるようにすることが目的であればこれでも十分ですが、今回はキャッシュ部分の処理をあえてworkboxを使って実現します。
TL;DR
npm run ejectでwebpackの設定をカスタマイズできるようにするWorkboxPlugin.GenerateSWでキャッシュの設定を記述する
webpackによる自動生成
webpackの設定をカスタマイズできるようにする
cacheの設定は最終的にはService Workerの実装で表現されます。 WorkboxのAPIを使って自分で実装することもできますが、実装自体を生成する方法がいくつか用意されています。 今回は、自分が普段のプロジェクトでも利用しているwebpackによる生成を行います。
まず、create-react-appで生成したアプリのwebpackの設定をカスタマイズできるようにします。
npm run eject
これで、create-react-appへの依存がなくなり、webpackなどを自分でカスタムできるようになります(裏を返すとプロジェクトの設定を自分でメンテしていくことになります)。
eject すると、config/webpack.config.prod.js というファイルが生成されています。
アセットをキャッシュしたいのはproduction環境のみなので、設定を記述するファイルもproduction用のファイルのみです。
※netlify-lambdaを使っている場合、eject後にpackage.jsonのstart:lambdaとbuild:lambdaを以下のように変えてください。
"start:lambda": "NODE_ENV=development BABEL_ENV=development netlify-lambda serve src/lambda",
"build:lambda": "NODE_ENV=production BABEL_ENV=production netlify-lambda build src/lambda",
ejectした後だと、NODE_ENVとBABEL_ENVが実行時に未設定の状態になるようで、起動できませんでした。
pluginのインストール
次に、Service Worker生成用のpluginをinstallします。(yarnをお使いの場合は適宜読み替えてください)
npm install workbox-webpack-plugin --save-dev
設定の追加
config/webpack.config.prod.js を開き、以下を追加します。
const WorkboxPlugin = require('workbox-webpack-plugin');
もともとある const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); と、そのpluginの設定は消してしまいましょう。
代わりに、 WorkboxPlugin.GenerateSW() を plugin に追加しましょう。
特にオプションを渡していませんが、これだけでbuild時に生成されるアセットをprecacheするようなService Workerが service-worker.js として生成されます。
公式ドキュメントを見ると sw.js で出力されるようなのですが、自分の環境では service-worker.js として出力されていました。
APIレスポンスのキャッシュ
静的なアセットのキャッシュだけではなく、APIリクエストの結果もキャッシュすることができます。
例えば、/fetchXXX (駅情報の取得など)のリクエストの結果をキャッシュしたいときは、以下のように書きます。
new WorkboxPlugin.GenerateSW({
  runtimeCaching: [{
    // Match any request ends with netlify functions fetchXXX
    urlPattern: /functions\/fetch.*$/,
    
    // Apply a staleWhileRevalidate strategy.
    handler: 'staleWhileRevalidate',
    options: {
      cacheName: 'api-cache',
    },
  }],
})
urlPattern でキャッシュ対象のパスのパターンを指定しています。handler がキャッシュの戦略で、options がキャッシュの設定です。
今回選んだ stateWhileRevalidate だと、キャッシュがあればそれを表示しつつ、バックグラウンドでキャッシュの値を更新する、という戦略です。
この状態でアプリを動作させてみると、一度目はキャッシュがないので通信して取得した結果を表示、2回目はキャッシュがあるのでその結果を使って画面に表示しつつも、同時にAPIリクエストを投げて最新の情報を取得している動きがdevツールで確認できました。
キャッシュの戦略は他にもあります。詳細は公式ドキュメントを参照してください。
まとめ
オフラインの状態で駅の一覧を表示し、さらに過去に選んだことのある駅を選択するとその駅の情報を即座に表示することができたときはちょっと感動しました。 地下鉄だと電波が弱いところもあるのでオフライン対応必須だなぁと思っていたのですが、それが設定ベースでに簡単に実現できて便利でした。