はじめに
先月、フロントエンドエンジニア界隈ではリニューアルした日経電子版が高速すぎてヤバイ件に注目が集まりましたね。
今自分で注目しているVue.jsと、PWAでSPAをつくるためのことはじめを記事にしようと思います。
1. PWAとは
PWA(Progressive Web Application)とは、一言で言えば「ネイティブアプリのような使い勝手を実現したWebアプリ」です。
Googleのコードラボを一通りやると全体像がつかめるかと思います。
PWAでできること
ホームスクリーンへの追加(Add to homescreen)
PWAで作られたWebアプリケーションにアクセスした際に、ホーム画面への追加を促すことができます。
条件は下記です。
- 必要な情報が記載されたマニフェストファイルが存在する
- サイトにService Workerが登録されている
- HTTPS経由で配信されている
- 2回以上のアクセスがあり、そのアクセスに5分以上の間隔がある
詳細はこちら。
キャッシュ(Service Worker Caching)
Webアプリケーションの骨格となるApp Shellのpre-cache
やWebアプリケーションの使用中に取得するリソースのruntime-cache
を利用することができます。
表示速度を高速化したり、オフラインのユーザ体験を向上させたりすることができます。
プッシュ通知(Push Notification)
プッシュ通知と言えばネイティブアプリのイメージがありましたが、Webアプリでもプッシュ通知を送ることができます。
サービスエンゲージメントを高めていくうえで上手く使用すると効果があります。
バックグラウンド同期
オフラインでのリクエストをためておき、オンラインになったタイミングで処理を進めることができる機能です。
例えばチャットサービスを使っていて地下鉄などで電波が悪くなることがあります。
そんなときにオフライン状態でメッセージを送信すると、電波状況が悪い区間を抜けてオンラインになったタイミングで同期することができます。
PWAを作るための条件
PWAを作るためには下記の条件を満たしている必要があります。
− HTTPSのサイトであること
- ブラウザがService Workerに対応していること
Service Workerのブラウザ実装状況を見ると、まだまだと言った感じがします。
しかしながら、iOSのSafariでサポートされるという情報もあり、展望は開けていると個人的には考えています。
2. 環境整備
PWAでできることを大まかに理解したところで、開発できる環境を整えていきます。
2.1 vue-pwa-boilerplate
vue-pwa-boilerplateはVue.jsでPWAを開発できる雛形です。
vue-cli
を使って簡単にプロジェクトを作成することができます。
$ npm install -g vue-cli
$ vue init pwa my-project
$ cd my-project
$ npm install
$ npm run dev
npm run dev
で開発サーバを立ち上げるとブラウザに画面が表示されます。
これでVue.jsでSPAが開発できる状態になりました。一方で、PWAという意味ではもう一手間必要です。
2.2 Web Server for Chrome
vue-pwa-voilerplateでは、開発ビルド(npm run dev
)ではService Workerが登録されません。
build/service-worker-dev.js
とbuild/service-worker-prod.js
を見比べてみてください。
npm run build
でService Workerが登録される状態でビルドされます。
⠙ building for production...Total precache size is about 136 kB for 5 resources.
Hash: 2990e7f381c49b2088ee
Version: webpack 3.10.0
Time: 5389ms
Asset Size Chunks Chunk Names
static/img/icons/apple-touch-icon-152x152.png 4.05 kB [emitted]
static/js/app.7b4c3edaaf02253673f6.js 12 kB 0 [emitted] app
static/js/manifest.7ac8a796b3963c4ae8b1.js 1.49 kB 2 [emitted] manifest
static/css/app.1d063bc0cd301699760e884e1e4c3379.css 528 bytes 0 [emitted] app
static/js/app.7b4c3edaaf02253673f6.js.map 42.5 kB 0 [emitted] app
static/js/vendor.68998a222dcb7b47ea9a.js.map 973 kB 1 [emitted] vendor
static/js/manifest.7ac8a796b3963c4ae8b1.js.map 14.2 kB 2 [emitted] manifest
index.html 2.5 kB [emitted]
static/img/icons/android-chrome-192x192.png 9.42 kB [emitted]
static/img/icons/apple-touch-icon-120x120.png 3.37 kB [emitted]
static/img/icons/android-chrome-512x512.png 29.8 kB [emitted]
static/js/vendor.68998a222dcb7b47ea9a.js 120 kB 1 [emitted] vendor
static/img/icons/apple-touch-icon-180x180.png 4.68 kB [emitted]
static/img/icons/apple-touch-icon-60x60.png 1.49 kB [emitted]
static/img/icons/apple-touch-icon-76x76.png 1.82 kB [emitted]
static/img/icons/apple-touch-icon.png 4.68 kB [emitted]
static/img/icons/favicon-16x16.png 799 bytes [emitted]
static/img/icons/favicon-32x32.png 1.27 kB [emitted]
static/img/icons/favicon.ico 15.1 kB [emitted]
static/img/icons/msapplication-icon-144x144.png 1.17 kB [emitted]
static/img/icons/mstile-150x150.png 4.28 kB [emitted]
static/img/icons/safari-pinned-tab.svg 10.6 kB [emitted]
static/manifest.json 436 bytes [emitted]
Build complete.
Tip: built files are meant to be served over an HTTP server.
Opening index.html over file:// won't work.
ここでもう一つ問題となるのは、npm run dev
やnpm run start
で起動するNode.jsの開発サーバではService Workerが動かないという点です。
Service Workerの挙動を確認するにあたってはWeb Server for Chromeを導入します。
dist/
以下にビルドされた諸々のファイルが吐き出されるので、パスを指定して開きます。
127.0.0.1:8887
にアクセスすると先ほどと同じ画面が表示されます。
ここで、Chromeの開発者ツールを開いてApplication > Service Workers
タブを開いてみましょう。
Service Workerが稼動しているのがわかります。
画面を実装するときはnpm run dev
、Service Workerの挙動を確認したい場合はWeb Server for Chromeと言った具合に使い分けると良いと思います。
3. Vue.jsのSPAをPWA化する
今の段階では、ほとんどただのSPAなので、PWAらしくしていきます。
今回やるのは
- Service Workerを使ったキャッシング
- ホームスクリーンへの追加
です。
3.1 キャッシュによるオフライン体験の向上
workbox-webpack-pluginの導入とpre cache
vue-pwa-boilerplateにはキャッシュするためのプラグインとしてsw-precache-webpack-pluginが入っています。
webpackのプラグインなので、設定ファイル上で次のように記述されています。
// service worker caching
new SWPrecacheWebpackPlugin({
cacheId: 'my-pwa',
filename: 'service-worker.js',
staticFileGlobs: ['dist/**/*.{js,html,css}'],
minify: true,
stripPrefix: 'dist/'
})
いわゆる、AppShellをキャッシュする設定が書かれています。
このまま使っても良いのですが、今回はworkbox-webpack-pluginを使っていきたいと思います。
npm uninstall sw-precache-webpack-plugin
npm install --save-dev workbox-webpack-plugin
- const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin')
+ const workboxPlugin = require('workbox-webpack-plugin')
// 中略
- new SWPrecacheWebpackPlugin({
- cacheId: 'my-pwa',
- filename: 'service-worker.js',
- staticFileGlobs: ['dist/**/*.{js,html,css}'],
- minify: true,
- stripPrefix: 'dist/'
- })
+ new workboxPlugin({
+ cacheId: 'my-pwa',
+ globDirectory: config.build.assetsRoot,
+ globPatterns: ['**/*.{html,js,css}'],
+ swDest: path.join(config.build.assetsRoot, 'service-worker.js'),
+ skipWaiting: false,
+ clientsClaim: true
+ })
Service Workerのライフサイクルに関わる部分(skipWaiting
、clientsClaim
)に関しては本エントリでは省略しますが、押さえておくべきところなので別エントリで書こうと思います。
改めてnpm run build
して127.0.0.1:8887
にアクセスします。
この時点だと、sw-precache-webpack-plugin
のときに登録されたService Workerが生きていて、キャッシュも残っているので一度クリアして再読み込みしてみます。
AppShellがキャッシュされていました!
runtime-caching
Workboxではpre-cachingだけでなく、runtime-cachingも設定することができます。runtime-cachingは例えばAPIの呼び出し結果をキャッシュしておき、同じAPIを呼び出した際にサーバにリクエストを投げずに、キャッシュから結果を取得させることができます。
http://api.myservice.com/something
といったようなエンドポイントのAPIを作ったとして、それをSPAから呼び出すことを想定します。
new workboxPlugin({
cacheId: 'my-pwa',
globDirectory: config.build.assetsRoot,
globPatterns: ['**/*.{html,js,css}'],
swDest: path.join(config.build.assetsRoot, 'service-worker.js'),
skipWaiting: false,
- clientsClaim: true
+ clientsClaim: true,
+ runtimeCaching: [
+ {
+ // APIのキャッシュ
+ urlPattern: /.*api.*/,
+ handler: 'networkFirst',
+ options: {
+ cacheName: 'api',
+ cacheExpiration: {
+ maxAgeSeconds: 60 * 60 * 24
+ }
+ }
+ }
+ ]
})
ここで設定したパラメータは下記のとおりです。
- urlPattern: キャッシュするURLのパターン
- handler: キャッシュ戦略
- options.cacheName: キャッシュの名前
- options.maxAgeSeconds: キャッシュの生存期間(秒)
キャッシュ戦略と生存期間
Comprehensive caching strategiesによると、キャッシュ戦略には次のようなものがあります。
- Cache only
- Cache first, falling back to network
- Cache, with network update
- Network only
- Network first, falling back to cache
今回は、networkFirst
としてみました。
これはオフライン時にネットワークからリソースが取れなかったときにキャッシュに格納してあるリソースを使用する(本当は常に最新の情報をネットワークから取得したいけど)ことを意図しています。また、キャッシュの生存期間は秒単位の時間を指定します。
更新性の低いリソースであれば戦略をcacheFirst
として生存期間を長くするなど、
キャッシュ戦略や生存期間は、取得するリソースの特性に応じて適切に設定すると良いでしょう。
ホームスクリーンへの追加
Add to Home Screen
ホームスクリーンへの追加を実装するには、project/static/manifest.json
を設定します。
vue-pwa-boilerplateを導入した段階で既に追加されるようになっています。
{
"name": "my-pwa",
"short_name": "my-pwa",
"icons": [
{
"src": "/static/img/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/static/img/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "/index.html",
"display": "standalone",
"background_color": "#000000",
"theme_color": "#4DBA87"
}
PCで開発しながら確認するためには、Application > Manifest
タブ内にあるAdd to homescreen
リンクをクリックします。
すると、ブラウザの上部にメッセージが出てきます。
「追加」ボタンをクリックすると名前をつけるためのダイアログが表示され、OKをクリックするとChromeのアプリ欄に追加されます。(chrome://apps/
で確認可)
マニフェストの詳細に関してはこちらを見ると良いです。
4. パフォーマンスの計測
PWAを作るときに役立つ「Audit」という機能がChromeの開発者ツールに含まれています。
Auditはパフォーマンスやベストプラクティスを診断してくれるスグレモノで、結果を見ながらアプリケーションの品質を高めていくことができます。
Application > Audit
タブを開くと「Perform an audit...」ボタンがあるので、クリックしてみます。
診断対象には下記が含まれます。
- Progressive Web App
- Performance
- Accessibility
- Best Practice
ほぼ何もしてないので良いスコアが出てます。
診断対象ごとに詳細な情報も表示されるので、フィードバックに従って修正していけばアプリケーションのクオリティを上げていくことができます。
まとめ
本エントリでは
- PWAでできること
- PWAの開発環境の導入
- SPAのPWA化
- パフォーマンスの計測
といった、開発するにあたって必要なことを一通り説明しました。
Vue.jsのアドベントカレンダーですがあまりVue.js要素が無くてすみません。
Vue.jsで始めよう!PWA!