原来Webpack在大厂中这样进行性能优化!
性能优化方案
优化分类:
- 优化打包后的结果(分包、减小包体积、CDN 服务器) ==> 更重要
- 优化打包速度(exclude、cache-loader)
代码分割(Code Splitting)
一、主要目的
- 减少首屏加载体积:避免一次性加载全部代码
- 利用浏览器缓存:第三方库(如 React、Lodash)变动少,可单独缓存
- 按需加载/并行请求:路由、组件、功能模块只在需要时加载(按需加载或者并行加载文件,而不是一次性加载所有代码)
二、三种主要的代码分割方式
1. 入口起点(Entry Points)手动分割
通过配置多个 entry 实现。
// webpack.config.js
module.exports = {
entry: {
main: './src/main.js',
vendor: './src/vendor.js', // 手动引入公共依赖
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
},
};
缺点:
- 无法自动提取公共依赖(比如
main和vendor都用了 Lodash,会重复打包)- 维护成本高
上面写的是通用配置,但我们在公司一般会分别配置开发和生产环境的配置。大多数项目中,entry 在 dev 和 prod 基本一致,无需差异化配置。差异主要体现在 output 和其他插件/加载器行为上。
// webpack.config.prod.js
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'js/[name].[contenthash:8].js', // 生产环境用 [contenthash](而非 [hash] 或 [chunkhash]),确保精准缓存
chunkFilename: 'js/[name].[contenthash:8].js',
path: path.resolve(__dirname, 'dist'), // 必须输出到磁盘用于部署
publicPath: '/static/', // 用于 CDN 或静态资源服务器
clean: true, // 清理旧文件
},
};
// webpack.config.dev.js
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'js/[name].js', // 开发环境若加 hash,每次保存都会生成新文件,可能干扰热更新或者devtools混乱
chunkFilename: 'js/[name].js',
path: path.resolve(__dirname, 'dist'), // 通常仍写 dist,但实际不写入磁盘(webpack-dev-server 默认内存存储),节省IO,提高编译速度
publicPath: '/', // 与 devServer 一致
// clean: false (默认)
},
};
2. SplitChunksPlugin(推荐!自动代码分割)
自动提取公共模块和第三方库。webpack 已默认安装相关插件。
默认行为(仅在 production 模式生效):
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'async', // 默认只分割异步模块
},
},
};
常用配置:
// webpack.config.prod.js
optimization: {
// 自动分割
// https://twitter.com/wSokra/status/969633336732905474
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
splitChunks: {
// chunks: async | initial(对通过的代码处理) | all(同步+异步都处理)
chunks: 'initial',
minSize: 20000, // 模块大于 20KB 才分割(Webpack 5 默认值)
maxSize: 244000, // 单个 chunk 最大不超过 244KB(可选)
cacheGroups: { // 拆分分组规则
// 提取 node_modules 中的第三方库
vendor: {
test: /[\\/]node_modules[\\/]/, // 匹配符合规则的包
name: 'vendors', // 拆分包的name 属性
chunks: 'initial',
priority: 10, // 优先级高于 default
enforce: true,
},
// 提取多个 chunk 公共代码
default: {
minChunks: 2, // 至少被 2 个 chunk 引用
priority: -20,
reuseExistingChunk: true, // 复用已存在的 chunk
maxInitialRequests: 5, // 默认限制太小,无法显示效果
minSize: 0, // 这个示例太小,无法创建公共块
},
},
},
// runtime相关的代码是否抽取到一个单独的chunk中,比如import动态加载的代码就是通过runtime 代码完成的
// 抽离出来利于浏览器缓存,比如修改了业务代码,那么runtime加载的chunk无需重新加载
runtimeChunk: true,
}
在开发环境下 splitChunks: false, 即可。
生产环境:
- 生成
vendors.xxxx.js(第三方库)- 生成
default.xxxx.js(项目公共代码)- 主 bundle 体积显著减小
3. 动态导入(Dynamic Imports)—— 按需加载
使用 import() 语法(符合 ES Module 规范),实现懒加载。
Webpack 会为每个
import()创建一个独立的 chunk,并自动处理加载逻辑。
三、魔法注释(Magic Comments)—— 控制 chunk 名称等行为
// 自定义 chunk 名称(便于调试和长期缓存)
const module = await import(
/* webpackChunkName: "my-module" */
'./my-module'
);
其他常见注释:
/* webpackPrefetch: true */:空闲时预加载(提升后续访问速度)/* webpackPreload: true */:当前导航关键资源预加载(慎用)
// 预加载“下一个可能访问”的页面
import(
/* webpackChunkName: "login-page" */
/* webpackPrefetch: true */
'./LoginPage'
);
详细比较:
- preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
- preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
CND
内容分发网络(Content Delivery Network 或 Content Distribution Network)
它是指通过相互连接的网络系统,利用最靠近每个用户的服务器;更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户;提供高性能、可扩展性及低成本的网络内容传递。
工作中,我们使用 CDN 的主要方式有两种:
- 打包所有静态资源,放到 CDN 服务器,用户所有资源都是通过 CND 服务器加载的
- 通过
output.publicPath改为自己的的 CDN 服务器,打包后就可以从上面获取资源 - 如果是自己的话,一般会从阿里、腾讯等买 CDN 服务器。
- 通过
- 一些第三方资源放在 CDN 服务器上
- 一些库/框架会将打包后的源码放到一些免费的 CDN 上,比如 JSDeliver、bootcdn 等
- 这样的话,打包的时候就不需要对这些库进行打包,直接使用 CDN 服务器中的源码(通过 externals 配置排除某些包)
CSS 提取
将 css 提取到一个独立的 css 文件。
npm install mini-css-extract-plugin -D
// webpack.config.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: 'production',
module: {
rules: [
// 生产环境:使用 MiniCssExtractPlugin.loader
{
test: /\.css$/i,
use: [
MiniCssExtractPlugin.loader, // 替换 style-loader
'css-loader',
'postcss-loader',
],
},
{
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader',
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].css',
}),
],
};
Terser 代码压缩
Terser 可以帮助我们压缩、丑化(混淆)我们的代码,让我们的 bundle 变得更小。
Terser 是一个单独的工具,拥有非常多的配置,这里我们只讲工作中如何使用,以一个工程的角度学习这个工具。
真实开发中,我们不需要手动的通过 terser 来处理我们的代码。webpack 中 minimizer 属性,在 production 模式下,默认就是使用的 TerserPlugin 来处理我们代码的。我们也可以手动创建 TerserPlugin 实例覆盖默认配置。
// webpack.prod.js
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'production',
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true, // 多核 CPU 并行压缩,默认为true,并发数默认为os.cpus().length-1
terserOptions: {
compress: { // 压缩配置
drop_console: true,
drop_debugger: true, // 删除debugger
pure_funcs: ['console.info', 'console.debug'], // 只删除特定的函数调用
},
mangle: true, // 是否丑化代码(变量)
toplevel: true, // 顶层变量是否进行转换
keep_classnames: true, // 是否保留类的名称
keep_fnames: true, // 是否保留函数的名称
format: {
comments: /@license|@preserve/i, // 保留含 license/preserve 的注释(某些开源库要求保留版权注释)
},
},
extractComments: true, // 默认为true会将注释提取到一个单独的文件(这里用于保留版权注释),false表示不希望保留注释
sourceMap: true, // 需要 webpack 配置 devtool 生成 source map
}),
],
},
};
不要在开发环境启动 terser,因为:
- 压缩会拖慢构建速度
- 混淆后的代码无法调试
- hmr 和 source-map 会失效
CSS 压缩
CSS 压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等;我们一般使用插件 css-minimizer-webpack-plugin;他的底层是使用 cssnano 工具来优化、压缩 CSS(也可以单独使用)。
使用也是非常简单:
minimizer: [
new CssMiniMizerPlugin()({
parallel: true
})
]
Tree Shaking 摇树
详情见之前文章:《简单聊聊 webpack 摇树的原理》
HTTP 压缩
HTTP 压缩(HTTP Compression)是一种 在服务器和客户端之间传输数据时减小响应体体积 的技术,通过压缩 HTML、CSS、JavaScript、JSON 等文本资源,显著提升网页加载速度、节省带宽。
一、主流压缩算法
| 算法 | 兼容性 | 压缩率 | 速度 | 说明 |
|---|---|---|---|---|
| gzip | ✅ 几乎所有浏览器(IE6+) | 高 | 快 | 最广泛使用,Web 标准推荐 |
| Brotli (br) | ✅ 现代浏览器(Chrome 49+, Firefox 44+, Safari 11+) | ⭐ 更高(比 gzip 高 15%~30%) | 较慢(压缩),解压快 | 推荐用于静态资源 |
| deflate | ⚠️ 支持不一致(部分浏览器实现有问题) | 中 | 中 | 已基本淘汰,不推荐使用 |
二、工作原理(协商压缩)
HTTP 压缩基于 请求头 ↔ 响应头协商机制:
- 客户端请求(表明支持的压缩格式)
GET /app.js HTTP/1.1
Host: example.com
Accept-Encoding: gzip, deflate, br // 客户端支持的压缩算法列表
- 服务端响应(返回压缩后的内容)
HTTP/1.1 200 OK
Content-Encoding: br // 服务端使用的压缩算法
Content-Type: application/javascript
Content-Length: 102400 // 注意:这是压缩后的大小!
...(二进制压缩数据)...
- 浏览器自动解压,开发者无感知
三、如何启用 HTTP 压缩?
我们一般会优先使用 Nginx 配置做压缩(生产环境最常用),这样就无需应用层处理。
除此之外,我们还会进行预压缩 + 静态文件服务,这主要就是 webpack 要做的工作。
在构建阶段(Webpack/Vite)就生成 .gz 和 .br 文件,部署到 CDN 或静态服务器。
// webpack.config.js
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
plugins: [
// 生成 .gz 文件
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 8192, // 大于 8KB 才压缩
minRatio: 0.8, // 至少的压缩比例
}),
// 生成 .br 文件(需额外安装)
new CompressionPlugin({
algorithm: 'brotliCompress',
test: /\.(js|css|html|svg)$/,
compressionOptions: { level: 11 }, // 最高压缩率
}),
],
};
Nginx 配合预压缩文件:
gzip_static on; # 优先返回 .gz 文件
brotli_static on; # 优先返回 .br 文件
打包分析
打包时间分析
我们需要借助一个插件 speed-measure-webpack-plugin,即可看到每个 loader、每个 plugin 消耗的打包时间。
// webpack.config.js
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();
const config = {
// 你的正常 Webpack 配置
entry: './src/index.js',
module: { /* ... */ },
plugins: [ /* ... */ ],
};
// 仅当环境变量 ANALYZE_SPEED=1 时包裹配置
module.exports = process.env.ANALYZE_SPEED ? smp.wrap(config) : config;
打包文件分析
方法一、生成 stats.json 文件
"build:stats": "w--config ./config/webpack.common.js --env production --profile --json=stats.json",
运行 npm run build:stats,可以获取到一个 stats.json 文件,然后放到到 http://webpack.github.com/analyse 进行分析。
方法二、webpack-bundle-analyzer
更常用的方式是使用 webpack-bundle-analyzer 插件分析。
// webpack.prod.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
mode: 'production',
plugins: [
// 其他插件...
new BundleAnalyzerPlugin({
analyzerMode: 'static', // 生成静态 HTML 报告(默认)
openAnalyzer: false, // 不自动打开浏览器
reportFilename: 'bundle-report.html',
generateStatsFile: true, // 可选:同时生成 stats.json
statsFilename: 'stats.json',
}),
],
};

浙公网安备 33010602011771号