とりあえず途中まで進んだので記録。
表題の通り既存のWebアプリにServiceWorkerを追加して、PWAっぽく使えるようにします。操作対象は最近いろいろいじっている、CastBackgroundです。今回はキャッシュ処理とmanifest.json の設定だけします。
github.com
実際どうやってるのかを見たい場合は、上記ソースコード を直接確認してください。manifest.json
とsw.js
を見れば大抵わかると思います*1 。
実際の手順
既存サイトにServiceWorkerを追加する場合、やるべきことは以下の通り
(manifest.json を追加する)
ServiceWorker用のJavaScript ファイルを「ルートフォルダに」作成する
キャッシュ対象となるファイルのリストを作成する
ServiceWorkerのライフサイクルに対応するイベントハンドラ を記述する(最低限必要なのは、install, fetch。たぶんactivate)
とりあえず順を追って確認
(manifest.json を追加する)
ServiceWorkerとは直接関係ないですが、まずはmanifest.json をつくっておくと、アプリがPWAとして認識されるようになるので、作っておくと便利です。
この辺は参考文献のMDNを参考に。とりあえず記述の通りの内容を書いておけば問題はなさそうです。Edgeで開くと「Web app manifest should have the filename extension 'webmanifest'.」と言われていますがその辺はよくわからないのでとりあえずスキップ
アイコンファイルなどは事前に作成しておくと良いでしょう。
ServiceWorker用のJavaScript ファイルを「ルートフォルダに」作成する
まずは、ServiceWorkerを作成します。インストール処理については大まかにいろんなサイトに書いてある限りで問題なし。
<script type="module" >
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js' , { scope: "/" } )
.then((reg) => {
console.log('Service worker registered.' , reg);
} );
}
</script>
type="module"
はとりあえず流れで書いてしまっていますが要らないかも(外したときの挙動を確認していないので記載しています)。
第一引数にはServiceWorkerとして動作するためのJavaScript ファイルを指定します。
ServiceWorkerは、サイトの状態などを確認するためにブラウザにインストールされるUIを持たないJavaScript ファイルです。このファイルでオフライン時のキャッシュの制御などの操作を行います。
第二引数のscopeについては、第一引数のJavaScript 配下のディレクト リパス を指定します。未指定時はJavaScript ファイルのあるディレクト リ となります。
インストールが完了すると、以降scope配下のファイルリクエス トがあったときに、このスクリプト が呼び出されるようになります。このため、よほどのことがなければアプリケーションのルートディレクト リ、すなわち/を指定することになります。
なお、ここでsw.jsがあるディレクト リより上位のディレクト リを指定すると例外が発生します*2 。
これは「Service Worker、はじめの一歩 | 第1回 Service Workerとは | CodeGrid 」によると、「Service-Worker-Allowedヘッダ」というヘッダがないと変更できない動作のため、sw.jsは素直にルートディレクト リに置くのが無難です。
キャッシュ対象となるファイルのリストを作成する
次に、キャッシュしたいファイルの一覧を作成します。キャッシュ対象は自分の作成したファイルだけでなく、CDN などを参照しているJQuery やBootstrapなどのファイルも対象 です。
この辺については面倒なので、わたしは次のようなファイルを列挙してJavaScript ファイルを生成するPython スクリプト を書きました。ファイルの変更があったときはこれを使用する ということで。
from pathlib import Path
import json
def files (rootdir: Path, path: Path) -> list [str ]:
l = []
for f in path.glob("*" ):
if str (f.name).startswith("." ): continue
if f.is_file() and f.suffix == "" : continue
if f.is_file() and f.suffix in [".pptx" , ".py" , ".json" , ".md" ]: continue
if f.is_dir() and f.name in ["tools" ]: continue
if f.is_file() or not f.name in ["res" , "src" ]:
fs = "/{}{}" .format(str (f.relative_to(rootdir)).replace(' \\ ' , '/' ), '/' if f.is_dir() else '' )
print (fs)
l.append(fs)
if f.is_dir():
l.extend(files(rootdir, f))
return l
root = Path(__file__).parent.parent
data = json.dumps(files(root, root), indent=2 )
with (root / "src" / "files.js" ).open("w" ) as f:
f.write(f"""
const CB_ALL_FILES = {data}
""" )
print ("Done." )
そして、sw.jsの冒頭に以下のように書きます。
importScripts("./src/files.js" )
const CB_ALL_FILES_URLS = CB_ALL_FILES.concat(
"https://cdn.honokak.osaka/honoka/4.3.1/css/bootstrap.min.css" ,
"https://code.jquery.com/jquery-3.5.1.slim.min.js" ,
"https://cdn.honokak.osaka/honoka/4.3.1/js/bootstrap.bundle.min.js"
);
これでファイルリストの作成は完了です。*3 。
なお、詳細は不明ですが、このような記述と、JavaScript 読み込み時のintegrity
やcrossorigin
の指定は競合を起こす場合があります*4 。
ServiceWorkerのライフサイクルに対応するイベントハンドラ を記述する
sw.jsに各種イベントハンドラ を書いていきます。ServiceWorkerのライフサイクルはGoogleの記事などを見ればおおよそわかります 。
とりあえず最低限キャッシュを動作させるには、install
とfetch
だけ書けば良いっぽい(ただ適宜キャッシュを捨てるなどの処理におそらくactivate
も必要)。
キャッシュが必要なファイルの一覧作成
install
でキャッシュ必要なURLのリスト(https ://からはじまるアドレスかサーバル ートからの絶対パス )を指定します。
self .addEventListener('fetch' , (event ) => {
console.log('service worker fetch ... ' + event .request);
event .respondWith(
caches.match(event .request).then((cacheResponse) => {
return cacheResponse || fetch(event .request).then((response) => {
return caches.open('v1' ).then((cache) => {
cache.put(event .request, response.clone());
return response;
} );
} );
} )
);
} );
上記のopen()メソッドの引数に使っている「v1」はキャッシュの呼称なので、本来であれば定数として持っておいた方が良さそうです。定数が変わればキャッシュを参照しなくなります。
ただしこの時点ではキャッシュが必要なURLのリストは指定できてもキャッシュは行なわれていない 状態です。実際のキャッシュはfetch
で行ないます。
fetchする
次に実際にファイルをダウンロードし、必要ならキャッシュに追加します。
self .addEventListener('install' , (event ) => {
event .waitUntil(
caches.open('v1' ).then((cache) => {
return cache.addAll(
CB_ALL_FILES
);
} )
);
} );
これでキャッシュは完了です。
動作確認する
動作確認には、Edgeの開発者モードがとても便利です。Applicationタブでほとんどの情報を参照することができます。
開発者モード
ただしくServiceWorkerがインストールされていれば、「ServiceWorkers」項目にsw.js
の名前が、「キャッシュストレージ」の項目にキャッシュされているファイルの一覧が表示されています(キャッシュストレージの項目に入っているはずのファイルがなかった場合は、files.jsの中身を確認します)。
オフライン時の挙動を確認する
オフライン時の挙動を確認するときは、画面上部のオフラインチェ ックボックスをチェックします。するとその間はインターネットに接続していない扱いになります。
オフライン チェックボックス
ServiceWorkerを削除してやりなおす
キャッシュが古くて更新したいときなど、ServiceWorkerを削除するときは、「登録解除」ラベルをクリックします(実運用の時は、ちゃんとアンインストール処理を設けましょう)。
登録解除ラベル
ただしそのまま再読込するとページがうまく読めないときがあるので、一度ブラウザタブを閉じてから開き直すと良いです。
(この時点での)まとめ
とりあえず落とし穴がところどころにあるのが注意が必要ですが、PWAのキャッシュストレージ機能を実装しました。実際使うにはキャッシュを適宜更新したり、期限切れキャッシュを削除したりする必要がありますが、ひとまず一つ目の峠は越えたようです。
参考文献