Vue.js, TypeScript, webpack環境でバンドル速度を上げる

TypeScriptを使うとBabelよりwebpackのバンドル速度が低下しました

<img width="310" alt=" 2018-04-04 12.15.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/c52df80c-e6fc-3272-bfa1-deafcd20e37b.png">

上記のようにバンドル後のサイズが379kb程度だと約8秒かかってます

ちなみにBabelだと以下です

<img width="310" alt=" 2018-04-04 12.27.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/35abf653-9051-e325-d6f4-7e8d9eca22fb.png">

約2.5秒なので、約5.5秒の差があります

webpackの設定を比べてみると

■ Babelの場合

```js:webpack.config.js
{
  test: /\.js$/,
  use: 'babel-loader?cacheDirectory',
  exclude: /node_modules/
}
```

Babelは
ES2015をbabel-loaderでES5に変換
しています

■ TypeScriptの場合

```js:webpack.config.js
{
  test: /\.ts$/,
  use: [
    {
      loader: 'babel-loader?cacheDirectory'
    },
    {
      loader: 'ts-loader',
      options: {
        appendTsSuffixTo: [/\.vue$/]
      }
    }
  ],
  exclude: /node_modules/
}
```

このようにTypeScriptの場合は、
TypeScriptをts-loaderでES2015に変換
ES2015をbabel-loaderでES5に変換
しているため、ts-loaderの変換の部分が遅くなってる原因だと分かります

今回はts-loaderの速度アップのため、2つの改善を行いました
どちらも簡単に設定できるので、Vue.js + TypeScript + webpackを使ってる人は試してみてください

全体のコードはこちらになります
https://github.com/kurosame/vuejs-boilerplate

## ts-loaderを並列実行する
トランスパイルと型チェックを並列実行させます
具体的にはts-loaderはJSに変換するトランスパイルのみを行い、
型チェックはFork TS Checker Webpack Pluginというプラグインで（並列で）行います
https://github.com/Realytics/fork-ts-checker-webpack-plugin

```sh
$ yarn add --dev fork-ts-checker-webpack-plugin
```

```js:webpack.config.js
const ForkTsChecker = require('fork-ts-checker-webpack-plugin')

...

{
  test: /\.ts$/,
  use: [
    {
      loader: 'babel-loader?cacheDirectory'
    },
    {
      loader: 'ts-loader',
      options: {
        appendTsSuffixTo: [/\.vue$/],
        transpileOnly: true  // このオプションを追加
      }
    }
  ],
  exclude: /node_modules/
}

...

plugins: [
  new ForkTsChecker()
]
```

↓↓↓ビルド結果↓↓↓
<img width="323" alt=" 2018-04-05 19.14.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/a8302d1e-2c83-3f5b-6237-dac166c5e47b.png">

約2秒位速くなりました

## キャッシュを使う
2通り方法があります

### HardSourceWebpackPlugin
初回のwebpack実行時に`node_modules/.cache/hard-source`配下にキャッシュを作り
2回目以降のwebpack実行からはキャッシュを使います

```sh
$ yarn add --dev hard-source-webpack-plugin
```

```js:webpack.config.js
const HardSource = require('hard-source-webpack-plugin')

...

plugins: [
  new HardSource()
]
```

<img width="314" alt=" 2018-04-06 15.37.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/7fbd55d6-58a7-48fa-7039-70a4b0823189.png">

約3.5秒位速くなりました

Fork TS Checker Webpack PluginとHardSourceWebpackPluginを2つ使うことで
最初の約8秒から約2.5秒まで短縮させることができました

ただし、このプラグインのトラブルシューティングを見てみると
webpackとwebpack-dev-serverで同じwebpack.configを使っていても、それぞれ異なるキャッシュを持つ必要があるみたいです
https://github.com/mzgoddard/hard-source-webpack-plugin#troubleshooting

よってwebpack-dev-serverやNuxt.jsを使っている場合は、別途設定が必要で
webpack-dev-serverの場合は、contentBaseオプションに指定したディレクトリがルートになるので、
以下のような指定が必要になるかもしれません

```js:webpack.config.js
new HardSource({
  cacheDirectory: '.cache/hard-source/[confighash]',
})
```

### Cache Loader
HardSourceWebpackPluginと同様に初回実行時にキャッシュを作り、2回目以降の実行からキャッシュを適用します
こちらはloader専用でloader個別に設定できます

```sh
$ yarn add --dev cache-loader
```

```js:webpack.config.js
{
  test: /\.ts$/,
  use: [
    {
      loader: 'babel-loader?cacheDirectory'
    },
    {
      loader: 'ts-loader',
      options: {
        appendTsSuffixTo: [/\.vue$/]
      }
    }
    // ここから追加
    {
      loader: 'cache-loader',
      options: {
        // こちらのオプションは無くてもいいですが、デフォルトだとプロジェクトルート直下に
        // キャッシュディレクトリを作るので、node_modules配下に作るように変更してます
        cacheDirectory: path.resolve(
          __dirname,
          'node_modules/.cache/cache-loader'
        )
      }
    },
    // ここまで
  ],
  exclude: /node_modules/
}
```

<img width="317" alt=" 2018-04-06 16.28.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/11b65be6-a1b4-910a-685d-1748c7546e13.png">

こちらも約3.5秒位速くなりました
HardSourceWebpackPluginと比べると若干遅いです

### どっちを使うか
HardSourceWebpackPluginの方が設定も簡単で、速いっぽいので
基本的にはこちらの方が良いでしょう

ただし、HardSourceWebpackPluginと比べてcache-loaderはキャッシュの影響範囲を指定できるので、
HardSourceWebpackPluginで不具合起きる時は、部分的にキャッシュを導入できるcache-loaderを使った方が良い時もあると思います

https://github.com/webpack-contrib/cache-loader/issues/11
こちらのissuesで言われている通り、HardSourceWebpackPluginとcache-loaderを同時に使うことや
また、cache-loaderをあまり効果が望めないloaderに設定することは
キャッシュの生成コストやメンテナンスコストを増やすことになるだけなので止めましょう

## 補足
ts-loaderよりawesome-typescript-loaderを使った方が速いみたいですが、
前に試した時にVue.jsだと使えなかったので、ts-loaderを使ってます

https://github.com/s-panferov/awesome-typescript-loader/issues/356

他にも以下を試しましたが、あまり効果が出なかったので省きました
thread-loader
https://github.com/webpack-contrib/thread-loader

HappyPack
https://github.com/amireh/happypack