ServiceWorkerとCacheによるSPAの高速化、オフラインモード
Cosenseの中で、ServiceWorkerを使って何やってるか解説する
FGNエンジニアMeetup vol.1の発表資料
こんにちは
shokaiですshokai.icon
Scrapboxを作っています
横浜の自宅から京都にリモートワークしている
福岡のほうが京都より安いし近い✈️
詳細な実装の話
/daiiz/ServiceWorkerを用いたキャッシング戦略 ~Wikiアプリケーションを例に~ by daiiz.icon
半年ぐらい前の資料
これをアップデートしつつ、もう一度噛み砕いて説明したいshokai.icon
普通のSPAを高速化したり、オフライン対応したりする事を考える
どこから手を付けるべきか?
デモ内容
起動がはやい
Desktop PWA
画面遷移がはやい
エディタにいろいろ書ける
オフラインモード
https://gyazo.com/7622d63ce35541453a74a7a53c2196d3
普通のSPAの動作
0. ブラウザでページ開くと
1. HTML, JS, CSSなどのassetsをダウンロードして
2. AjaxでAPIからデータをダウンロードして
3. 画面が表示される
よくある高速化
サーバーサイドレンダリング
3. 画面が表示されるまで終わったHTMLをいきなり返せば、最初の表示が速くなる
その後で、ブラウザ上でも1,2,3を実行したりする技もある
CDN
1と2のダウンロードが速くなる
どちらも通信を効率化する
これらはCosenseでは全くやっていない
サーバーはアメリカのHerokuにある
往復200 msecぐらいかかるが、問題ない
ServiceWorkerで高速化
0. ブラウザでページ開くと
1. HTML, JS, CSSなどをCacheStorageから表示
2. 前回取得したAPIデータをCacheStorageから表示
3. 画面が表示される
この時点で操作可能になる
4. AjaxでAPIからデータをダウンロードして
5. 画面がさらに更新される
1, 2, 3まで一切通信をせずブラウザ内のキャッシュでやる
通信するのは4だけ
通信速度を効率化するのではなく、タイミングや順序を入れ替えた
基本の話
ServiceWorkerとは?
プログラマブルなネットワークproxy
HTTP通信を途中で書き換え可能
オフライン表示の為の機能ではない
レスポンスをcacheしてあれば、オフライン表示も実装できるよね(自力でがんばれ)という世界観
何でもできる
通信を握りつぶしたり
送信先を書き換えたり
リクエストしたフリをしてリクエストせず、適当なレスポンスを返したり
回線切ってChrome起動すると見える
https://gyazo.com/438ef8eb64c22ae94b1885b1b9d8fafd
Chromeのホーム画面はServiceWorkerで実装されてるから、オフラインでも表示できる
workerのソースも見れるぞ
https://www.google.co.jp/_/chrome/newtab-serviceworker.js
CacheStorageとは?
KVSです
key Request object
value Response object
最近のブラウザに組み込まれている型
https://gyazo.com/8a41adb085f83eea29f03a8d90513738
UIスレッドとServiceWorkerの両方からアクセスできる
const response = await caches.match(request)で取り出せる
ServiceWorkerのインストール
ここは特に工夫の余地無いので飛ばす
navigator.serviceWorker.registerでググれ
一度インストールすれば、約24時間毎に更新チェックされる
ブラウザが自動的にやってくれる
HTTP通信がServiceWorkerを通る
1. リクエスト
UIスレッド → ServiceWorker → サーバー
2. レスポンス
UIスレッド ← ServiceWorker ← サーバー
どちらもServiceWorkerを通る
Fetchイベント
code:serviceworker.js
self.addEventListener('fetch', event => {
event.respondWith((async () => {
const response = await fetch(event.request)
return response
})())
})
1. fetchというイベントが来る
2. 関数の方のfetch(request)でサーバーから取得して
3. UIスレッドに返す
これを自由に拡張して
例えばfetch(request)が失敗したらcacheを返す様にすれば、オフラインモード完成
Prefetch
ServiceWorkerとUIスレッドの間は、postMessageでもやりとりできる
リンクにマウスホバーで「cache温めといて」
https://gyazo.com/86faa398b233f86be3ad4a15cd2e777e
クリックする前にデータ取得して光速を超える
詳しくはServiceWorkerをproductionで使ってる話
話を戻す
ServiceWorkerを使った高速化
0. ブラウザでページ開くと
1. HTML/JS/CSSをCacheStorageから表示
2. 前回取得したAPIデータをCacheStorageから表示
3. 画面が表示される
4. AjaxでAPIからデータをダウンロードして
5. 画面がさらに更新される
順に見ていく
1. HTML, JS, CSSなどをCacheStorageから表示
assets cacheという仕組みを実装した
バックグラウンドでHTML, JS, CSSを取得しておく
取得タイミング
ServiceWorkerが自動更新した後
しばらくUIスレッドが通信していない時
日時をkeyにしたCacheStorageに保存してある
https://gyazo.com/550a81a0c38c5da5652dd119a2ab348f
新しいのを取得したら古いのを削除
https://scrapbox.io/assets/assets.json
取得するassetのリスト
このServiceWorkerの振る舞いをcache firstと呼んでいる
まずcacheから返す
cacheに無ければ、networkから取得して返す
2. 前回取得したAPIデータをCacheStorageから表示
ブラウザ側のstateを復元する
この工程にServiceWorkerは関わらない
CacheStorageはUIスレッドからも直接読み書きできるので
stateにreadyState = RESTORE_CACHEをセットしておく
後で使う
エディタの中身はまだこの工程を実装していない
古いページを編集したらややこしくなるので
3. 画面が表示される
stateに基づき、Reactを普通にレンダリングするのだが
UIによっては「あくまでCacheから表示していますよ」と教えた方が良い物もある
https://gyazo.com/43d6a2919ef4c0b1dcebc8ca46fab0cb
最新データの取得&準備にちょっと時間がかかる為
4. AjaxでAPIからデータをダウンロードして
ServiceWorkerは、普通にfetchイベント受けてfetch(request)してresponseをUIスレッドに返す
だけでなく
fetchの失敗をtry-catchして
Cache Storageから返す
responseをCacheStorageに保存しておく
日付を付けて、古いのは消す
外部originの画像もimageに保存
https://gyazo.com/1c581ffc204f3c71bbbe63e3c40c4284
これをnetwork first (cache second)と呼んでいる
まずnetworkから返そうとする
失敗したらcacheから返す
stateにreadyState = FROM_REMOTEもしくはFALLBACK_CACHEをセットしておく
後で使う
5. 画面がさらに更新される
もう一度Reactのレンダリングを行う
readyState = FROM_REMOTEの時
普通に表示する
ServiceWorkerがインストールされてない場合と同じ
readyState = FALLBACK_CACHEの時
右下にhttps://gyazo.com/d00b202f9ae63fec45d2b45daf1bdac2を表示しつつ
編集系の操作をロックし、閲覧専用にする
これでOffline modeができたshokai.icon*5
Offline mode
Wikiなので編集が行われる
編集した後のデータでCacheStorageを更新したい
色々考えたけど「今見てるページをたまにGETする」が一番簡単だったshokai.icon
まとめ
起動はやい&オフライン表示
ServiceWorker
assetsをcache firstで返す
APIリクエストをnetwork firstで処理する
UIスレッド
stateをまずcacheから復元する
これら3つのstateをユーザーに適切に教える
画面遷移はやい
prefetch
Scrapboxではこの順で導入した
1. assets cache実装して
2. prefetch
3. APIレスポンス全部cacheする
4. オフライン表示
5. cacheから復元して起動速度アップ
6. ページ編集後にcache更新
既存のSPAを高速化するなら、どこからやる?
prefetchだと思うshokai.icon
効果がわかりやすい。画面遷移はしょっちゅうある
実装が一番簡単
起動はやい&オフライン表示 は
assets cacheがまず必要
実装がちょっとめんどくさい
めんどくさい割に感動が薄い(起動した時しか効果が無い)
ビルド・デプロイシステムにも関わってくる
rakusai.iconが作った https://github.com/nota/sw_skelton が参考になる
WebWorkerも併用するとアツい
ブラウザ側でマルチスレッドプログラミングができる
最近はスマホでも8コアとか入ってる
無料でスケールする
エッジコンピューティング
API設計が変わってくる
これを
サーバーでデータを計算をしてブラウザに返す
こうしていく
サーバーはデータをドカッと返す
ブラウザで計算する
cacheされたAPIレスポンスを元に、ブラウザ側でインタラクティブな事をやれる
こういう分業になる様にAPIを作っていくと、たぶんオフラインモードでできる事が増える
サーバー
データの単純な保存、アクセス権限チェック
ブラウザ
計算していい感じに表示
詳しくは検索や推薦をWebWorkerでやるへ