Webpack 构建优化
Webpack 构建速度与打包体积优化方案
问题
如何优化 Webpack 的构建速度和打包体积?涉及 Tree Shaking、SplitChunks、DllPlugin、并行构建、缓存等技术。
解答
1. Tree Shaking
移除未使用的代码,需要 ES Module 语法支持。
// webpack.config.js
module.exports = {
mode: 'production', // 生产模式自动开启
optimization: {
usedExports: true, // 标记未使用的导出
minimize: true, // 压缩时移除未使用代码
},
};
// package.json - 标记无副作用的模块
{
"sideEffects": false
// 或指定有副作用的文件
// "sideEffects": ["*.css", "./src/polyfill.js"]
}
// utils.js
export function used() {
return 'I am used';
}
export function unused() {
return 'I will be removed';
}
// main.js
import { used } from './utils';
console.log(used()); // unused 会被 Tree Shaking 移除
2. SplitChunks 代码分割
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all', // 对所有类型的 chunk 生效
minSize: 20000, // 最小 20kb 才分割
minChunks: 1, // 至少被引用 1 次
maxAsyncRequests: 30,
maxInitialRequests: 30,
cacheGroups: {
// 第三方库单独打包
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
},
// 公共模块
common: {
minChunks: 2, // 至少被 2 个 chunk 引用
name: 'common',
priority: 5,
reuseExistingChunk: true,
},
},
},
},
};
3. DllPlugin 预编译
将不常变动的库预先打包,加速后续构建。
// webpack.dll.config.js - 预编译配置
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
vendor: ['react', 'react-dom', 'lodash'], // 要预编译的库
},
output: {
path: path.resolve(__dirname, 'dll'),
filename: '[name].dll.js',
library: '[name]_library',
},
plugins: [
new webpack.DllPlugin({
name: '[name]_library',
path: path.resolve(__dirname, 'dll/[name].manifest.json'),
}),
],
};
// webpack.config.js - 主配置引用 DLL
const webpack = require('webpack');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
manifest: require('./dll/vendor.manifest.json'),
}),
new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname, 'dll/vendor.dll.js'),
}),
],
};
# 先执行 DLL 构建(只需执行一次)
webpack --config webpack.dll.config.js
# 再执行正常构建
webpack
4. 并行构建
// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'thread-loader', // 多线程处理
options: {
workers: 4, // worker 数量
},
},
'babel-loader',
],
},
],
},
optimization: {
minimizer: [
new TerserPlugin({
parallel: true, // 并行压缩 JS
}),
new CssMinimizerPlugin({
parallel: true, // 并行压缩 CSS
}),
],
},
};
5. 缓存策略
// webpack.config.js
const path = require('path');
module.exports = {
// 持久化缓存(Webpack 5)
cache: {
type: 'filesystem', // 文件系统缓存
cacheDirectory: path.resolve(__dirname, '.webpack_cache'),
buildDependencies: {
config: [__filename], // 配置文件变化时使缓存失效
},
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true, // babel-loader 缓存
},
},
},
],
},
output: {
// 内容哈希用于浏览器缓存
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
},
};
6. 其他优化
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
// 缩小查找范围
resolve: {
extensions: ['.js', '.jsx'], // 减少扩展名尝试
alias: {
'@': path.resolve(__dirname, 'src'),
},
modules: [path.resolve(__dirname, 'node_modules')], // 指定模块目录
},
// 排除不需要解析的库
module: {
noParse: /jquery|lodash/, // 这些库不需要解析依赖
},
// 外部依赖(CDN 引入)
externals: {
react: 'React',
'react-dom': 'ReactDOM',
},
plugins: [
// 分析打包结果
new BundleAnalyzerPlugin(),
],
};
关键点
- Tree Shaking:依赖 ES Module 静态分析,需配置
sideEffects标记无副作用模块 - SplitChunks:按策略分割代码,分离第三方库和公共模块,利用浏览器缓存
- DllPlugin:预编译不常变动的库,避免重复构建,Webpack 5 中可用持久化缓存替代
- 并行构建:
thread-loader多线程编译,TerserPlugin并行压缩 - 缓存:Webpack 5 的
filesystem缓存、babel-loader缓存、contenthash浏览器缓存 - 缩小范围:
resolve.modules、noParse、externals减少不必要的处理
目录