35
18

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-04-27

はじめに

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

CyberAgent が過去に開催した Web パフォーマンス改善のコンテストのリポジトリ に対して試しに導入し、パフォーマンスの改善を試みます。

対象読者

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

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

webpack-bundle-analyzer とは?

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

導入方法

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

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

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 を読み込みます。

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');

そして、pluginsnew BundleAnalyzerPlugin() を追加します。

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

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 が使用されていました。

  // 左の音声データの絶対値を取る
  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つのみです。
mapmax は標準ライブラリを使用し、zip mean chunkは自分で実装してしまいましょう!

  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

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

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

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

おまけ

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
35
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
18