Vuetify2を使ったプロジェクトのビルドが遅いので高速化する

Vuetify+Vue CLIで構築したプロジェクトで、
Vuetify2.xにアップグレードしたら`npm run serve`の開始時間が著しく長くなってしまったので改善します。

## 原因
Vuetify2.0よりデフォルトで有効となった[A-la-carte(treeshaking)](https://vuetifyjs.com/ja/customization/a-la-carte/)が原因です。

### A-la-carteとは

Vuetifyの数多くのコンポーネントのうち、「プログラム中で使われているもの」だけを成果物に含める機能です。使っていないコンポーネントをimportしないので、ビルドサイズが減少します。

しかしながら、これはビルド時間を増大させます。

A-la-carteを使わない場合、vuetifyの全てのコンポーネントは`node_modules/vuetify/dist/vuetify.min.js`から読み込まれます。これは既にimportの解決やminifyが済んでいるプリコンパイル済みソースであるため、これをビルド成果物に含めるための処理は極めて軽いです。

ところが、A-la-carteを用いる場合、`node_modules/vuetify/lib/*`(要するにVuetifyのビルド前のソースそのもの)からコンポーネントが読み込まれます。これは、Vuetify内部のimportの解決もこちらのコンパイルタイムで行わなければならないことを意味します。Vuetifyはソース数が非常に多く、これは大変な処理負荷となります。

さらに、scssなどもトランスパイルしなければならないため、これも少なからずビルド時間が増大する原因となります。A-la-carteを用いなければcssは`node_modules/vuetify/dist/vuetify.min.css`より読み込まれるので負荷はありません。

### `npm run serve`がなぜ重いか

ところが、実際の`npm run build`時にはそこまで大きくコンパイル時間が伸びることはありません(当然伸びるのは伸びるのですが)。これは、`vuetify-loader`と呼ばれるWebpackプラグインによって、不要なものが削ぎ落とされているからです。必要なものだけビルドするので、使っているコンポーネント数に比例してしかビルド時間は伸びないはずです。

しかし、`npm run serve`においてはそれがうまくいかず、非常にビルド時間が伸びてしまいます。これは、Webpack4のtreeshaking(`vuetify-loader`はこの機能に依存しています)がproductionビルドでしか有効にならないためです。そのため、developmentビルドでは使っているコンポーネント数に関係なく全てのコンポーネントがビルドされ、非常に時間がかかります(特に開始時)。

## 解決策

解決策の方針としては以下のようになります。

- developmentビルド時は、ビルド時間を削減するためにA-la-carte(treeshaking)を利用しないようにする
- productionビルド時は、ビルドサイズを削減するために`vuetify-loader`を用いたA-la-carteを利用する

### ソースの変更

以下はVue CLI3の例ですが、Webpackを生で使っている場合でも大きくは変わらないと思います。

#### vue-cli-plugin-vuetifyの無効化

`package.json`より、`vue-cli-plugin-vuetify`と`vue-cli-plugin-vuetify-essentials`の削除

```diff:package.json
  "devDependencies": {
    ...,
-   "vue-cli-plugin-vuetify": "^1.1.2",
-   "vue-cli-plugin-vuetify-essentials": "^0.8.3",
    ...
  }
```

#### vuetifyのインポートの変更

Vue CLIインストールであれば`plugins/vuetify.js`でvuetifyを以下のようにインポートしていると思います。

```js:plugins/vuetify.js
import Vue from 'vue';
import Vuetify, { VList } from 'vuetify/lib';

Vue.use(Vuetify);

export default new Vuetify({ /* vuetifyのオプション */ });
```

これと同様にして、`plugins/vuetify-dev.js`を以下の内容で作成します。

```js:plugins/vuetify-dev.js
import Vue from 'vue';
import Vuetify from 'vuetify';
import 'vuetify/dist/vuetify.min.css';

Vue.use(Vuetify);

export default new Vuetify({ /* vuetifyのオプション */ });
```

さらに、`main.js`でVuetifyをインポートしている部分を変更します。

```diff:main.js
  import Vue from 'vue';
- import vuetify from './plugins/vuetify';
+ const vuetify = process.env.NODE_ENV === "production"
+   ? require('./plugins/vuetify').default
+   : require('./plugins/vuetify-dev').default;
  import ...

  /* (中略) */

  new Vue({
    vuetify,
    render: h => h(App)
  }).$mount('#app');
```

#### `vuetify-loader`の手動ロード

`vue-cli-plugin-vuetify`を消してしまったので、`vuetify-loader`を手動でWebpackに読み込ませます。
`vue.config.js`を以下のように編集します。

```diff:vue.config.js
+ const vuetifyOptional = process.env.NODE_ENV === "production"
+   ? [new (require('vuetify-loader/lib/plugin'))()]
+   : [];
  
  module.exports = {
    ...,
+   configureWebpack: {
+     plugins: [
+       ...vuetifyOptional,
+     ],
+   },
-   transpileDependencies: ["vuetify"],
+   transpileDependencies: vuetifyOptional.length > 0 ? ["vuetify"] : [],
    ...,
  }
```

### 動作確認
`npm run serve`と`npm run build`をして正しくビルドできることを確認して下さい。

---

### 参考文献
https://vuetifyjs.com/ja/getting-started/quick-start/
https://vuetifyjs.com/ja/customization/a-la-carte/