Web ページを手っ取り早く PWA 化してインストール可能にする


Web ページをアプリとしてインストール (ホーム画面に追加) できるようにするまでの、イージーモードな手順です。

アイコン画像を用意する

アイコン画像は、複数のバリエーションを用意することもできますが、「144x144 以上の png 1枚」が最低限必要です。

manifest.json を用意する

PWA には、アプリケーションの情報をおさめた manifest.json が必須です。最低限の内容はこんな感じになります:

{
  "name": "<アプリ名>",
  "short_name": "<短いアプリ名>",
  "icons": [{
    "src": "icon.png",
    "sizes": "144x144",
    "type": "image/png"
  }],
  "start_url": "/",
  "display": "fullscreen"
}

display は必須ではありませんが、設定しないと普通にブラウザ画面で表示されてしまってアプリ感が出ません。

サービスワーカーを生成する

サービスワーカーは主にアプリをキャッシュして、オフライン時でも動くようにするためのプログラムです (その他、プッシュ通知のやりとりとかもろもろのバックグラウンド処理をやらせることもできます)。

Web ページの PWA 化には必須ですが、最低限のものは workbox という CLI ツールで生成できるのでここでは自動生成してしまいます。

workbox をインストールして、

npm install workbox-cli

ウィザードで設定ファイルを生成します。

npx workbox wizard

キャッシュしたいファイル形式にチェックを入れたりするだけで設定ファイル workbox-config.js が生えます。

設定ファイルを用意したら generateSW コマンドで実際のサービスワーカーを生成できます。

npx workbox generateSW workbox-config.js

デフォルト設定だと sw.js という名前で生成されると思います。

sw.js にはキャッシュすべきファイル名がベタ書きされているので、アプリの構成が変わった時は更新する必要があります。

husky や GitHub Actions などで push するたびに自動更新するようにしておくと便利かもしれません。

manifest.json とサービスワーカーを読み込む

最後に、 PWA 化したい Web ページで manifest.json とサービスワーカーを読み込みます。

<link rel="manifest" href="manifest.json">
<script>
  if ("serviceWorker" in navigator) {
    navigator.serviceWorker.register("sw.js");
  }
</script>

動作確認とトラブルシュート

PWA の動作確認には Web サーバー (https または localhost) が必要です。適当に Web サーバーを立ち上げて、ページを開いてみてください。

僕はエディタから簡易 Web サーバーを立ち上げられるようにしています (https://github.com/skeeto/emacs-web-server)。

URL 欄にインストールボタンが出ていれば無事動いています。

インストールボタンが出ない場合は、デベロッパーツールの Application タブを開くと、サービスワーカーの動作状況や manifest.json の問題を見ることができます。

「アイコンの画像サイズが 144x144 になってないよ」とかで怒られてるケースが多いと思います。

おまけ:インストールを促す

アプリがインストール可能な状態になると beforeinstallprompt イベントが発行されるので、これにフックすると独自のダイアログでインストールを促すことができます。

let installEvt;
window.addEventListener('beforeinstallprompt', (e) => {
  installEvt = e;
  e.preventDefault();
  showInstallDialog();
});

このとき、イベントのオブジェクトを保存 (installEvt) しておくと、後で .prompt メソッドを呼ぶことで実際にインストールさせることもできます (prompt の名前の通り、ユーザーはキャンセルすることもできます)。

installButton.onclick = () => installEvt.prompt();