Webpack 4 构建优化篇
随着业务迭代,项目页面数量增加、第三方依赖不断引入,Webpack 会出现两大典型问题:
- 构建编译速度慢(开发环境热更新编译耗时长)
- 打包产物体积臃肿(线上资源加载慢,影响用户体验)
本文以 Webpack 4 为主线,区分构建速度优化、产物体积优化,配套分析工具、可直接复制的配置与实际踩坑经验。
性能分析工具
优化不能靠主观猜测,先采集数据:包体积分析、编译耗时分析,定位真正瓶颈。
webpack-bundle-analyzer 包体积可视化分析
交互式树状图,直观展示每个模块占 bundle 的大小,快速定位体积过大的第三方库、重复依赖。
npm install webpack-bundle-analyzer -D
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'server' // 启动web服务打开分析页面;可选 static 输出静态html文件
})
]
};
也可以通过环境变量控制开关;配置里根据 process.env.npm_config_report 决定是否加入插件,例如:
plugins: process.env.npm_config_report
? [new BundleAnalyzerPlugin()]
: []
NODE_ENV=production npm_config_report=true npm run build

speed-measure-webpack-plugin 编译耗时统计
统计每一个 loader、plugin 的执行耗时,找到拖慢编译的环节。
npm install speed-measure-webpack-plugin -D
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
// 使用 wrap 包裹完整 webpack 配置
const webpackConfig = smp.wrap({
// 原有完整配置写在这里
plugins: []
});
module.exports = webpackConfig;
webpack 内置 --profile 参数,无需插件也可以输出模块耗时日志:
npm run build -- --profile

速度优化
多页面项目热更新优化
Webpack 4 的 html-webpack-plugin(v4+)原生自带缓存机制,多页面项目直接多次实例化即可,不需要第三方补丁插件。
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: './src/index.html',
inject: true,
chunks: ['index']
}),
new HtmlWebpackPlugin({
filename: 'list.html',
template: './src/list.html',
inject: true,
chunks: ['list']
})
]
};
babel-loader 开启缓存
babel-loader 开启 cacheDirectory,将转译结果缓存到磁盘,二次编译跳过未修改文件,显著提升开发编译速度。
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true // 开启磁盘缓存
}
}
}
]
}
};
合理配置 devServer
开发服务器合理配置,减少不必要文件监听、编译开销:
// webpack.dev.js
module.exports = {
devServer: {
hot: true, // 开启HMR热模块替换
watchOptions: {
ignored: /node_modules/ // 忽略node_modules文件监听,减少文件观察数量
}
}
};
备注:这是 webpack-dev-server 3(配合 Webpack 4)的写法;webpack-dev-server 4+ 改为 watchFiles、static.watch。
DllPlugin 预编译静态第三方库(开发环境提速)
DllPlugin 属于开发环境优化手段,生产环境优先使用 splitChunks 做代码分割。
AutoDllPlugin 第三方插件可以简化配置,但存在一些边界兼容问题,推荐直接使用 webpack 原生 DllPlugin + DllReferencePlugin。
新建 webpack.dll.config.js,预编译不常改动的第三方库:
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production',
entry: {
vendor: ['vue/dist/vue.esm.js', 'axios', 'lodash']
},
output: {
path: path.resolve(__dirname, 'dll'),
filename: '[name].dll.js',
library: '[name]_dll'
},
plugins: [
new webpack.DllPlugin({
path: path.resolve(__dirname, 'dll', '[name]-manifest.json'),
name: '[name]_dll',
context: __dirname
})
]
};
执行预编译,生成 dll 文件与清单:
webpack --config webpack.dll.config.js
在业务 webpack.dev.js 使用 DllReferencePlugin 读取清单,跳过这些库的编译。
const webpack = require('webpack');
const path = require('path');
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require(path.resolve(__dirname, 'dll/vendor-manifest.json'))
})
]
};
在 HTML 模板手动引入生成的 vendor.dll.js:
<script src="./dll/vendor.dll.js"></script>
备注:只有新增、升级第三方依赖包时,才需要重新执行 dll 打包;日常业务代码修改,不需要重新打包 dll。
体积优化
Webpack 4 内置 SplitChunksPlugin,不需要手动 new 插件实例,直接在 optimization.splitChunks 配置。
作用:提取重复的业务公共模块、node_modules 第三方库,拆分为独立 chunk,利用浏览器长效缓存。
// webpack.prod.js
module.exports = {
mode: 'production',
optimization: {
// 把 webpack 的 runtime 运行时代码抽离为独立文件,利于缓存
runtimeChunk: true,
splitChunks: {
/**
* chunks
* async:仅分割异步import()的chunk(默认)
* initial:仅分割同步引入的chunk
* all:同步 + 异步全部分割,多页面项目推荐 all
*/
chunks: 'all',
minSize: 30000, // 模块大于30kb才会单独拆分(压缩前大小)
maxSize: 0,
minChunks: 1, // 模块至少被1个chunk引用才拆分
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
// cacheGroups 缓存组,核心配置,可以覆盖外层参数
cacheGroups: {
// 提取 node_modules 第三方库
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
// 业务公共模块,被至少2个chunk引用才抽离
common: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 复用已经存在的chunk,不重复创建
}
}
}
}
};
cacheGroups 关键字段说明:
test:匹配模块路径,满足条件归入该缓存组;priority:优先级,数字越大优先级越高,一个模块匹配多个缓存组时归优先级最高的;reuseExistingChunk:如果 chunk 已经存在,则直接复用,不再生成新文件。
备注:name: true 是 Webpack 4 的写法,表示由 Webpack 自动生成 chunk 名;Webpack 5 不再接受布尔值,需改成字符串、函数或 false。
代码压缩
Webpack 4 在 mode: 'production' 时默认开启代码压缩;也可以手动配置 terser-webpack-plugin,自定义压缩行为。
npm install terser-webpack-plugin -D
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true, // 开启多进程并行压缩,充分利用多核CPU
terserOptions: {
compress: {
drop_console: true // 删除console,可选
}
},
sourceMap: false
})
]
}
};
备注:插件顶层的 sourceMap 是 Webpack 4 所用 terser-webpack-plugin 的写法;较新版本已移除该选项,改为跟随 webpack 的 devtool。
将大型第三方库不参与打包,HTML 通过 CDN 引入,业务代码正常 import,webpack 将该模块从 bundle 中排除,直接减小产物体积。
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
module.exports = {
externals: {
vue: 'Vue' // import vue 映射到全局变量 Vue
}
};
import Vue from 'vue';
resolve 模块解析优化
减少模块查找开销,合理设置别名、检索后缀、检索目录。
const path = require('path');
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
},
extensions: ['.js', '.vue', '.json'], // 只写项目实际用到的后缀,不要写一大堆
modules: [path.resolve(__dirname, 'node_modules')] // 限定模块查找目录
}
};
devtool source-map 合理配置
source-map 会增加编译耗时与产物大小,开发、生产环境分开配置。
关键字含义:
eval:eval 包裹模块代码source-map:输出独立.map文件cheap:不包含列信息module:保留 loader 转换前源码inline:map 嵌入 bundle,不生成独立文件
// webpack.dev.js 开发环境,便于调试
module.exports = {
devtool: 'cheap-module-eval-source-map'
};
// webpack.prod.js 生产环境
module.exports = {
// devtool: 'source-map' // 需要调试线上报错时开启,单独输出map,部署可以不上传map
devtool: false // 关闭source-map,生产追求速度和体积优先
};
备注:cheap-module-eval-source-map 是 Webpack 4 的名称;Webpack 5 改为 eval-cheap-module-source-map。
其他体积优化
MiniCssExtractPlugin 提取 CSS
生产环境不要用 style-loader,使用 MiniCssExtractPlugin 将 CSS 提取为独立 CSS 文件,配合 splitChunks 还可以拆分公共 CSS。
npm install mini-css-extract-plugin optimize-css-assets-webpack-plugin -D
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css'
})
],
optimization: {
minimizer: [
// Webpack 4 写入 minimizer 会覆盖默认压缩器,JS 压缩需要自己带上
new TerserPlugin({
parallel: true,
sourceMap: false
}),
new OptimizeCSSAssetsPlugin()
]
}
};
备注:Webpack 5 改用 css-minimizer-webpack-plugin,并可用 '...' 保留内置 TerserPlugin。
DefinePlugin 注入环境变量
Webpack 4 在 mode: 'production' 时会自动设置 process.env.NODE_ENV;也可以手动使用 DefinePlugin,配合 tree-shaking 剔除死代码。
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
};
合理使用 tree-shaking
tree-shaking 用来剔除未使用 ES Module 代码;要求:
- 使用 ES6
import、export,不使用 CommonJSrequire; mode: 'production';- babel 不要把 ES Module 转成 CommonJS(
@babel/preset-env设置modules: false,交给 Webpack 处理模块); - 依赖包的
package.json正确声明sideEffects,否则有副作用的模块不容易被剔除。
总结
- 废弃清单(不要再使用)
CommonsChunkPlugin:Webpack 4 已移除,替换为splitChunkshtml-webpack-plugin-for-multihtml:仅 Webpack 3,Webpack 4 原生html-webpack-plugin自带缓存increase-memory-limit:废弃,改用NODE_OPTIONS=--max-old-space-size=4096webpack-parallel-uglify-plugin:废弃,Webpack 4 内置 TerserPlugin 支持并行HappyPack:停止维护,优先使用thread-loader
- DllPlugin 定位:主要用于开发环境提速;生产环境优先用 splitChunks 做代码分割,不要把 dll 产物直接作为线上资源。
splitChunks.chunks: 'all':多页面项目建议设置为all,同步、异步模块都参与分割,更充分提取公共代码。- 开发环境优先:
babel-loader的cacheDirectory+devServer忽略node_modules+ DllPlugin(大型项目);生产环境优先:splitChunks+TerserPlugin+MiniCssExtractPlugin+ externals。