この記事は、「3分クッキング ServiceWorkerで阿部寛さんを超える」の続きです。
ng buildでServiceWorkerのリソースキャッシュが簡単にできることを説明しましたが、実際のキャッシュ戦略を考えるといろいろ不足があります。
- ユーザーアップロード画像
- CDNに配置しているコンテンツ
- XHRのレスポンス内容
これらはng buildでは生成されないものです。そのための設定方法を紹介します。
ngsw-config.json
@angular/service-workerでは、ServiceWorkerのスクリプト自体はngsw-worker.js
になります。Angularのアプリ開発者はjsonとして用意されているインターフェースng-config.json
を設定する形になります。
ng-config.jsonの設定項目の詳細は、Angularドキュメント(日本語版)の「Service Workerの設定」も参照ください。
テンプレートは以下です。ng buildの成果物にフォーカスした内容です。
{
"index": "/index.html",
"assetGroups": [{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html"
],
"versionedFiles": [
"/*.bundle.css",
"/*.bundle.js",
"/*.chunk.js"
]
}
}, {
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**"
]
}
}]
}
URLでAssetを設定する
index.htmlのlinkタグやscriptタグで設定しているCDNや他の外部URLからロードされるサードパーティのリソースは以下の設定ができます。
Angularでよく使われているであろうMaterialDesignのアイコンフォントファイルを例にします。
{
"index": "/index.html",
"assetGroups": [{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html"
],
"versionedFiles": [
"/*.bundle.css",
"/*.bundle.js",
"/*.chunk.js"
],
// ここを追加する
"urls": [
"https://fonts.googleapis.com/icon?family=Material+Icons"
]
}
}, {
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**"
]
}
}]
}
このURLパターンのものは、ホスト先のHTTPヘッダーにしたがってキャッシュされます。
動的なコンテンツのキャッシュはDataGroupで設定する
都度変更されうるXHRのレスポンスやアバター画像のフォルダなどは、AssetGroupのバージョン管理と切り離してキャッシュする仕組みが用意されています。
以下のようにdataGroups
というキーを用意します。ここのセクションは、個々にキャッシュパラメータを定義して、基本的に手動で管理する前提です。
{
"index": "/index.html",
"assetGroups": [{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html"
],
"versionedFiles": [
"/*.bundle.css",
"/*.bundle.js",
"/*.chunk.js"
],
"urls": [
"https://fonts.googleapis.com/icon?family=Material+Icons"
]
}
}, {
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**"
]
}
}],
// ここを追加する
"dataGroups": []
}
ユーザーアップロード画像をキャッシュする
アバター画像をAWS S3にアップして、一意に名前が振られたファイルをCloudFrontで配信するケースを例にします。
"dataGroups": [{
"name": "avatar",
"urls": ["http://hoge.cloudfront.net/*.jpg"],
"cacheConfig": {
"maxSize": 1,
"maxAge": "1d",
"timeout": "5s",
"strategy": "performance"
}
}]
nameは一意になっていればよいです。
cacheConfigが肝になっていて、maxSize
とmaxAge
が必須です。残り2つは任意であり、現在はこの4種類のパラメータが定義されています。
-
maxSize
は保有する世代数を指定します。例では1つだけを持ちます。 -
maxAge
はキャッシュの有効期間です。例では1日経ったらアプリが要求するとネットワークから取得します。 -
timeout
はネットワークが応答しないときにキャッシュを使うと判断する時間です。例では5秒にしています。次のstrategyと合わせるとこれはmaxAgeが切れた時に有効になるものです。 -
strategy
はキャッシュ戦略です。例ではパフォーマンスを優先してmaxAgeが過ぎるまでは原則キャッシュを利用します。
以上の構成で、アプリからリクエストが発生した時に、初回はネットワークから取得し、以後はオンライン/オフラインに限らずキャッシュを使い、maxAgeが過ぎたら再度ネットワークへフォワードするという定義になります。
XHRのレスポンスをキャッシュする
users一覧ページのXHRレスポンスを例にします。
"dataGroups": [{
"name": "xhr",
"urls": [
"/users",
],
"cacheConfig": {
"maxSize": 10,
"maxAge": "1d",
"timeout": "10s",
"strategy": "freshness"
}
}]
今回はstrategy
をfreshness
にしています。freshnessは、基本的にネットワークへ流します。オフラインなどタイムアウトが発生した時のみ、キャッシュを使用します。キャッシュへの切り替え判断は、timeout
に設定されている10秒です。
dev.toや日経新聞で話題になった事前キャッシュ(先読み)
dev.toや日経新聞で話題になった事前キャッシュ(先読み)は、XHRのレスポンスだけならアプリ側でhttpリクエストをトリガーしておけばServiceWorkerにキャッシュが入るので可能です。
AMPhtmlファイルをオフラインキャッシュしたいとなると、AMPhtmlをDataGroupに設定してアプリ側はiframeで出力するなどの対応が必要になるかと思います。正直なところ、実装したことないのでどこまでできるか不明です。(ご存知の方がいたら教えてください)1
AngularServiceWorkerの注意点
ServiceWorkerは同一スコープ2で一つしかスクリプトを設定できません。つまり、自前のServiceWorkerを作ったり、他のサービス(Firebaseなど)のServiceWorkerと同居することが難しいです。この解決手段として、ServiceWorker内でimportScriptsする手法3がありますが、@angular/service-workerではServieWorker自体にコードを記述することはできません。CLIビルドの度にnode_modulesからngsw-worker.jsをコピーする仕様です。