はじめに
本記事は Vue連載2024 3日目の記事です。
こんにちは。Technology Innovation Group (TIG) 所属の、大岩(@920OJ)です。
本記事では、Vue3で作ったWebサイトをPWA(Progressive Web Apps)化する方法を紹介します。
これまでも多くの方がVueで作られたWebサイトをPWA化する方法について発信されていますが、Webpackプラグインを導入するものであったり、メンテナスモードに入ってしまった @vue/cli-plugin-pwa
を利用されているものが多いため、ここでは改めて2024年現在どのように実装すれば良いか紹介します。
PWA技術のいま
Progressive Web Apps、直訳すると「革新的なWebアプリ」です。かっこいいですね。
誤解を恐れずに言えば、Webページを各プラットフォームのネイティブアプリのように表示・利用できるようにする技術です。ただネイティブアプリのように振る舞うだけでなく、ServiceWorkerを併用することでPush通知を受信可能にしたり、オフライン時にもキャッシュから表示できるようにしたり、バックグラウンド処理をはじめとする様々な機能を利用できます。
Google Trendsによると、PWAは2017年頃から盛り上がりを見せ、2020年頃に一度ピークが落ち着いたものの、最近じわじわと話題が盛り返しているようです。2017年〜2020年頃にPWAに触れたものの、それっきり追っていないという方も多いのではないでしょうか?
個人的な主観とはなりますが、一時期PWAが次世代のアプリの姿として持て囃されていたものの、日本の約半数のシェアを占めるiOSがPWAサポートに消極的であったことが採用の障壁となり話題も落ち着いたものと推測します。
AndroidのGoogle ChromeにおいてPWAサポートが始まったのが2015年。一方で、iOS SafariがWeb App ManifestとService Workerのサポートを始めたのがiOS 11.3リリースの2018年3月です。しかしその当時も限定的なサポートにとどまり、別ドメインの遷移がPWA内で完結しない、カメラが起動できない等の問題が発生していました。
参考: https://qiita.com/zprodev/items/e5db743727c5722874cb
しかし、iOSのバージョンが上がるにつれてそれらの問題も徐々に改善していきます。特に昨今一番のアップデートとして、バックグラウンドでのPush通知に対応したことが挙げられます。2023年3月27日リリースのiOS16.4から、Service WorkerでバックグラウンドのPush通知を受信する ServiceWorkerRegistration.showNotification()
に対応しました。これにより、iOSでのPWA実現の一番の障壁だったPush通知がそれなりに動くようになり、一層PWAが完成に近づいたと言えるでしょう。
https://developer.mozilla.org/ja/docs/Web/API/ServiceWorkerRegistration/showNotification
ただし今年頭にネガティブなニュースもあり、AppleはiOS17.4のβ版にてEU圏内においてPWAサポートを削除すると発表したことがあります。理由はEUのデジタル市場法(DMA)への準拠により、iOSでのPWAをWebkitエンジン以外のレンダリングエンジンに対応させることによりセキュリティ・プライバシーの懸念を引き起こすため、とのことです。これは後にユーザからの反発によって撤回されたようですが、App Storeを主体とするiOSの収益モデルと競合し得る技術を忌避しようとする動きはこれからも警戒する必要があると考えます。
……と長々と書いてしまいましたが、結局のところ伝えたいのは「iOSでもやっとPWAがちゃんと使えるようになってきたぜ!」です。早速その威力を体感するためにも、Vue3でサクッとアプリを作り、PWA化してみましょう。
Vite PWA を使って PWA化してみよう
その前に、今回利用する Vite PWA について紹介します。
https://vite-pwa-org.netlify.app/
最近のVue3は、開発サーバやビルドツールとしてViteを利用しています。このViteのプラグインとして、PWAに必要なmanifestファイルやServiceWorkerのスクリプトファイルを出力してくれるのが Vite PWA です。
Vite PWAは、既存のアプリケーションをほとんど設定不要でPWA(プログレッシブウェブアプリ)に変換するのをサポートします。一般的な使用例に適した実用的なデフォルト設定があらかじめ用意されています。
vite-plugin-pwaプラグインでは以下のことが可能です:
- Webアプリケーションのマニフェストを生成し、それをエントリーポイントに追加する(マニフェスト生成についてはセットアップガイドを参照してください)。
- strategies オプションを使用してサービスワーカーを生成する(詳細は「サービスワーカーの戦略」セクションを参照してください)。
- ブラウザにサービスワーカーを登録するスクリプトを生成する(「サービスワーカーを登録する」セクションを参照してください)。
https://vite-pwa-org.netlify.app/guide/#vite-pwa
本来であれば自分で用意しないといけない複雑な設定ファイルを、Viteのコンフィグファイルにオプションを記載するだけで自動で出力してくれるというすぐれものです。
PWAはウェブマニフェストファイルとServiceWorkerのファイルから成り立ちます。前者はPWAをインストールする際に使われるメタ情報的な役割で、後者はキャッシュ対応やバックグラウンド処理等のJSコードです。Vite PWAはこの2つのファイルを自動生成してくれます。
アプリを準備する
まずはVue3で作成したアプリをPWA化してみましょう。ここでは、簡易的に天気予報APIを叩いて各地の天気予報を取得する簡単なアプリを用意しました。
ソースコードとデモサイトは以下に配置しています。
- GitHub: https://github.com/920oj/pwa-example-20241126/tree/main/vue-example
- デモサイト: https://pwa-example-20241126.vercel.app/
今回は以下をPWAで実現できるようにしてみます。
- PWAをインストールできるようにする
- プリキャッシュでオフライン対応する
- 外部リソースをキャッシュする
Vite PWA のインストール
まずはプラグインをインストールします。
npm install -D vite-plugin-pwa |
次に、 vite.config.ts
に以下を記載します。
// (前略) |
最後に npm run dev
で開発サーバを立ち上げてみましょう。立ち上がったら、F12で開発者ツールを開き、「Application」タブから「Service workers」を開いてください。
このように dev-sw.js
がService Workerとして読み込まれている事がわかります。
ターミナルのほうを見てみると、 sw.js
と workbox-xxxxxxxx.js
が自動生成されていることがわかります。
PWAをインストールできるようにする(Webマニフェスト設定)
上記設定を実施しても、Webマニフェストの設定が不十分であるためPWAとしてインストールできません。Webマニフェスト用の設定を書く必要がありますが、その前にPWAのアイコンを用意しましょう。ただし、Webマニフェストで指定する必要があるアイコンは種類が非常に多く、すべてを手作業で用意するには骨が折れる作業です。
そこで、Vite PWA にはアセットを自動生成してくれるツールが用意されています。
npm i @vite-pwa/assets-generator -D |
インストール後、 pwa-assets.config.ts
として以下を記載します。今回は public/icon.png
にアプリアイコンとして利用したいファイルを配置しました。
import { defineConfig } from '@vite-pwa/assets-generator/config' |
記載できたら、 npx pwa-assets-generator --preset minimal-2023
で実行しましょう。
正しく書き出せていそうです。
後は vite.config.ts
に設定を記載するだけです。
VitePWA({ |
manifest
にオプションを記載していきます。ブラウザごとに対応している値は異なるので、MDNのサイトで対応状況を確認しておくと良いでしょう。
https://developer.mozilla.org/en-US/docs/Web/Manifest
保存したら、再度開発サーバを立ち上げてみましょう。アドレスバー横にアイコンが表示され、クリックするとPWAのインストールダイアログが表示されます。
iPhoneでも試してみましょう。cloudflaredを使って外からアクセスできるようにして、ページを開いて「ホーム画面に追加」をタップします。
問題なく表示できていますね。
プリキャッシュを導入する
続いて、PWAをオフライン状態でも機能するように、プリキャッシュ設定を導入しましょう。ここまではWebマニフェストと呼ばれるJSONファイルによって機能が動作していましたが、ここからはServiceWorkerのコードによって動作します。本来であればServiceWorkerのソースコードを一から書いていく必要がありますが、Vite PWAでは generateSW
(generate ServiceWorkerの略)オプションを有効化することで、ServiceWorkerのコードを自動生成してくれます。
実際にオプションを記載していく前に、Workboxというライブラリについて説明する必要があります。
Workboxとは、ServiceWorkerをより簡単に制御できるようにGoogleが開発しているモジュール群です。
https://developer.chrome.com/docs/workbox/what-is-workbox?hl=ja
Workboxには以下のモジュールが用意されています。
# | モジュール名 | 機能 |
---|---|---|
1 | workbox-routing | リクエスト |
2 | workbox-strategies | キャッシュ戦略 |
3 | workbox-precaching | プリキャッシュ設定の導入 |
4 | workbox-expiration | キャッシュの管理 |
5 | workbox-window | ServiceWorkerの登録・更新作業 |
6 | workbox-build | ServiceWorkerの自動生成 |
Vite PWAでは内部的にこの workbox-build
を呼んでおり、vite.config.ts
に記載した設定値をWorkboxモジュールに渡すことでPWAに必要なServiceWorkerのソースコードを生成しています。そのため、プリキャッシュの導入においてもVite PWAがWorkboxを使ってよしなに生成してくれるので、我々は設定値をコンフィグファイルに記載するだけで良いのです。
Vite PWAでは、 globePatterns
オプションを変更することでVueアプリのキャッシュに必要なファイルをプリキャッシュできます。一度この設定をしないままビルドをしてみて、書き出されたファイルの拡張子を指定するとやりやすいと思います。
$ find . -type f -name "*.*" | sed 's/.*\.//' | sort -u |
今回の場合、css、html、ico、js、png、webmanifestを指定すると良さそうです。globパターンを記載します。
VitePWA({ |
この状態でビルドすると、ターミナルにVite PWAが書き出されたファイルをVite PWAが探して、プリキャッシュに含めてくれます。
PWA v0.21.0 |
書き出された dist/sw.js
を見てみると、 e.precacheAndRoute()
の引数に、書き出されたファイルが一覧されているようです。
では実際に試してみましょう。ビルドした後、先ほどと同様 cloudflared を使ってhttps通信でスマホからアクセスし、ホーム画面に追加。PWAアプリを一度開き、その後オフライン状態にして再度開くと……
オフライン状態でも表示できました!しかし、外部リソース(ここでは天気予報APIやそこから取得した画像ファイル)からの取得は失敗してしまっていますね。
外部リソースのキャッシュを有効化する
先程も軽く触れましたが、ServiceWorkerはブラウザとネットワーク間に入り仮想キャッシュとして機能します。すなわち、ネットワークへのアクセス(fetch)が発生するたびにServiceWorkerがそれをハンドリングし、そのリソースをキャッシュから返すか、ネットワークへアクセスしに行くかを判断し挙動を変えることができます。
このキャッシュ戦略にはいくつかあり、workbox-strategiesの機能で切り替えることができます。以下にその種類を簡単に記載しますが、詳細はweb.davの記事を参考にしてください。
- Network First(ネットワーク優先)
- まずはネットワークを見に行き、最新のデータを返す。リクエストが失敗したり、時間がかかりすぎたりすると、最新のキャッシュを返す。
- Cache First(キャッシュ優先)
- まずは最新のキャッシュを返す。キャッシュが存在しない場合やキャッシュに不備がある場合はネットワークを見に行く。
- Stale While Revalidate(SWR)
- まずは最新のキャッシュを返すが、その裏でネットワークを見に行き、キャッシュを最新化する。
今回はNetwork Firstで実装してみようと思います。Vite PWAで外部リソースのキャッシュを有効化するには、 runtimeCaching
オプションを利用します。また、今回は tsukumijima 氏によって公開されている「天気予報API(livedoor 天気互換)」のドメイン weather.tsukumijima.net
と、気象庁のドメイン www.jma.go.jp
から外部リソースが読み込まれているので、この2ドメインをruntimeCacheの対象としましょう。
// (前略) |
では実際に試してみましょう。build後、PWAをインストールし直してみます。
今回はオフライン状態でもAPIリクエストをキャッシュできました!
もう一つのモード: injectManifest について
さて、ここまで generateSW
を指定しServiceWorkerファイルを自動生成する方法で説明してきましたが、もう一つ injectManifest
というモードがあります。これは、自前で用意したServiceWorkerのコードにプリキャッシュや外部リソースのキャッシュに対応させるコードをWorkboxが挿入(inject)してくれるモードです。
https://vite-pwa-org.netlify.app/workbox/inject-manifest.html
本記事では尺(?)の都合上触れませんでしたが、例えばPush通知を受信可能とするには別途自前でServiceWorkerコードを作成する必要があります。 generateSW
ではServiceWorkerコードが自動生成されてしまうため、組み入れることができません。これを解決するのが injectManifest
モードです。
Chrome for Developersのウェブサイトでは、各モードの選択を以下のように実施するべきとしています。
- generateSWを使用するケース
- ファイルをプリキャッシュしたい場合
- 単純なランタイムキャッシュ(外部リソースのキャッシュ)が必要な場合
- injectManifestを使用するケース
- ServiceWorkerをより詳細に制御したい場合
- ファイルをプリキャッシュしたい場合
- ルーティングおよびキャッシュ戦略をカスタマイズする必要がある場合
- ServiceWorkerを他のプラットフォーム機能(Web Push等)と併用する場合
injectManifest
モードを利用したVite PWAの設定についてもある程度知見が溜まってきたので、別途記事にできればと思います。
おわりに
色々ドキュメントを読み解いていくにつれて、やはりPWA技術は難しいと感じました。その難解さを簡易なインターフェースに抽象化してくれるVite PWAは非常に助かるプラグインですが、どうしてもその裏側で使われているライブラリやPWAの仕様をちゃんと把握しておかなければ嵌まってしまう落とし穴が多いように思います。
この記事に書ききれなかった様々な試行錯誤も沢山ストックしているので、また別の機会に記事化できればと思います。それでは!
おまけ
色々調べるうえで役に立ったWebサイトを以下に記載します。Google系のドキュメントは機械翻訳されており、誤った訳も多いので、なるべく英語のドキュメントを読むことをおすすめします。
- ServiceWorker についての理解
- 初心者向けのServiceWorkerの図解
- プリキャッシュ対応