【Vue.js】Vue CLI 3のプロジェクトをPWA化する
久しぶりに、所得税・住民税・事業税・国保の計算をバージョンアップしたいなぁと考え、必要性はそこまでないですがPWA化してみます。
PWAをこのシステムに導入する利点とすれば、キャッシュを使ってオフライン対応ですね。今後は、Webプッシュも使ってようと考えてますが。
環境
- Windows 10 Pro
- vue cli 3
- Node 10.13
PWAとno PWAの違い
まずは、PWAの指定有無で2つのプロジェクトを作成し、その差分からどのようにバージョンアップしていくか計画を立てます。
PWA対応プロジェクトの作成
vueプロジェクトを作成します。
vue create pwa
最初にManualを選択し、あとはPWAを追加しただけです。
Vue CLI v3.4.1
┌───────────────────────────┐
│ Update available: 3.8.4 │
└───────────────────────────┘
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, PWA, Linter
? Pick a linter / formatter config: Basic
? 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, PostCSS, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? (y/N)
</div></code></pre>
<h3 id="tok-2-2">PWA非対応プロジェクト</h3>
<pre><code><div>cue create no-pwa
Manualを選択後、あとはデフォルトで突き進みます。
Vue CLI v3.4.1
┌───────────────────────────┐
│ Update available: 3.8.4 │
└───────────────────────────┘
? Please pick a preset: Manually select features
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)Babel
, Linter
? Pick a linter / formatter config: Basic
? 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, PostCSS, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? (y/N)
PWAとnot PWAの差分
早速、プロジェクトの差分を見てみます。
pacage.jsonの差分
PS C:\develop\workspace\pwa> diff (CAT .\pwa\package.json) (CAT .\no-pwa\package.json)
InputObject SideIndicator
———– ————-
"name": "no-pwa", =>
"name": "pwa", <=
"register-service-worker": "^1.6.2", <=
"@vue/cli-plugin-pwa": "^3.8.0", <=
PWA化には、cli-plugin-pwa
とregister-sercie-worker
の2種が必要みたいです。
main.jsの差分
PS C:\develop\workspace\pwa> diff (CAT .\pwa\src\main.js) (CAT .\no-pwa\src\main.js)
InputObject SideIndicator
———– ————-
import './registerServiceWorker' <=
Service Workerのimportが増えているだけ。
registerServiceWorkerの中身はこちら。(もちろんPWA対応プロジェクトのみ存在)
中身の理解は後回しでとりあえず進めます。
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)
}
})
}
PWA対応プロジェクトの場合、public/img/icons
ディレクトリが新たに作成されてます。
public/img/icons
android-chrome-192×192.png
android-chrome-512×512.png
apple-touch-icon-120×120.png
apple-touch-icon-152×152.png
apple-touch-icon-180×180.png
apple-touch-icon-60×60.png
apple-touch-icon-76×76.png
apple-touch-icon.png
favicon-16×16.png
favicon-32×32.png
msapplication-icon-144×144.png
mstile-150×150.png
safari-pinned-tab.svg
public以下に、manifest.json
とrobots.txt
も。
manifest.json
{
"name": "pwa",
"short_name": "pwa",
"icons": [
{
"src": "./img/icons/android-chrome-192×192.png",
"sizes": "192×192",
"type": "image/png"
},
{
"src": "./img/icons/android-chrome-512×512.png",
"sizes": "512×512",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "standalone",
"background_color": "#000000",
"theme_color": "#4DBA87"
}
img以下のアイコンのパスだったりの情報が含まれてます。
robots.txt
User-agent: *
Disallow:
ホーム画面に配置する際のアイコン等ですね。それにしても多いな。。。
既存プロジェクトをPWA化
それでは早速、所得税・住民税・事業税・国保の計算シミュレーション用プロジェクトにPWAを組み込んでいきます。
cli-plugin-pwaのインストール
こちらのcli-plugin-pwaのドキュメントを参考にインストールします。
https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa#installing-in-an-already-created-project
vueコマンドで行けるようです。
vue add @vue/pwa
C:\develop\workspace\calc-tax-insurance-v3>vue add @vue/pwa
📦 Installing @vue/cli-plugin-pwa…
+ @vue/cli-plugin-pwa@3.8.0
added 47 packages from 23 contributors and audited 45360 packages in 20.298s
found 111 vulnerabilities (66 low, 12 moderate, 32 high, 1 critical)
run `npm audit fix` to fix them, or `npm audit` for details
✔ Successfully installed plugin: @vue/cli-plugin-pwa
🚀 Invoking generator for @vue/cli-plugin-pwa…
📦 Installing additional dependencies…
added 1 package from 1 contributor and audited 45361 packages in 18.931s
found 111 vulnerabilities (66 low, 12 moderate, 32 high, 1 critical)
run `npm audit fix` to fix them, or `npm audit` for details
✔ Successfully invoked generator for plugin: @vue/cli-plugin-pwa
The following files have been updated / added:
public/img/icons/android-chrome-192×192.png
public/img/icons/android-chrome-512×512.png
public/img/icons/apple-touch-icon-120×120.png
public/img/icons/apple-touch-icon-152×152.png
public/img/icons/apple-touch-icon-180×180.png
public/img/icons/apple-touch-icon-60×60.png
public/img/icons/apple-touch-icon-76×76.png
public/img/icons/apple-touch-icon.png
public/img/icons/favicon-16×16.png
public/img/icons/favicon-32×32.png
public/img/icons/msapplication-icon-144×144.png
public/img/icons/mstile-150×150.png
public/img/icons/safari-pinned-tab.svg
public/manifest.json
public/robots.txt
src/registerServiceWorker.js
package-lock.json
package.json
src/main.js
yarn.lock
You should review these changes with git diff and commit them.
必要なファイルの追加やmain.jsまで自動更新してくれました。楽ちん。
※ main.jsの書き換え時に改行コードがLF→CRLFに変更されてしまい、全行差分としてでてきたので最初はびっくりしましたが、LFに変更したらregisterServiceWorker
のimportのみになったのでほっとしました。
動作確認(PC版)
Chromeのdevtool(F12で起動します)で確認してみます。
コンソールにServiceWorkerが登録されたログが出てきました。
Service worker has been registered.
こちらは、ApplicationのService Workers部。
どうやら、プッシュ通知のテストもできるみたいですね。後で試してみましょう。
Cache Storageに、cssやJSなどがキャッシュされているのも確認できます。
Chromeのメニューからアプリのインストールが可能になりました。
インストールしてみます。 アイコンを変更していないので、Vue.jsのアイコンのままですが。。
インストールが終わったら別ウィンドウでシミュレーション画面が表示されました。URLとかの表示もないのでうまくいったようです。タイトル部の色がグリーンなのは、manifest.jsonの「theme_color」が反映されているようですね。
タスクバーにもChromeの横に別にアイコンが出てきました。Vue.jsですが。。。
Chromeのアプリ一覧にも表示されてます!
※ ちなみにシークレットモードでアプリのインストールはできない模様です。ほかのサイトでは出てきてるような画面みたんですが、私の環境では出ませんでした。
シークレットモードでChromeのデバッグツールでApplication-Manifestを見ると、Installabilityに「Page is loaded in an incognito window」と表示されていたのでダメなのかなぁ。
動作確認(Android版)
対象画面にアクセスすると、画面下部にホーム画面に追加のリンクが表示されるのでタップすると、
確認画面がでてきて、追加をタップでホーム画面に追加できました。
PWAによるアプリ化は無事成功です。
アプリの更新を考える
今回の一番の目的は、オフラインでも使えるようにプリキャッシュ・ランタイムキャッシュを利用することです。オフラインでも動作するということは、常に最新のassetやjsで更新されるわけではなくなってくるので、まずはアプリの更新をどのようにするかのプロセスを考慮しないといけません。キャッシュが効いてしまって、いつまでも新しいバージョンに置き換わらないとか笑えないので。
調べて見ると、workbox
というプラグインを使うとよさそうです。
workboxですが、Service Workerの更新に関する動作を設定項目は以下2つの模様。
設定値 | 動作 |
---|---|
skipWaiting | 待機フェーズをスキップし、すぐにacitivateするかどうか。 |
clientsClaim | activeになったらすぐにすべてのクライアントを制御するか |
Service Workerのライフサイクルに関連するみたいですが、説明をみても正直よく理解できませぬ。。。
とりあえず、すべてのパターンでテストしてみるのが手っ取り早い!
ということで試してみました。
テストの流れとしては、main.jsに以下のようなコンソールログを仕込み、ログの出方でアプリが更新されたかを確認します。
console.log('1 deploy.') // 初回はこちらを有効
// console.log('2 deploy.') // アプリ更新時はこちらを有効
workboxの設定
まずはworkboxを使う準備をするため、vue.config.js
にwebpack用の設定を追加します。
workboxでは、generateSW
とinjectManifest
の2つのモードがあります。injectManifest
の方がWebプッシュなど他のService Workerの機能を使えるなど拡張できるのですが、キャッシュだけ使いたいなどの簡単な使い方であればgenerateSW
でいいみたいです。
本当はWebプッシュ使いたいけど、まずは簡単に組み込めるgenerateSW
で進めます。
vue.config.js
const { GenerateSW } = require('workbox-webpack-plugin')
module.exports = {
…
pwa: {
name: '所得税・住民税・事業税・国保の計算【kawadeblog】',
themeColor: '#efe37d',
appleMobileWebAppCapable: 'yes',
appleMobileWebAppStatusBarStyle: 'black',
// configure the workbox plugin
workboxPluginMode: 'GenerateSW',
workboxOptions: {
// swSrc is required in InjectManifest mode.
// swSrc: 'dev/sw.js'
// …other Workbox options…
}
},
configureWebpack: config => {
config.plugins.push(
new GenerateSW({
cacheId: 'calc-tax-insurance-v3',
skipWaiting: false,
clientsClaim: false
})
)
}
}
nameとかthemeColorはmanifest.jsonとだぶるようですが、念のためどちらにも同じ値を設定しました。優先されるのがどちらかも不明でした。。
registerServiceWorker.js
Service Workerのライフサイクルにより、どのようなタイミングでイベントが発火しどこでアプリを更新するかですが、cli-plugin-pwa
をインストールするとすでに以下のJSが自動で生成されています。こちらで、各イベントのコンソールログ出力が組み込まれているので、このログ出力を確認しながら検証を進めます。
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)
}
})
}
skipWaiting: false, clientClaim: false
まずはデフォルトで。
初回アクセス
ServiceWokerの登録が行われ、準備が整った旨のログが出力されます。
1 deploy.
Service worker has been registered.
New content is downloading.
Content has been cached for offline use.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
※ イベントの順序
registered
→ updateFound
→ cached
→ ready
デバッグツールでみてみると、ServiceWorkerが登録されたことを確認できます。
2回目のアクセス
すでにキャッシュされているのと新しいバージョンはないので、その分のイベントがなくなっただけですがreadyとregisteredの順序が逆になりました。
1 deploy.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
Service worker has been registered.
※ イベントの順序
ready
→ registered
アプリを更新後、初回アクセス
メッセージが変わりました。新しいService Workerがダウンロードされ更新されたログが出てきました。ただ、ログは更新後のログではなく古い方が出力されています。
1 deploy.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
Service worker has been registered.
New content is downloading.
New content is available; please refresh
※ イベントの順序
ready
→ registered
→ updatefound
→ updated
デバッグツールで見てみると、新しいService Workerが待ち状態になってます。アプリはまだ更新されていません。
アプリを更新後、2回目のアクセス
ブラウザを通常のリロード、スーパーリロードの双方で再読み込みしてみましたが、コンソールの表示は前回と同じ。待機状態のまま。
1 deploy.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
Service worker has been registered.
New content is downloading.
New content is available; please refresh
※ イベントの順序
ready
→ registered
→ updatefound
→ updated
デバッグツールでも確認しましたが、待機状態は変わらずです。
ブラウザを再起動後にアクセス
最後に、ブラウザをいったん落としてから再アクセスします。
すると、Service Workerの登録のみのメッセージに代わりました。main.jsに仕込んでいるログも更新後のログが出力されています。無事にアプリの更新ができたようです。
2 deploy.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
Service worker has been registered.
※ イベントの順序
ready
→ registered
デバッグツールでみてみましょう。
Service Wokerのバージョン?みたいのが#4865
→#4875
に代わり無事に更新されました!
結論
どうもskipWatingとclientsClaimがどちらもfalseだと、アプリケーションの更新は自動でダウンロードまでしてくれますが、ブラウザの再読み込みでは更新されずブラウザの再起動が必要みたいです。
最近はタブを開きっぱなしとかよくあるので、この設定だと更新がいつまでもできずじまいになりそうです。
skipWaiting: true, clientClaim: false
次は、skipWaiting
をtrueにしてテストしてみます。
初回アクセス
初回はskipWaiting=falseの場合と同じ。
1 deploy.
Service worker has been registered.
New content is downloading.
Content has been cached for offline use.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
※ イベントの順序
registered
→ updateFound
→ cached
→ ready
デバッグツールの表示
2回目のアクセス
updateFound
が消えました。
1 deploy.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
Service worker has been registered.
※ イベントの順序
ready
→ registered
デバッグツールは初回アクセスと同じ結果。
アプリを更新後、初回アクセス
新バージョンの更新が行われます。skipWating=falseの場合とイベント発火は同じです。
1 deploy.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
Service worker has been registered.
New content is downloading.
New content is available; please refresh.
※ イベントの順序
ready
→ registered
→ updatefound
→ updated
デバッグツールで違いが出ました。待ち状態ではなくなっています。
Service Workerのバージョンも#4904
→#4906
となってますが、ログは1 deploy.
と出力されているのでアプリは古いバージョンで動作していることになります。
アプリを更新後、2回目のアクセス
お!!アプリも更新されました。
2 deploy.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
Service worker has been registered.
※ イベントの順序
ready
→ registered
結論
skipWaiting=true
だとService Workerの更新待ち状態が言葉通りスキップされ、新バージョンの適用もブラウザ再起動ではなくリロードでできるようになりした。skipWaitingは待ち状態をスキップすることにより、新バージョンへ更新をブラウザの再起動が不要で次回アクセス時に更新まで進めることができるようです。おそらくプログラムで制御できるのでしょうけど、一旦このオプションは有効にしていた方がよさそうです。
skipWaiting: false, clientsClaim: true
それではどんどん行ってみます
つぎは、clientsClaim=trueにした場合です。説明からすると、最新バージョンが当たったら即座に画面に新バージョンが適用されるということなのでしょうか。。(結果的によくわかりませんでした!)
初回アクセス
初回はいつものやつ。
1 deploy.
Service worker has been registered.
New content is downloading.
Content has been cached for offline use.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
デバッグツール
2回目のアクセス
いつものヤツ
1 deploy.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
Service worker has been registered.
アプリを更新後、初回アクセス
更新時のイベント発火も同じ。
1 deploy.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
Service worker has been registered.
New content is downloading.
New content is available; please refresh.
skipWaiting=falseにしたので、待ち状態になっています。
アプリを更新後、2回目のアクセス
とりあえず、通常のリロードでは変わりありません。アプリも古いまま。
1 deploy.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
Service worker has been registered.
New content is downloading.
New content is available; please refresh.
相変わらず待ち状態
ここからはskipWaiting=falseの時と同じ。。ブラウザを再起動するまで新バージョンは適用されませんでした。
結論
この簡単なテストでは違いが出ませんでした。
skipWaiting: true, clientClaim: true
最後に、両方のオプションをtrueにした場合です。
初回アクセス
初回はいつも…
1 deploy.
Service worker has been registered.
New content is downloading.
Content has been cached for offline use.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
2回目のアクセス
いつも…
1 deploy.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
Service worker has been registered.
アプリを更新後、初回アクセス
更新時のイベント発火も同じ。。あれ?私の予想では、ここでは新バージョンが適用されてるとばかり思ってたのですが、まだ古いバージョンです。
1 deploy.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
Service worker has been registered.
New content is downloading.
New content is available; please refresh.
アプリを更新後、2回目のアクセス
2回目でやっと更新されました。
2 deploy.
App is being served from cache by a service worker.
For more details, visit https://goo.gl/AFskqB
Service worker has been registered.
結論
うーん、skipWatingの動作はわかりましたが、clientsClaimはよくわからずでした。。。私の予想では、skipWaiting=trueであれば、updated
イベント発火後に即座に新バージョンが適用されるのではと思ったのですが。。。まだまだ、理解が足りないですね。
もっとドキュメントを読みライフサイクルを理解しないといけなそうです。
次回に続く…
もっと簡単にできるかと思いましたが、一つ一つ理解しようと進めると時間がかかります。他の記事とかみると、皆さんスルっとすすめてるのでスゴイですねぇ。
結構ながくなってしまったので、プリキャッシュとランタイムキャッシュを使ってオフライン対応する記事は次回に持ち越しです!
ディスカッション
コメント一覧
まだ、コメントがありません