Svelte で PWA (台本ビューア) <第2回>

[PWA で作った台本ビューア](https://github.com/satamame/pscv/tree/0.0.2) を [Svelte](https://svelte.jp) で作り直すことにしたので、やったことをメモしていきます。

- 前回 : [<第1回> 基本のコンポーネントを作る](https://qiita.com/satamame/items/3e0683a1fc6ecd6b66ee)
- 次回 : [<第3回> 更新プロンプトを出す](https://qiita.com/satamame/items/36c6761d363ca3894824)
- [シリーズ記事一覧](https://qiita.com/satamame/items/3e0683a1fc6ecd6b66ee#%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA%E8%A8%98%E4%BA%8B%E4%B8%80%E8%A6%A7)

# <第2回> PWA にする

**今回の概要**

- 前回作ったアプリを PWA として配布可能にします。
- vite-plugin-pwa を使うので、主にそれについて説明します。

今回使うソースコードは [GitHub のこちらのコミット](https://github.com/satamame/pscv/tree/qiita.02) にあります。

# vite-plugin-pwa について

Svelte のアプリを PWA にする方法として、[vite-plugin-pwa](https://vite-plugin-pwa.netlify.app) を使うことにしました。

Vite のようなバンドラで出力したファイルはファイル名にハッシュ値がついていたりして、毎回 Service Worker を書き直すのは大変です。
vite-plugin-pwa を使うと、ビルド時に Service Worker やウェブマニフェストの生成もしてくれるので非常に便利です。

Service Worker やウェブマニフェストについては PWA の解説を探して ([こことか](https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps)) みてください。

**vite-plugin-pwa の特徴**

- 自動で Service Worker を生成する
- 自動でウェブマニフェストを生成する
- Service Worker の更新 (アプリの新バージョンの配布) の方法を決められる
- 開発環境で Service Worker の動作を確認できる

# プラグインのインストール

前回作ったプロジェクトに、以下のコマンドでインストールしました。

```
> yarn add vite-plugin-pwa -D
```

インストールされたバージョンは 0.13.1 でした。

つぎに、プラグインを有効化するために vite.config.ts を編集します。

```typescript:vite.config.ts
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import { VitePWA } from 'vite-plugin-pwa'  // これを追加

export default defineConfig({
  base: '/pscv/',
  plugins: [
    svelte(),
    VitePWA(),  // これを追加
  ],
})
```

これでビルド時に vite-plugin-pwa が動くようになりますが、プラグインのオプションについても少し説明します。

# プラグイン・オプション

すべてのオプションについては [公式のドキュメント](https://vite-plugin-pwa.netlify.app/guide/) をご覧ください。
基本的にはデフォルトで大丈夫ですが、`manifest` の `icons` は設定する必要があります。
ここでは私が設定したものを順に見ていきます。

プラグイン・オプションは `VitePWA()` に引数として渡します。
長くなると `plugins` の一覧性が悪くなるので、`const` で定義して渡すことにしました。

```typescript:vite.config.ts (抜粋)
const pwaOptions = {
  strategies: 'generateSW' as 'generateSW', // デフォルトなので不要
  registerType: 'prompt' as 'prompt',       // デフォルトなので不要
  manifest: {
    name: '台本ビューア',
    short_name: 'PSCV',
    description: '台本データ (JSON) を読むためのビューアです。',
    icons: [],              // あとで追加します
    start_url: 'index.html',
    display: 'standalone',  // デフォルトなので不要
    background_color: '#BD913F',
    theme_color: '#916028',
    lang: 'ja',
  },
  includeAssets: ['ui_icon/*.svg'],
  devOptions: {
    enabled: true,
  },
}

export default defineConfig({
  base: '/pscv/',
  plugins: [
    svelte(),
    VitePWA(pwaOptions),  // 引数を追加
  ],
})
```

'デフォルトなので不要' とコメントしているところは、書かなくても同じなのですが知っておいた方が良いオプションなので書いてあります。
また最初の2つに `as ～` として型アサーションしているのは、リテラル型で定義されていて文字列をそのまま入れるとエラーになるからです (もっと良い回避方法があるかも)。

## strategies と registerType

`strategies` と `registerType` については、公式ドキュメントの [Service Worker Strategies And Behaviors](https://vite-plugin-pwa.netlify.app/guide/service-worker-strategies-and-behaviors.html) というページに説明があります。

vite-plugin-pwa はデフォルトでビルド時に Service Worker を自動生成しますが、 `strategies` オプションを 'injectManifest' という値にすることで、自分で Service Worker のロジックを書くことができます。
その場合はビルドしたファイルのリストを、あらかじめ書いておいた Service Worker に埋め込めるようです (試してません)。

`registerType` はアプリに更新があった場合にどのように Service Worker を更新するかを指定します。こちらはビルド時ではなく実行時のふるまいです。
デフォルトの 'prompt' はユーザに許可を得て更新する設定ですが、実際は許可を得る部分を作る必要があります ([→参考](https://vite-plugin-pwa.netlify.app/frameworks/svelte.html#prompt-for-update))。
'autoUpdate' という値にすると、アプリの更新を検知したらすぐに Service Worker の更新が走るようになります。
どちらが良いかはアプリの種類によります。

## manifest

`manifest` は自分で PWA のウェブマニフェストを書く時とほぼ同じ内容で、ビルド時にこれを元にウェブマニフェストのファイルが作られます。

プラグインが適当なデフォルト値を持っているので `icons` 以外は書かなくても何とかなります。
どういうオプションがあるかは [ソースコード](https://github.com/vite-pwa/vite-plugin-pwa/blob/main/src/types.ts) の `ManifestOptions` 型の定義が参考になります。[MDN のページ](https://developer.mozilla.org/ja/docs/Web/Manifest) や [W3C のページ](https://w3c.github.io/manifest/) (或は [その翻訳](https://triple-underscore.github.io/appmanifest-ja.html)) にも情報があります。

`display` は PWA をインストールして実行した時の見た目を決めます。
デフォルトの 'standalone' がネイティブアプリらしくて良いですが、他にどんな値があるかも知っておくと良いです。
MDN に [その説明をしているページ](https://developer.mozilla.org/ja/docs/Web/Manifest/display) があります。

## includeAssets

`includeAssets` でキャッシュするファイルを追加できます ([→公式の説明](https://vite-pwa-org.netlify.app/guide/static-assets.html))。
デフォルトでローカルにキャッシュされるのは、ビルドしたスクリプトとウェブマニフェストで参照しているアイコンです。それ以外の画像ファイルなどは、表示した時点でキャッシュされるようです。

`includeAssets` で指定したファイルはデフォルトで (Service Worker が読み込まれた時点で) キャッシュされるので、[第1回](https://qiita.com/satamame/items/3e0683a1fc6ecd6b66ee) で ui_icon フォルダに入れた svg ファイルなどは追加しておくと良いです。
指定するパスは public フォルダからの相対パスで、正規表現が使えます。

※この設定は GitHub のソースには入っていません。この後のコミットで追加します。

## devOptions

`devOptions` を追加して `enabled` を `true` にすると、開発環境で Service Worker の動きを確認できるようになります。
公式ドキュメントの ['Getting Started'](https://vite-plugin-pwa.netlify.app/guide/) に簡単な説明がありますが、私がいろいろ試した結果、アイコンを正しく設定して一度ビルドしてから preview コマンドを実行 (`yarn preview` など) すると、開発サーバが起動してブラウザが PWA と認識するようです。

# アイコンを準備する

これも vite-plugin-pwa のドキュメントに書いてありますが、[Favicon Generator](https://realfavicongenerator.net/) を使うと1枚の画像から必要なアイコンのセットを生成できます。
元になる画像は 512x512px で、角丸や丸に切り抜かれても良いように図柄を小さめにしました ([→参考](https://w3c.github.io/manifest/#icon-masks))。
Safari 用の mask-icon もその元画像から作れますが、モノクロの素材を別で用意しました。

![02_icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339253/af5f9810-0d3d-ec5b-6ad0-5da429ce4043.png)

## アイコンを参照するようにする

上で作ったアイコンを、ウェブマニフェストや index.html から参照するようにします。

|ファイル名|参照のしかた|
|--|--|
|android-chrome-192x192.png|manifest の icons で参照する|
|android-chrome-512x512.png|manifest の icons で参照する|
|apple-touch-icon.png|index.html で apple-touch-icon として参照する|
|favicon.ico|index.html で icon として参照する|
|safari-pinned-tab.svg|index.html で mask-icon として参照する|

ファイルは public フォルダのサブフォルダに入れて、index.html や manifest から相対パスで参照するようにしました。
具体的な書き方は [PWA Minimal Requirements](https://vite-plugin-pwa.netlify.app/guide/pwa-minimal-requirements.html) にも書いてありますし、私の GitHub を見ていただいても分かると思います (画像のファイル名は変えてます)。

- [index.html のソースコード](https://github.com/satamame/pscv/blob/qiita.02/index.html)
- [vite.config.ts のソースコード](https://github.com/satamame/pscv/blob/qiita.02/vite.config.ts)

`manifest` の `icons` の最後にある `purpose: 'any maskable'` は、OS が角丸や丸に切り抜いて使って良いということだと思いますが、もともと図柄の周辺に余裕を持たせているので 512x512 のアイコンと同じ画像を指定しました。

# robots.txt

PWA には robots.txt も必要なのだそうです ([→参考](https://vite-plugin-pwa.netlify.app/guide/pwa-minimal-requirements.html#search-engines))。
以前 PWA を作った時は robots.txt はなくても問題なかったと思いますが、いちおう作っておきます ([→ソースコード](https://github.com/satamame/pscv/blob/qiita.02/public/robots.txt))。

# 動作確認

ブラウザから PWA として認識されるようになったことを確認するには、いちどビルドしてから preview コマンドを実行します。

```
> yarn build
.....
> yarn preview
yarn run v1.22.19
$ vite preview
  ➜  Local:   http://127.0.0.1:4173/pscv/
  ➜  Network: use --host to expose
```

![02_preview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339253/038de9f3-8662-34f2-3718-1b40b09ef6dc.png)

タブに favicon が表示されていること、開発ツールで Service Worker が認識されていること、タブメニューに「「台本ビューア」をインストール...」が表示されていることが確認できました。
インストールメニューが出ない時は開発ツールで Storage をクリアしてリロードするなど試してみてください。

## スマホで動作確認

スマホで確認するために自サバの (サーバルート)/pscv フォルダに dist フォルダの中身をアップロードしました。
サーバは https でアクセスできるようにする必要があります。

### Android

PWA を認識した時の動作はブラウザによって違いますが、Vivaldi や Chrome では以下のようなプロンプトとメニュー項目が出ました。

![02_android_install_prompt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339253/d1857e15-2e22-c7a7-16cb-14d3a03f3ed4.png)

Firefox ではプロンプトは確認できませんでしたが、インストールメニューは表示されました。

PWA をデバイスにインストールすると、Chrome と Firefox では丸いアイコンになりました (OS のバージョンや設定によると思います)。

![02_android_home.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339253/528e5588-0825-6baf-22f4-5786fda2178a.png)

### iOS

iOS ではプロンプトが出ず、共有メニューから「ホーム画面に追加」を選べば良いようです。
追加されたアイコンをタップすると起動して PWA であることが分かりますが、インストール時や削除時の扱いはショートカットと同じのようです。

![ios_home.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339253/e984ebe6-aeab-c97e-d927-91864bb3a43c.png)

# 参考リンク

- [Vite Plugin PWA](https://vite-plugin-pwa.netlify.app/)
- [プログレッシブウェブアプリ (PWA) | MDN](https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps)
- [ウェブアプリマニフェスト | MDN](https://developer.mozilla.org/ja/docs/Web/Manifest)
- [Web Application Manifest](https://w3c.github.io/manifest/)
- [Web Application Manifest （日本語訳）](https://triple-underscore.github.io/appmanifest-ja.html)
- [Favicon Generator for perfect icons on all browsers](https://realfavicongenerator.net/)