この記事はパーソルキャリア Advent Calendar 2018の3日目です。
昨日に続けてNuxtとPWAの記事となります。昨日は主にpreCache
についてでしたが、今日はruntimeCache
のお話です。
この記事の内容
- runtimeCacheとは
- pwa-moduleでruntimeCacheを設定する
- runtimeCacheの意図しない挙動とその原因
- 意図通りにruntimeCacheされるようにする
この記事で触れないこと
- Nuxt.jsとは何か
- PWAとは何か
Nuxtについては公式ドキュメントがわかりやすいのでこちらをご参照ください。
PWAについては、Googleのコードラボや私自身が簡単にまとめたスライドをご参照いただければと思います。
tl;dr
- pwa-moduleを使う場合、同一オリジンのパスをruntimeCacheに指定するとデフォルト設定の影響で意図どおりに動作しない
-
offline: false
を設定し、デフォルト設定を自前で追加して、URLパターンマッチの順番をコントロールするとうまくいく
runtimeCacheとは
Service Workerは大きく分けて2種類のキャッシュを扱うことができます。簡単に説明すると次のようになります。
- preCache: AppShellに該当する静的アセットのキャッシュ
- runtimeCache: 非同期で呼び出すAPIのレスポンスなど、動的なデータのキャッシュ
runtimeCacheは取得するリソースの特性によって、キャッシュ戦略をうまく設定してあげると速度と更新性を最適化するのに有効です。キャッシュ戦略については、Workboxのページが図解があってわかりやすいです。
pwa-moduleでruntimeCacheを設定する
Nuxtプロジェクトにpwa-moduleを導入したら、nuxt.config.js
でruntimeCacheの設定を書くことができます。
workbox: {
skipWaiting: true,
clientsClaim: true,
runtimeCaching: [
{
// APIから取得した結果
urlPattern: '/api/xxxx/.*',
handler: 'cacheFirst',
method: 'GET',
strategyOptions: {
cacheExpiration: {
maxAgeSeconds: 60 * 60 * 24, // 1日
},
cacheableResponse: {
statuses: [200],
},
},
},
],
}
試しにこのように設定してみます。
例えばサービスがhttps://your.domain
で動いていたとすると、https://your.domain/api/xxxx/.*
に合致するリクエストはキャッシュを最初に見に行くという設定です。もしキャッシュが存在しなければ(もしくはキャッシュへのアクセスに失敗したら)ネットワークから取得します。
なので、キャッシュからリソースが取得できればかなり早いレスポンスタイムになります。
skipWaiting
とclientsClaim
については今回は詳しく言及しませんが、簡単にいうと「初回の読み込みからService Workerがページをコントロールできるようにする設定」です。
runtimeCacheの意図しない挙動とその原因
意図通りに動かない事象
意気揚々とruntimeCacheを設定して、実行してみました。
が!**「キャッシュの割になんか遅いぞ...?」**となりました。
Chromeのデベロッパーツールを見るとService Workerからレスポンス来てるし、実際にキャッシュも存在するのんいなぜ...?
挙動を見る限り、networkFirstで動いている感じでした。cacheFirstがpwa-moduleでサポートされてないのかと一瞬思いましたが内部的にworkboxが使われているしそんなはずはありません。
原因
ビルド時に生成されるsw.js
を見てみたところ、原因が判明しました。
下記はsw.js
のruntimeCacheの設定が出力されている部分を抜粋したものです。
workbox.routing.registerRoute(new RegExp('/.*'), workbox.strategies.networkFirst({}), 'GET')
workbox.routing.registerRoute(new RegExp('/api/xxxx/.*'), workbox.strategies.cacheFirst({"cacheExpiration":{"maxAgeSeconds":900},"cacheableResponse":{"statuses":[200]}}), 'GET')
ん?なんか身に覚えのないやつがいるぞ...?
workbox.routing.registerRoute(new RegExp('/.*'), workbox.strategies.networkFirst({}), 'GET')
試しにnuxt.config.js
のruntimeCache
の内容を削除してビルドしてみましたが、デフォルトで入る設定のようです。
URLパターンがマッチするのに動作しない
ここでのポイントは、
- 自分で設定に書いたURLパターン'/api/xxxx/.*'が、
sw.js
に存在するruntimeCacheの両方のURLパターンにマッチする - 実際の挙動は先に書かれているものに従う
ということです。
デフォルトで出力される設定は、決して邪魔しようとしているのではなく、同一オリジンのすべてに対してcacheを使えるようにして、オフライン(ネットワークからリソースが取得できない)の場合にも、キャッシュにあるデータを代用することでアプリケーションが動作するようにすることを意図した、むしろ親切な挙動といえます。
URLパターンは先にマッチしたほうが採用される動きになるので、自分で書いた設定をデフォルトで出力される設定より前に埋め込められれば幸せになれそうです。
実際に、sw.js
を直接書き換えて動かしてみると、意図通りにcacheFirstになり、レスポンスも速くなりました。
意図通りにruntimeCacheされるようにする
Workboxモジュールもドキュメントがありますが、今回の範囲は網羅されてないようでした。ということでソースコードを見てみます。
// Optionally cache other routes for offline
if (options.offline && !options.offlinePage) {
options._runtimeCaching.push({
urlPattern: fixUrl(`${routerBase}/.*`),
handler: 'networkFirst'
})
}
これっぽいです。
どうやらoffline
にfalse
を設定してあげればいけそうです。このオプションはデフォルトでture
になっていました。
そうすると、デフォルトの親切設定が出力されなくなるので必要な場合は自分でしていしてあげる必要があります。
workbox: {
offline: false,
skipWaiting: true,
clientsClaim: true,
runtimeCaching: [
{
// 求人検索APIから取得した結果
urlPattern: `/api/xxxx/.*`,
handler: 'cacheFirst',
method: 'GET',
strategyOptions: {
cacheExpiration: {
maxAgeSeconds: 60 * 60 * 24, // 1日
},
cacheableResponse: {
statuses: [200],
},
},
},
{
// デフォルト(最後に記述する)
urlPattern: '/*',
handler: 'networkFirst',
method: 'GET',
},
],
}
/*
に対してnetworkFirstを設定してあげます。ここで重要なのは、最後に入れてあげることです。
先に述べたように、sw.js
内で上からURLパターンをマッチさせていき、最初にマッチした戦略がてきようされることになります。
この状態でビルドするとこんな感じになります。
// 自分でコントロールしたいもの
workbox.routing.registerRoute(new RegExp('/api/xxxx/.*'), workbox.strategies.cacheFirst({"cacheExpiration":{"maxAgeSeconds":900},"cacheableResponse":{"statuses":[200]}}), 'GET')
// デフォルトで設定されるのを明示的に指定したもの
workbox.routing.registerRoute(new RegExp('/.*'), workbox.strategies.networkFirst({}), 'GET')
めでたしめでたし!
デフォルトの挙動の変更
runtimeCaching: [].concat(options._runtimeCaching, options.runtimeCaching).map(i => (Object.assign({}, i, {
urlPattern: i.urlPattern,
handler: i.handler || 'networkFirst',
method: i.method || 'GET'
})))
見たところ、デフォルトの設定が自分で設定したものよりも上に出力される要因はこの処理みたいです。
options._runtimeCaching
とoptions.runtimeCaching
の順番を入れ替えてあげれば、今回の記事で書いたような対応はしなくて良さそうです。
ということで、プルリクエストを出しました。
良さそうであればイイカンジのリアクションをよろしくお願いいたしますmm
まとめ
pwa-moduleはとても便利ですが、たまにかゆいところに手が届かないことがあります。
かゆいところをかけるようにするために、OSSに貢献するというのも良いなと思いました。
Nuxt×PWA盛り上げていきたい!
追記(2019/02/08)
上記の変更を含むβ版がリリースされています。
https://github.com/nuxt-community/pwa-module/releases