30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

webpack のビルドとマークアップのパース処理

Vueのコンポーネントをwebpackでビルドする際のパフォーマンスの話。
他のライブラリでは分からない。

詳しい人にとっては常識かもしれないし、大した話ではないのだが、ここまでパフォーマンスに違いが出るのは知らなかったので、記録しておく。

webpackという文脈で「パフォーマンス」というとき、ビルドされたアプリのパフォーマンスと、ビルドそのもののパフォーマンスの2種類の話がある。この記事で扱うのは、後者。

結論から言うと、対象となるファイルのサイズよりも、コンポーネントの階層構造の複雑さが、ビルド時間に大きな影響を与える。

事例

担当中のウェブアプリがビルドに4分以上かかっており、原因を調べていたところ、とあるコンポーネントがボトルネックの1つになっていることが分かった。

そのコンポーネントは、規約の一種で、申込画面のなかでユーザーの同意を得るために表示させている。
プレーンテキストではなくマークアップされており、リスト形式や表が使われている。特にリストは何重にも入れ子になっており、それなりに複雑な構造。

規約なのでスタティックな内容であり、ユーザー毎に表示を出し分けたり、状態によって表示内容が変化したりすることはない。
他のコンポーネントとの依存関係もないので、ハードコーディングというか、用意されたマークアップファイルをそのまま貼り付けていた。

しかしこれが失敗で、このコンポーネントの存在だけでビルドが数分遅くなってしまっていた。
v-forによるリストレンダリングを使って重複する記述を省いていったところ、ビルド時間を大幅に短縮できた。

検証

どの程度の影響があるのか、実際に検証してみる。

使用したライブラリのバージョン。

  • vue@2.5.22
  • vue-loader@15.6.0
  • vue-template-compiler@2.5.22
  • webpack@4.29.0
  • webpack-cli@3.2.1

webpack.config.jsの内容は以下。vue-loader以外は何も使っていない。

const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        exclude: /node_modules/,
        loader: 'vue-loader',
      },
    ],
  },
  plugins: [new VueLoaderPlugin()],
};

webpack --mode=productionを実行してビルドする。

複雑な構造のコンポーネント(ハードコーディング)

リスト構造やテーブル構造を多用し、それなりに複雑な構造を持っているコンポーネントを用意した。
かなり長いので、Gist に貼る。

Complex.vue

手元の環境だと、ビルドに52.14s.かかった。

シンプルな構造のコンポーネント

次に、先程と全く同じファイルサイズ(8128バイト)だが、構造としては単純なコンポーネントを用意した。

Simple.vue

<p>ランダムな文字列</p>が続いているだけ。

これのビルドは、3.33s.で終わった。
このことから、ファイルサイズは重要ではないということが分かる。

複雑な構造のコンポーネント(リストレンダリングによる効率化)

最後に、複雑なほうのコンポーネントを、リファクタリングする。
リストレンダリングを使うことで、マークアップの記述を減らした。

Improve.vue

面倒なので一部分しか改善を行わなかったが、それだけでも15.13s.にまでビルド時間を短縮できた。

Vue + Rails ではてなブックマークをインポートできるアプリを作った

習作なので実用性は低い。
後述するように Heroku にデプロイしてきちんと動いているので、動作に問題はない。

github.com

作成した目的

サーバーサイドの知識がなさすぎるので、それを学ぶため。
実際に何かを作るのが一番勉強になるし、ソースコードが手元に残るという利点もある。
それと、Vue を使って一から自分で何かを作ったことはなかったので、それを経験するという目的もあった。

当面はウェブエンジニアをやっていくつもりだが、CRUDアプリの開発とその運用が出来れば、「ウェブプログラミングの基礎は出来る」と言えると思っている。
プログラマーのススメという記事に「Webアプリを作るってことは、一言で言えば、データベースのラッパー(CRUD)を作るだけの話。」という一文があるが、自分もそう思う。今回作ったアプリには削除機能はないので、CRUDのDはないが。
要は、ブラウザを通してデータベースとやり取りして、データを表示したり操作したりする、というものを作れればいいと思っている。

なのでその第一歩として、このアプリを作った。
もちろん内容としては初歩の初歩であり、「本物のプロダクト」からは程遠いものだが、今必要なのは広く浅く学んで概要や全体像を掴むことなので、これでいいはず。

「砂場」を作るのも目的の一つ。
気になったことをすぐに試せるような環境を、作っておきたかった。
フロントエンドや Rails で新しいことを学んだときに、それをすぐに試せる題材。既に書いたように、実際に使うのが一番勉強になる。そしてその対象は、「Hello world」ではなく出来るだけ実用的なもののほうがいい。そのほうが、新しく学んだ技術の具体的な使い勝手を学べる。
例えば、Vue や Vuex のテストや、Rails のE2Eテストをこれから学んでいくつもりだが、このアプリを対象にテストを書くことで、より実践的に学習できる。
AWSについてももっと学びたいが、そのための題材としてこのアプリを使うことも出来る。

技術的なこと

既に少し書いたが、全体像を掴むことが大事だと思っているので、クオリティを高めることよりも、ゼロから作ってそして最後まで完成させることを重視した。

フロントは、Vue + Vuex + Vue Routerという、オーソドックスな Vue アプリだと思う。
デザインにはBulmaというライブラリを使っている。
ビルドはwebpackWebpackerは使っていないし、アセットパイプラインも使っていない。
なるべくフロントエンドと Rails が疎結合になるように心掛けたので、Rails はほとんとAPIサーバーのような使い方をしている。
これも「砂場」の一例で、フロントエンドだけを分離することで、新しい技術を学ぶときの素材として使える。例えば Ruby 以外のサーバーサイド言語を学ぶ際に、このフロントエンドに対応するAPIを作る、ということを題材にすることが出来る。

Rails 側の実装は『現場で使える Ruby on Rails 5速習実践ガイド』を参考にしたのであまり行き詰まることはなかった。
ただ、Active Record の知識がなさすぎて苦戦した。単純なことでも数時間悩んでしまったし、頑張って実装した機能が組み込みで用意してあったりした。
パフォーマンスの問題もある。ローカル環境では問題ないのだが、デプロイした本番環境だと、ファイルのインポートに時間がかかりすぎる。

Heroku へのデプロイ

作ったからにはデプロイしてネットに公開するところまでやり遂げたかったので、Heroku を使ってデプロイした。
初めての経験だったのでメモを残しておく。初心者なので間違った理解も含まれているかもしれない。

アプリ側の準備

Heroku を使う前にまず、アプリ側の作業を済ませておく。

  • config/database.ymlproductionusernameを編集する
    • 初期状態だとアプリの名前がそのまま入っているはずだが、これだとエラーになる。
    • Heroku の管理画面でDBのユーザー名を確認できるが、手っ取り早くデプロイしたい場合はusernameをコメントアウトしても動いた。
  • package.jsonenginesscriptsを編集する
    • enginesnodeyarnの指定を追加する。
    • scriptsheroku-postbuildコマンドを追加する。このコマンドはデプロイ時に実行されるので、webpackによるビルドなどを書く。

Heroku CLI での作業

  • Heroku CLI のインストール
  • $ heroku login
  • $ heroku create {アプリ名}
    • アプリのURLと Git のパスが表示される
  • $ git remote add heroku {パス}
    • 先程表示された Git のパスを指定する
  • $ heroku config:set RAILS_MASTER_KEY=<your-master-key>
  • $ heroku config:set NPM_CONFIG_PRODUCTION=false
    • この設定をしておかないと、ビルド時にwebpack: not foundというエラーが出てしまう
  • $ heroku buildpacks:add --index 1 https://github.com/heroku/heroku-buildpack-nodejs
    • $ heroku buildpacksで確認したときにheroku-buildpack-nodejsが表示されていれば成功
  • $ git push heroku masterでデプロイする
  • $ heroku run rails db:migrateでマイグレーションする
    • ちなみに$ heroku run rails cでコンソールに入れる
  • $ heroku openでアプリのページが表示されるので動作確認する