React Bundle.js の最小化

## はじめに
本編は、`webpack` と `TypeScript` を使用する前提になってます。React に限らず、他のSPAにも適用できます。

## なぜ最小化が必要
複数のライブラリ使うと、ビルド後の `bundle.min.js` も膨大(10mb↑)になって、ユーザの初期ロード時間が長くなります。ネットワーク弱者は、表示できない可能性もあります。

## どんな方法があるの
- 必要だけの `import`にしましょう
- Chunks しましょう
- Compession しましょう
- Content-type: gzip にしましょう

## 具体的にどうすれの？
これから色んなツールを駆使し、サイズを減りましょう

### まずは分析
bundle.js の内部構造を分析し、大きいサイズのライブラリから、減らして行きます。
最初に登場するのは、[webpack-bundle-analyzer](https://www.npmjs.com/package/webpack-bundle-analyzer) です。

`bundle.min.js` の内部構造を図面化してくれるツールです。大体こんな感じ
![93f72404-b338-11e6-92d4-9a365550a701.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160072/3f43f75c-c576-25f6-cd7b-c70f96cb787b.gif)

```javascript:webpack

import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';

const configs: Configuration = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
};

export default configs;
```

次は、`stats.json`です。
バンドルされたライブラリの内部、jsonファイル使った場合、圧縮されませんため、`stats.json`から分析します。下記コマンドから作成できます。

```js:stats.json
webpack --config webpack.prod.ts --profile --json > stats.json
```

### 次はライブラリ構造の調査
先の分析結果を元に、割合が大きいのライブラリから調査する、今回は `lodash.js` を対象にします。`node_modules` にライブラリのソース確認できますので、ちょっとのぞきましょう。

```js
lodash
   | - isNull.js
   | - map.js
   | - math.js
   | - min.js
```

上記構造から、このライブラリは複数ファイルに分散されることが分かりました。必要の分だけ`import`します。

```js:改善方法
- import * as _ from 'lodash';
+ import map from 'lodash/map';
```

### stats.json の調査
[webpack-bundle-analyzer](https://www.npmjs.com/package/webpack-bundle-analyzer)結果に、大きいなjsonブロックがなければ、調査しなくても大丈夫です。典型的のは、`moment`です。下記リンクから`latest.json`を検索すれば、詳しく書いてます。

[webpackのbundle後のJavaScriptのサイズを減らしている話](https://recruit-tech.co.jp/blog/2018/12/15/try_optimization_webpack_bundle_size/)

### Chunks を有効にする
Code Splitting 手法の１つ、その他の色々ありますので、リンク先を見てください。有効する方法は簡単なので、下記設定を参考してください。
[https://webpack.js.org/guides/code-splitting/](https://webpack.js.org/guides/code-splitting/)

```js:webpack
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';

const configs: Configuration = {
  optimization: {
    splitChunks: {
      name: true,
      cacheGroups: {
        commons: {
          chunks: 'initial',
          minChunks: 2
        },
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          chunks: 'all',
          priority: -10
        }
      }
    },
    runtimeChunk: true
  },
};

export default configs;
```

### 圧縮機能を有効にする
[compression-webpack-plugin](https://www.npmjs.com/package/compression-webpack-plugin)の登場です。ビルド同時に、圧縮されたファイルも出力される。
[node-zopfli](https://www.npmjs.com/package/node-zopfli)を使えば、もっと圧縮率を上げられます。


```javascript:webpack
import CompressionPlugin from 'compression-webpack-plugin';
import zopfli from 'node-zopfli';

const configs: Configuration = {
  plugins: [
    new CompressionPlugin({
      test: /\.js$/,
      filename: '[path].gz[query]',
      algorithm: (source, compressionOptions, callback) => {
        return zopfli.gzip(Buffer.from(source), compressionOptions, callback);
      }
    })
  ]
};

export default configs;
```

### 最後は、Content-type 設定しましょう
圧縮されるファイルは、そのまま使えませんので、サーバ上、gzip配信機能の有効が必要です。

- S3 / CloudStorage で配信
  - File Metadataに `Content-Encoding: gzip`を設定する
  - https://cloud.google.com/storage/docs/transcoding?hl=ja
- CloudFront で配信
  - https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html
- Nginx で配信
  - https://qiita.com/cubicdaiya/items/2763ba2240476ab1d9dd
  - gzip_static 機能使いましょう

## まとめ
`dynamic import`や`loadable component`も、サイズ最小化の有効手段なので、是非使ってみてください。