この記事は、私が Webpack4 を使用している過程で得たいくつかのまとめであり、Webpack のバンドルサイズを最適化する方法を紹介しています。
Webpack は現在、大規模プロジェクトの主要なバンドルソリューションです。React のスケルトンを Webpack4 にアップグレードして以来、私も Webpack のバンドル最適化のいくつかの方法を試行錯誤しています。この記事では、Webpack のバンドルサイズを最適化する方法について、以下の観点からまとめています。
バンドルの分析#
webpack-bundle-analyzer を使用すると、バンドルされたチャンクの可視化分析が行えます。バンドル後の各モジュールのサイズや、gzip 圧縮後のサイズを確認することができます。本番環境では、モジュールは gzip 圧縮されたものが読み込まれるため、実際のサイズを確認するための指標となります。
npm install --save-dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
Tree-Shaking#
Webpack4 では、mode が production の場合、デフォルトで Tree-Shaking が有効になっていますが、プロジェクトによっては babel の設定によって無効になることがあります。
Tree-Shaking は、ES2015 モジュールシステムの静的構造特性(import や export など)に依存しており、未使用のコードを見つけ出すために使用されます。そのため、babel プラグインを使用する場合(例:babel-preset-env)、デフォルトではモジュールが commonjs 形式にバンドルされるため、Tree-Shaking が無効になってしまいます。
そのため、babel の設定でモジュールの変換を行わないようにする必要があります。以下のように設定します:
// .babelrc
{
"presets": [
["env", {
modules: false,
...
}]
]
}
url-loader、file-loader の設定最適化#
url-loader と file-loader は、通常背景画像を処理するために使用されます。デフォルトでは、CSS 内の背景画像は Base64 にバンドルされますが、CSS 内の背景画像が大きすぎると、バンドル後の CSS が非常に大きくなります。シングルページアプリケーションでは、CSS が DOM のレンダリングをブロックし、ホームページの表示に長い時間がかかることがあります。以前のプロジェクトでは、ビルド後の CSS が 4MB にもなり、ユーザーエクスペリエンスに深刻な影響を与えました。解決策は、url-loader の limit フィールドを設定し、一定のバイト数を超える場合は Base64 にバンドルしないようにすることです。設定後、再ビルドされた CSS は約 300KB になります。
{
test: /\.(jpg|png|gif|svg)$/,
loader: 'url-loader',
include: path.join(__dirname, './src'),
exclude: /node_modules/,
options: {
limit: 8192,
name: 'images/[name].[hash:7].[ext]'
}
}
ダイナミックローディング#
React や Vue を使用して SPA アプリケーションを開発する場合、Webpack はデフォルトで 1 つの JS ファイルを出力します。これは、初回のレンダリング時にすべてのページの JS が読み込まれることを意味します。JS のサイズが大きすぎると、初回のレンダリング速度に大きな影響を与えます。ダイナミックローディングは、各ルートに対応するページを個別のチャンクとしてバンドルする良い選択肢です。現在、最も一般的な方法は、動的 import を使用することです。React プロジェクトでは、react-loadable というサードパーティライブラリを使用することができます。
import Loadable from 'react-loadable';
import Loading from './my-loading-component';
const LoadableComponent = Loadable({
loader: () => import('./my-component'),
loading: Loading,
});
<Route path="/user" component={LoadableComponent} />
Webpack は自動的にチャンクを分割し、初回のレンダリング時には対応するチャンクのみが読み込まれるため、レンダリング速度が向上します。
必要な moment、lodash、antd のみをバンドルする#
moment、lodash、antd は非常に頻繁に使用されるサードパーティライブラリですが、ライブラリ自体のサイズが非常に大きいため、プロジェクトではすべての機能を使用しない場合があります。すべてをインポートすると、バンドル後のサイズが非常に大きくなってしまいます。
antd は、babel-plugin-import を使用して必要な部分のみをバンドルすることができます。babel-plugin-import をインストールし、.babelrc ファイルを設定するだけです:
{
"plugins": [
[
"import", {
"libraryName": "antd",
"style": true
}
]
]
}
babel-plugin-import は、現在 antd、antd-mobile、lodash などのライブラリで使用できます。
lodash も babel-plugin-import を使用して必要な部分のみをバンドルすることができます。また、babel-plugin-lodash を使用して lodash を必要に応じてバンドルすることもできます。
npm i --save lodash
npm i --save-dev babel-plugin-lodash @babel/cli @babel/preset-env
// .babelrc
{
"plugins": ["lodash"],
"presets": [["@babel/env", { "targets": { "node": 6 } }]]
}
moment は、すべてのローカライズコンテンツとコア機能を一緒にバンドルします。バンドルされた moment には、不要なロケールファイルも含まれます。そのため、moment のロケールファイルを必要に応じてバンドルする必要があります。
ビルド時にローカライズコンテンツを無視するために、IgnorePlugin を使用することができます:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/);
または、webpack の ContextReplacementPlugin を使用することもできます。このプラグインは、指定したファイルを正規表現でフィルタリングすることができます。
以下のコードを webpack の設定ファイルの plugins に追加するだけです:
plugins: [
new webpack.ContextReplacementPlugin(
/moment[/\\]locale$/,
/zh-cn/,
),
],
ここでは、moment/locale ディレクトリの zh-cn ファイルにマッチするように正規表現を使用しています。