はじめに
この記事では、 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
ファイルを見てみると、以下のようにパッケージがすでに追加済のようです。
"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
を読み込みます。
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()
を追加します。
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
にアクセスすると、次のように表示されました。
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つのみです。
map
と max
は標準ライブラリを使用し、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
図から lodash
が消え、 Parsed size も 11.93 MB から10.56 MB に減りました
このように、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