GAミント至上主義

安くて速いが好きなWEBアプリ開発者。最近はPython, Vue.js, Firebase, GKE。@株式会社ビズオーシャン

Vue.jsのPWAでLighthouse100点を目指したがPerformance77点で挫折した

WEB上だと空のプロジェクトとかでLighthouseの高得点を目指す記事はあるけど、実用的なPWAアプリでの情報は少ないので試したことをまとめてみる。

履歴書をスマホで入力できてPDFで出せる新サービスyagish履歴書。
まだまだ改良の余地はあるけど使える状態。

rirekisho.yagish.jp

まず結果。
5日ぐらいがんばった後のレポートはこれ。
Chrome拡張でLihghtHouseのバージョンは3.0.1。
ユーザーの第一印象となるトップページだけがんばった。

f:id:uyamazak:20180704102245p:plain
実行のたびに結果は変わる(Peformance 50~77点ぐらい)

Performance以外の100点は意外と簡単だった。
ただ足りないのを追加したり、よくないのを変えていく感じ。
Performanceはコード量とか処理を減らせというので難しい。

でも45点だった最初と比べると、スマホでも初回5秒以上かかっていた読み込みが体感できるほど早くなり、やってよかったと思っている。
緑(75点)以上で妥協するのも全然いいと思う。

Vueは下記PWAテンプレートをもとに開発している。
GitHub - vuejs-templates/pwa: PWA template for vue-cli based on the webpack template

これは現在すでにメンテモードになってしまい、下記プラグイン形式に移るので今からはじめるならこっち。
大きいところだとWebpackが3だったのが4になったりして、そのままは移せない。
vue-cli/packages/@vue/cli-plugin-pwa at dev · vuejs/vue-cli · GitHub

いろいろ試行錯誤したけど、効果が大きかったのは下記3つ。

1.画像は可能な限り小さくする

最初は履歴書のサムネがjpgで60KBあったけど、PhotoshopでPDFを開き、幅480pxに縮小、PNGで保存し、さらに下記サービスで圧縮したところ、10KB前後まで削減でき、文句言われなくなった。
色が少なく、はっきりしている画像はやっぱりPNGが強い。

PNGイメージをオンラインで圧縮する

ロゴやヤギなど他の画像はVGを使っているため、この手法は使えなかった。

2. vue-routerで遅延評価

js側だとこれが一番効いた気がする。
公式ドキュメント通り、ページで使うVueコンポーネントをPromise返しにするだけでおk。
動作もほとんど影響しないが、これまでトップページでも読み込まれていた他のページのファイルが分割されて20点ぐらいあがった。

formatList はトップページで使うコンポーネントのため、直接importのままにした。どっちにしても数字的にはたいして影響しなかった気がする。

router.vuejs.org

router/index.js

before

import Vue from 'vue'
import Router from 'vue-router'
import formatList from '@/components/FormatList'
import formatDetail from '@/components/FormatDetail'
import formatEdit from '@/components/FormatEdit'
import notFound from '@/components/NotFound'
import helpPage from '@/components/pages/Help'
import termsPage from '@/components/pages/Terms'
import serviceInfoPage from '@/components/pages/ServiceInfo'

const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      component: formatList,
      name: 'formatList',
      meta: {
        title: SITE_TITLE
      }
    },
    {
      path: '/format/:id',
      component: formatDetail,
      props: true,
      beforeEnter: formatExistsOr404,
      name: 'formatDetail',
      meta: {
        title: '書式詳細 - ' + SITE_TITLE
      }
    },
// 省略

after

import Vue from 'vue'
import Router from 'vue-router'
import formatList from '@/components/FormatList'
import formatsData from '@/assets/formats/index'

const formatDetail = () => import('@/components/FormatDetail')
const formatEdit = () => import('@/components/FormatEdit')
const notFound = () => import('@/components/NotFound')
const termsPage = () => import('@/components/pages/Terms')
const serviceInfoPage = () => import('@/components/pages/ServiceInfo')
const helpPage = () => import('@/components/pages/Help')

// 省略

router以外でも動的コンポーネントを使っている箇所も遅延にしてみたけど、大して変わらなかった。

WEBフォントは必須にしない

今回のサービスの特質上、ユーザーとPDFサーバーでフォントをそろえる必要があった。
GoogleのWEBフォントを使っており、サイト上でもメインで使っていたけど、読み込まれるまで文字が表示されないため、大きく減点されていた。
そこでWEBフォントはどうしても必要な履歴書のプレビュー画面だけにして、他の箇所では優先順位をさげた。

あと原因が分かったけど改善が難しいもの。

vendorファイルのサイズ削減

つかってるjsのライブラリがまとまるvendorファイル。
これが3MBありビルド中もtoo Large的な警告が出ていた。
中身を確認するには下記コマンド(Webpack Bundle Analyzer)がいいらしく、今回のPWAテンプレートにも入っていたので実行してみた。

How to troubleshoot a large vendor file? · Issue #1297 · vuejs-templates/webpack · GitHub

Webpack Bundle Analyzerについて詳しくはこれ。今回ホスト名、ポート番号は設定で変えた。
www.npmjs.com

絶望の結果がこれ。
f:id:uyamazak:20180704105314p:plain

element-uiとfirebaseが2大巨頭だがどっちも深く使っているため大きく削れない。
elementは使ってないものは省けるが、commonがでかすぎて、他を削ったところで数十kb削減にしかならず、効果は薄かった。
firebaseは公式にある通り使っているものだけrequireしている。
Firebase を JavaScript プロジェクトに追加する  |  Firebase

でもPerformanceには1点も効果がなかった。

あと小さいけどlodashも使っている機能が限られているので、下記のように個別に読み込んでサイズを小さくできたが、これも結果はほぼ変わらなかった。

const MyLodash = {
  forEach: require('lodash/forEach'),
  compact: require('lodash/compact'),
  debounce: require('lodash/debounce'),
  orderBy: require('lodash/orderBy'),
  defaultsDeep: require('lodash/defaultsDeep')
}
module.exports = MyLodash

トップページの高速化ならPWA +AMP

というわけで、Vueなどを使ったSPAではライブラリの容量が大きくロード時間などで100点は難しい。
そのため、これ以上の高速化となるとAMPを組み合わせるしかない。

詳しくは公式ドキュメント
www.ampproject.org

これを使えばAMPで高速に表示して、その間にService Workerでロードしつつ、リンク先ではSPAが動くという形にでき、最初の画面表示を爆速にできるはず。