やりたいこと
SEO を気にしないモダンブラウザのウェブアプリでは、初回ロード時にServiceWorker の初期化前に始まったリクエストが、タイミング次第でキャッシュが構築されたりされなかったりする問題がある。同じ動作になることを保証するために、 ServiceWorker の初期化が終わってからJSの読み込みなどを行いたい。
ついでに、裏側でServiceWorkerの更新があったらユーザーにモーダルなどで通知したい。
前提として、生存時間が長い富豪的なSPAを想定している。
コード
あらゆるJS含むリソースの初期化が前提に出来ないので、フレームワークを使わず、HTMLのインライン一筆書きした。
- /sw.js - serviceWorker
- /main.js - 本来読み込みたいコード
es modules がないブラウザは nomodule を実行して警告する。
<html>
<head>
<title>PWA</title>
<meta charset="utf8">
</head>
<body>
<div class=root>
Loading...
</div>
<script nomodule type=text/javascript>
document.body.innerHTML = "Please use modern browser";
</script>
<script type=module>
function setupBrowserFS() {
return new Promise(resolve => {
BrowserFS.configure({ fs: "IndexedDB", options: {} }, err => {
if (err) {
throw err
}
resolve()
})
})
}
async function setupServiceWorker() {
if (navigator.serviceWorker == null) {
throw new Error('Your browser can not use serviceWorker')
}
let installed = !!navigator.serviceWorker.controller
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (installed) {
const modal = document.createElement('div')
modal.innerHTML = `
<div style='position: absolute; outline: 1px solid black; right: 10px; bottom: 10px; width: 350px; height: 80px'>
<div>New version available!</div>
<span>It will be applied from the next</span> - <button onclick="location.reload()">Reload</button>
</div>
`
document.body.appendChild(modal)
}
});
const reg = await navigator.serviceWorker.register('/sw.js');
await navigator.serviceWorker.ready
installed = true
setInterval(() => {
reg.update();
}, 60 * 1000);
}
;(async () => {
const el = document.querySelector('.root')
try {
// SW
el.innerHTML = 'Checking service worker ...'
await setupServiceWorker()
// Run
el.innerHTML = 'Loading...'
await import('/main.js')
} catch (e) {
el.innerHTML = 'Something wrong: ' + e.message
}
})()
</script>
</body>
</html>
このコードでは、60s に一回 service worker の更新確認を行っている。更新があったかどうかのヘッダのチェックだけなので軽いはずだが、なんだかんだでリクエスト走るし、なんだかんだでリロードされると思うので、実際は15分に1回ぐらいでいいと思う。
気をつける点は、この index.html を offline cache に突っ込んでる場合、このコードの変更自体が適用されるのが次回の更新、更新が確認できるのが次の次になることだろうか。