webpack-bundle-analyzer から始めるフロントエンドのパフォーマンス改善入門

# はじめに

この記事では、 webpack-bundle-analyzer で不要なパッケージを探して削除する方法を紹介します。

[CyberAgent が過去に開催した Web パフォーマンス改善のコンテストのリポジトリ](https://github.com/CyberAgentHack/web-speed-hackathon-2021) に対して試しに導入し、パフォーマンスの改善を試みます。

# 対象読者

本記事の対象読者は以下を想定しています。

- webpack-bundle-analyzer のインストール方法が知りたい
- フロントエンドが書けるようになってきたので、そろそろ webpack にも入門したい
- フロントエンドのパフォーマンス改善をしたいけど、何をやったらいいかわからない
- Web パフォーマンス改善のコンテストに参加することになったので付け焼き刃が欲しい

# webpack-bundle-analyzer とは？

webpack-bundle-analyzer は webpackによってバンドルされたファイルに含まれる各パッケージの容量を可視化してくれるとても便利なツールです。 webpack のプラグインとして利用できるほか、CLI としても使うことができます。

https://github.com/webpack-contrib/webpack-bundle-analyzer

# 導入方法

webpack-bundle-analyzer の使用例として、今回はCyberAgent が過去に開催した Web パフォーマンス改善のコンテストのリポジトリである CyberAgentHack/web-speed-hackathon-2021 に対して導入を行います。

https://github.com/CyberAgentHack/web-speed-hackathon-2021

 `client/package.json` ファイルを見てみると、以下のようにパッケージがすでに追加済のようです。

```js:client/package.json
    "webpack": "5.64.2",
    "webpack-bundle-analyzer": "4.5.0",
    "webpack-cli": "4.9.1",
    "webpack-dev-server": "4.5.0"
  },
```

無い場合は `yarn add -D webpack-bundle-analyzer` コマンドで追加できます。
今回は `yarn install` コマンドを実行するだけで、`package.json` に記録されているパッケージがインストールできました。

次に、webpack-bundle-analyzer を webpack のプラグインとして利用するために  `client/webpack.config.js` を変更していきます。

他のプラグインと同様に、`webpack-bundle-analyzer` を読み込みます。

```diff_javascript:client/webpack.config.js
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  const webpack = require('webpack');
```

そして、`plugins` に `new BundleAnalyzerPlugin()` を追加します。

```diff_javascript:client/webpack.config.js
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      AudioContext: ['standardized-audio-context', 'AudioContext'],
      Buffer: ['buffer', 'Buffer'],
      'window.jQuery': 'jquery',
    }),
    new webpack.EnvironmentPlugin({
      BUILD_DATE: new Date().toISOString(),
      // Heroku では SOURCE_VERSION 環境変数から commit hash を参照できます
      COMMIT_HASH: process.env.SOURCE_VERSION || '',
      NODE_ENV: 'development',
    }),
    new MiniCssExtractPlugin({
      filename: 'styles/[name].css',
    }),
    new HtmlWebpackPlugin({
      inject: false,
      template: path.resolve(SRC_PATH, './index.html'),
    }),
+   new BundleAnalyzerPlugin(),
  ],
```

以上で導入は完了です！ビルドしてみましょう！

```
$ yarn build
yarn run v1.22.18
$ yarn workspaces run build

> @web-speed-hackathon-2021/client
$ rimraf ../dist
$ cross-env NODE_ENV=development webpack

WARNING (@babel/preset-env): The `corejs` option only has an effect when the `useBuiltIns` option is not `false`

Webpack Bundle Analyzer is started at http://127.0.0.1:8888
Use Ctrl+C to close it
```

`http://127.0.0.1:8888` にアクセスすると、次のように表示されました。

![1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325392/01d57bef-04c3-cd85-5bd4-1ad73ad82b32.png)

Parsed size が 11.93MB ととても大きいですね。

特に大きいパッケージ `react-dom`,  `moment`,  `lodash` のうち、`lodash` は使用箇所が少ないので、削除できないか検討してみましょう。

```
$ git grep lodash -- client
client/package.json:    "lodash": "4.17.21",
client/package.json:    "@types/lodash": "4.14.177",
client/src/components/foundation/SoundWaveSVG/SoundWaveSVG.jsx:import _ from 'lodash';
```

`SoundWaveSVG.jsx` では、次のように `lodash` が使用されていました。

```js
  // 左の音声データの絶対値を取る
  const leftData = _.map(buffer.getChannelData(0), Math.abs);
  // 右の音声データの絶対値を取る
  const rightData = _.map(buffer.getChannelData(1), Math.abs);

  // 左右の音声データの平均を取る
  const normalized = _.map(_.zip(leftData, rightData), _.mean);
  // 100 個の chunk に分ける
  const chunks = _.chunk(normalized, Math.ceil(normalized.length / 100));
  // chunk ごとに平均を取る
  const peaks = _.map(chunks, _.mean);
  // chunk の平均の中から最大値を取る
  const max = _.max(peaks);
```

使用されている関数は `map` `zip` `mean` `chunk` `max` の 5つのみです。
`map` と `max` は標準ライブラリを使用し、`zip` `mean` `chunk`は自分で実装してしまいましょう！

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Math/max

```javascript
  const zip = (arr1, arr2) => {
    const result = [];
    for(let i = 0; i < arr1.length; i++) {
      result.push([arr1[i], arr2[i]]);
    }
    return result;
  };

  const mean = (arr) => {
    const total = arr.reduce((sum, element) => sum + element, 0);
    return total / arr.length
  };

  const chunk = (arr, chunkSize = 1, cache = []) => {
    const tmp = [...arr];
    if (chunkSize <= 0) return cache;
    while (tmp.length) cache.push(tmp.splice(0, chunkSize));
    return cache
  }

  // 左の音声データの絶対値を取る
  const leftData = buffer.getChannelData(0).map(Math.abs);
  // 右の音声データの絶対値を取る
  const rightData = buffer.getChannelData(1).map(Math.abs);
  // 左右の音声データの平均を取る
  const normalized = zip(leftData, rightData).map(mean);
  // 100 個の chunk に分ける
  const chunks = chunk(normalized, Math.ceil(normalized.length / 100));
  // chunk ごとに平均を取る
  const peaks = chunks.map(mean);
  // chunk の平均の中から最大値を取る
  const max = Math.max(...peaks);
```

これで、`lodash` が不要になりました。 `lodash` を削除して、再び ビルドしてみましょう！

```
$ yarn workspace @web-speed-hackathon-2021/client remove lodash
```

![2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325392/c2d3be25-616b-92fc-96de-c9fa0d716dbd.png)


図から `lodash` が消え、 Parsed size も 11.93 MB から10.56 MB に減りました :tada:

このように、`webpack-bundle-analyzer` により、大きいパッケージを特定 & 削除をすることでバンドルサイズが減らし、パフォーマンスの向上に繋げることができました！

今回対象としたリポジトリ CyberAgentHack/web-speed-hackathon-2021 のパフォーマンスを上げる方法は他にもまだまだたくさんあります。以下の記事に各手法がまとめられており、とても参考になりました。（`chunk` の実装はこちらからお借りしました。）

https://trap.jp/post/1481/


# おまけ

`client/package.json` をよく見てみると、`analyze` というコマンドがありました。

```
  "scripts": {
    "analyze": "cross-env NODE_ENV=development webpack --analyze",
    "prebuild": "rimraf ../dist",
    "build": "cross-env NODE_ENV=development webpack",
    "develop": "cross-env NODE_ENV=development webpack serve"
  },
```

これを実行しても `webpack-bundle-analyzer` の結果が見れます。（ここで使用するため、`client/package.json` に最初から `webpack-bundle-analyzer` が入っていたんですね。）

以下コマンドで見れます。

```
$ yarn workspace @web-speed-hackathon-2021/client analyze
```