This article is a summary of my experience using Webpack 4, introducing some methods to optimize the bundle size of Webpack.
Webpack is currently the mainstream bundling solution for large-scale projects. Since upgrading the React scaffolding to Webpack 4, I have been continuously exploring and experimenting with various methods to optimize Webpack bundling. This article will summarize how to optimize the bundle size of Webpack from the following aspects.
Bundle Analysis#
webpack-bundle-analyzer can provide visual analysis of the chunks outputted by the bundling process. It shows the size of each module after bundling and also provides the size after gzip compression. In the production environment, the modules loaded are all compressed with gzip, which can be used as the basis for the actual size.
npm install --save-dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
Tree-Shaking#
Webpack 4 enables Tree-Shaking by default when the mode is set to production. However, it may be disabled in your project due to Babel configurations.
Tree Shaking relies on the static structure features of the ES2015 module system, such as import and export, to identify unused code. If you are using Babel plugins, such as babel-preset-env, it will default to bundling modules into CommonJS format, which will disable Tree Shaking.
To solve this, you need to configure Babel to not transform modules. The configuration is as follows:
// .babelrc
{
"presets": [
["env", {
modules: false,
...
}]
]
}
Optimization of url-loader and file-loader#
url-loader and file-loader are commonly used to handle background images. By default, they will bundle the background images in CSS into Base64 format. However, if the background images in CSS are too large, it will result in a large CSS file after bundling. In a single-page application, CSS blocks DOM rendering, causing a long white screen time for the homepage. I have encountered a project where the built CSS file was as large as 4MB, severely affecting user experience. The solution is to configure the limit field of url-loader. If the file size exceeds a certain number of bytes, it will not be bundled into Base64. After configuring this, the size of the CSS file after bundling was reduced to around 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]'
}
}
Lazy Loading#
When developing SPA applications with React or Vue, webpack will output a single JS file by default. This means that when rendering the initial screen, all page JS files will be loaded. If the JS file is too large, it will greatly affect the rendering speed of the initial screen. Lazy loading is a good choice, where each route's corresponding page is bundled into a separate chunk. The most popular method currently is to use dynamic import. In a React project, you can use the third-party library 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 will automatically split the chunks, and only the corresponding chunk will be loaded when rendering the initial screen, improving the rendering speed.
Bundle Optimization for moment, lodash, antd#
moment, lodash, and antd are commonly used third-party libraries. However, these libraries have large file sizes, and not all functionalities are used in the project. Importing all of them will result in a large bundle size.
For antd, you can use babel-plugin-import for on-demand loading. Simply install babel-plugin-import and configure the .babelrc file:
{
"plugins": [
[
"import", {
"libraryName": "antd",
"style": true
}
]
]
}
babel-plugin-import can currently be used for antd, antd-mobile, lodash, and other libraries.
For lodash, you can also use babel-plugin-import for on-demand loading, or use babel-plugin-lodash to load lodash on demand.
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 bundles all localization content and core functionalities together. The bundled moment includes many unnecessary locale files. Therefore, we need to selectively bundle the locale files of moment.
You can use IgnorePlugin to ignore the localization content during bundling:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/);
Alternatively, you can use the ContextReplacementPlugin of webpack to override the search rules and filter specific files using regular expressions.
Simply add the following code to the plugins section of the webpack configuration file:
plugins: [
new webpack.ContextReplacementPlugin(
/moment[/\\]locale$/,
/zh-cn/,
),
],
Here, we use a regular expression to match the zh-cn file under moment/locale.