はじめに
本記事では、 Vue.js で PWA(Progressive Web App)を開発する方法を説明します。プロジェクトの作成には Vue CLI を使用します。
参考までに、今回使用した主なライブラリ等のバージョンを記載します。
- Node.js v13.2.0
- yarn 1.17.3
- Vue CLI 4.1.1
環境構築
Vue.js をインストールする方法はいくつかありますが、 PWA 関係の設定を簡単にするために、今回は Vue CLI を使用します。
はじめに、Vue CLI をインストールしてプロジェクトを作成する手順を説明します。
Vue CLIをインストール
Vue CLI は npm 経由でインストールすることができます。
1 | $ npm install -g @vue/cli |
Vue CLI のバージョンを指定する場合は以下のとおりです。
1 | $ npm install @vue/cli@4.1.1 |
プロジェクトを作成
インストールが完了したら、 Vue CLI のコマンドでプロジェクトを作成します。
1 | $ vue create vue-pwa-sample |
上記のコマンドを実行すると以下のような表示に切り替わります。
1 2 3 4 5 | Vue CLI v4.1.1 ? Please pick a preset: default (babel, eslint) ❯ Manually select features |
Manually select features にカーソルを合わせてEnterキーを押下します。すると、機能を選択する表示に切り替わるので、以下のように選択してEnterキーを押下します。
1 2 3 4 5 6 7 8 9 10 11 | ? Please pick a preset: Manually select features ? Check the features needed for your project: ◉ Babel ◉ TypeScript ◉ Progressive Web App (PWA) Support ◉ Router ◉ Vuex ❯◯ CSS Pre-processors ◉ Linter / Formatter ◯ Unit Testing ◯ E2E Testing |
その他の設定に関しては、自分の好みに合わせて選択してください。参考までに、今回は以下のように選択しました。(Vue.js+TypeScriptな環境構築については以前紹介していますので、よろしければこちらもあわせてご覧ください)
1 2 3 4 5 6 7 8 9 | ? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, TS, PWA, Router, Vuex, Linter ? Use class-style component syntax? Yes ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes ? Use history mode for router? (Requires proper server setup for index fallback in production) Yes ? Pick a linter / formatter config: Prettier ? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save ? Where do you prefer placing config for Babel, ESLint, etc.? In package.json ? Save this as a preset for future projects? (y/N) n |
インストールが完了したら、環境構築は完了です。
PWA選択時に追加されるファイル
Progressive Web App (PWA) Support オプションを選択した場合、以下の npm package が追加されます。
- “register-service-worker”: “^1.6.2”,
- “@vue/cli-plugin-pwa”: “^4.2.0”,
また、 Service Worker 関連で registerServiceWorker.ts が追加されます。registerServiceWorker.ts の中身は以下のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | /* eslint-disable no-console */ import { register } from "register-service-worker"; if (process.env.NODE_ENV === "production") { register(`${process.env.BASE_URL}service-worker.js`, { ready() { console.log( "App is being served from cache by a service worker.\n" + "For more details, visit https://goo.gl/AFskqB" ); }, registered() { console.log("Service worker has been registered."); }, cached() { console.log("Content has been cached for offline use."); }, updatefound() { console.log("New content is downloading."); }, updated() { console.log("New content is available; please refresh."); }, offline() { console.log( "No internet connection found. App is running in offline mode." ); }, error(error) { console.error("Error during service worker registration:", error); } }); } |
その他、 public ディレクトリにアイコン画像一式と robots.txt が追加されます。
動作確認時の注意
通常、開発時にブラウザで確認するために yarn run serve でサーバーを起動すると思いますが、この方法では後述する Service Worker が動作しません。そのため、 Service Worker の機能を確認するためには、一度 yarn run build でビルドし、 Firebase Hosting などに適宜デプロイして確認してください。
Service Worker
はじめに、ブラウザによっては Service Worker の機能が利用できなかったり、制限されることがあるということを知っておく必要があります。特に iOS 端末での Service Worker を含む PWA の対応はまだまだなので、テストを行うには Android 端末のほうが良いと思います。
また、 Service Worker は HTTPS で通信するページでしか登録できないため、サーバ側が HTTPS に対応している必要があります。
Service Workerとは
Service Worker は、ブラウザがWebページとは別にバックグラウンドで実行するスクリプトです。主にPUSH通知やバックグラウンド同期のために用いられます。
Service Walker について押さえておくべきポイントは以下の4点です。
- Service Worker は JavaScript Worker の一種なので DOM を直接操作することはできない。その代わりに、制御するページとの通信を postMessage インターフェースに応答することで行い、それにより DOM を操作することができる。
- ページからのネットワークリクエストを制御することができる。
- 使用されていない間 Service Worker は終了しているので、 onfetch や onmessage ハンドラ内でグローバルに定義したステートを頼りに実行することはできない。そのため、 ServiceWorker の複数のライフサイクルで値を共有したい場合は、 indexDB API にアクセスする必要がある。
- Service Worker は JavaScript の Promises を多用する。
Service Workerのライフサイクル
Service Worker 以下のようなライフサイクルで動作します。
Installing
Service Worker をインストールするには、Webページの JavaScript から Service Worker を登録する必要があります。 Service Worker を登録すると、ブラウザはバックグラウンドでインストールを行います。このとき、一般的にはいくつかの静的なアセットがキャッシュされます。
もし、1つでもファイルのキャッシュに失敗した場合、 Service Worker はアクティベートされません(インストール失敗)。その場合、再度、 Service Worker を登録することでインストールをやり直すことができます。
Activated
インストール完了後、アクティベーション処理が行われます。アクティベーションステップは古いキャッシュの処理などを行うのに最適な段階です。
アクティベーションステップが終わると、 Service Worker はそのスコープ内の全てのページを制御します。ただし、 Service Worker を登録したページはこの時点ではまだ制御されず、次に読み込まれた時に制御されるようになります。
Terminated と fetch/message
Service Worker が制御を行っている間、Service Worker には2つの状態があります。1つ目はメモリ節約のために終了している状態で、2つ目はページ内で起こったネットワークリクエストやメッセージに対して、 fetch イベントもしくは、 message イベントの処理を行っている状態です。
Cacheの設定
Service Worker でのキャッシュには PreCache と RuntimeCache の2種類の方法があります。これらについて説明します。
Pre Cache
PreCache はアプリのアセット(js、css、image等)をキャッシュする際に利用します。こちらはアプリのドメインと同じ場所に配置してあるものが対象です。
Runtime Cache
API のレスポンス、サードパーティ製の JavaScript ライブラリ、 CDN から配信されるアセット等をキャッシュしたい場合は Runtime Cache を利用します。
Workboxとは
Vue CLI でPWAを有効にしたプロジェクトを作成した場合、 Service Worker を自前でゴリゴリ実装しなくても Runtime Cache を利用することができます。これには、 Workbox というライブラリが利用されます。
このライブラリは、 Google が 提供するライブラリで、アセットのキャッシュや PWA の全ての機能を簡単に使えるようになります。
ただし、実際には Workbox を直接利用するのではなく、 vue.config.js に設定を書くことで Runtime Cache の挙動をカスタマイズしていきます。 PWA の設定についてはこちらをご覧ください。
Pre Cacheの設定
Vue CLI でプロジェクトを作成した場合、 PreCache については特に設定しなくても自動でキャッシュされます。
Runtime Cacheの設定
Runtime Cache の設定は、 vue.config.js に書いていきます。まず、サンプルを記載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | module.exports = { pwa: { workboxOptions: { runtimeCaching: [ { urlPattern: /\/api\/.+/, handler: 'networkFirst', options: { cacheName: 'api', expiration: { maxAgeSeconds: 60 * 60 * 24 }, cacheableResponse: { statuses: [0, 200] } } } ] } }, } |
上記のサンプルは、APIのレスポンスをキャッシュする想定です。個別のパラメーターについて見ていきます。
urlPattern
urlPattern はキャッシュ対象の URL を正規表現で書きます。この際、後述する handler も合わせて書く必要があります。
handler
handler には以下のいずれかを選んで書きます。
- staleWhileRevalidate
- cacheFirst
- cacheOnly
- networkFirst
- networkOnly
staleWhileRevalidate はキャッシュとネットワークの両方に要求が行き、ネットワークのレスポンスでキャッシュが上書きされる設定です。
cacheFirst は一度キャッシュされると更新されなくなる設定で、長期間変化しないデータに向いた設定です。デフォルトではスターテスコード200と不透明な応答(CROSをサポートしないクロスオリジンサーバーからの応答)をキャッシュします。
caches.match() を直接呼び出すより cacheOnly を利用する利点は、キャッシュ設定を使用し、RequestWrapper で定義されたプラグインをトリガーできる点です。
デフォルトの networkFirst では、ステータスコード200と不透明な応答(CROSをサポートしないクロスオリジンサーバーからの応答)をキャッシュします。
fetch() を直接呼び出すよりnetworkOnly を使用する利点は、RequestWrapper で定義されたプラグインをトリガーする点です。
A2HS
ここでは、 PWA の代表的な機能の1つである、 A2HS(Add To Home Screen)について説明します。
A2HSとは
ご存じの方も多いと思いますので簡単に説明すると、Webページをアプリのようにインストールし、ホーム画面に追加する機能です。
OS やブラウザによって使える機能がかなり変わってくるので、 Android の Chrome を前提に説明します。
A2HS を導入するには Web App Manifest を作成し、 Service Worker が登録されている必要があります。
Vue CLI で PWA を導入した場合は、以下のように vue.config.js に設定を書いていきます。
1 2 3 4 5 6 7 8 9 10 11 12 | module.exports = { pwa: { name: 'My Application', themeColor: '#4169E1', appleMobileWebAppCapable: 'yes', appleMobileWebAppStatusBarStyle: 'black', workboxPluginMode: 'InjectManifest', workboxOptions: { // Service Worker は必須 swSrc: 'dev/sw.js', } } |
それ以外の場合は、以下のような manifest.json を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | { "short_name": "MyApp", "name": "My Application", "icons": [ { "src": "/images/icons-192.png", "type": "image/png", "sizes": "192x192" }, { "src": "/images/icons-512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": ".", "background_color": "#F0FFFF", "display": "standalone", "theme_color": "#4169E1" } |
ちなみに、 yarn run build でビルドした際に出力される manifest.json は以下のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | { "name": "vue-pwa-sample", "short_name": "vue-pwa-sample", "theme_color": "#4DBA87", "icons": [ { "src": "./img/icons/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "./img/icons/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }, { "src": "./img/icons/android-chrome-maskable-192x192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { "src": "./img/icons/android-chrome-maskable-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } ], "start_url": ".", "display": "standalone", "background_color": "#000000" } |
A2HS を有効にしていて、ブラウザが A2HS に対応している場合、画面に以下のようなバナーが表示されます(AndroidのChromeで見た場合)。
Web App Manifest
manifest.json で設定できる項目は多岐にわたりますが、ここではよく使われるであろう項目に絞って説明します。その他の項目についてはこちらをご覧ください。
background_color
スタイルシートが読み込まれる前にマニフェストが有効になった場合、ブラウザが簡易的に描画する際の背景色として使われます。
1 | "background_color": "red" |
description
Webサイトの説明文を記載します。
1 | "description": "このサイトの説明" |
display
アプリの向きを指定します。有効な値は以下のとおりです。
fullscreen
利用可能な表示エリアがすべて使用され、ユーザーエージェントは表示されません。
standalone
スタンドアローンアプリケーションのようなスタイルになります。このモードでは、ユーザーエージェントはナビゲーションの制御のためのUIを非表示にしますが、ステータスバーなどのUIを表示することはできます。
minimal-ui
standalone に近いUIになりますが、ナビゲーション制御のための最小限のUI要素(ブラウザによってことなります)が表示されます。
browser(デフォルト)
従来のブラウザタブまたは、新しいウィンドウで開きます。
1 | "display": "standalone" |
icons
アプリケーションのアイコン画像をサイズごとに設定します。
sizes
画像の幅と高さを区切り文字込で指定。
src
画像ファイルのパス。
type
メディアタイプのヒント。
purpose
画像の目的を定義します。値は、 badge 、 maskable 、 any (デフォルト)のいずれかです。
1 2 3 4 5 6 7 8 9 10 11 | "icons": [ { "src": "icon/icon.jpg", "sizes": "48x48", "type": "image/jpeg" }, { "src": "icon/icon.ico", "sizes": "72x72 96x96" }, ] |
orientation
ページの向きを定義します。値は以下のとおりです。一般的には portrait がよく使われるのではないでしょうか。
- any
- natural
- landscape
- landscape-primary
- landscape-secondary
- portrait
- portrait-primary
- portrait-secondary
1 | "orientation": "landscape" |
インストール可能なネイティブアプリケーションを配列で定義します。
platform
アプリケーションのプラットフォーム。
url
アプリケーションのURL。
id
platform 上でアプリケーションを見つけるための固有の ID 。
1 2 3 4 5 6 7 8 9 10 | "related_applications": [ { "platform": "play", "url": "https://play.google.com/store/apps/details?id=com.example.app", "id": "com.example.app" }, { "platform": "itunes", "url": "https://itunes.apple.com/app/example-app/id123456789" } ] |
theme_color
アプリケーションのテーマカラーを定義します。
1 | "theme_color": "blue" |
さいごに
今回は、 Vue.js で PWA を開発する方法を Service Worker と A2HS に注目して紹介しました。この2つは PWA の機能の中でも特にユーザー体験を向上させられると思いますので、一度試してみていただければと思います。
参考
- Service Worker の紹介
- 【Vue.js】Vue CLI 3のプロジェクトをPWA化する
- 【Vue.js】PWAでプリキャッシュ・ランタイムキャッシュを使いオフライン対応
- ウェブアプリマニフェスト