干了这么多年前端,几乎每个项目做到中后期都要被同一个问题摁在地上摩擦:构建越来越慢,产物越来越大。早上到公司跑一次 npm run build,运气好两分钟,运气不好直接去泡杯咖啡回来还在转圈。webpack构建优化这个话题,我估计每个团队都聊过,但真正系统性做下来、并且把收益量化出来的,其实不多。
这篇东西不打算把所有插件都给你铺一遍,那不叫优化,叫收藏夹。我把自己在多个项目中真正验证过、能稳定缩短构建时间并控制产物体积的做法整理成一套可以直接抄的配置和流程,覆盖从“为什么慢”到“怎么改”再到“怎么避免改坏”的完整链路。适合正在负责前端工程化、或者是被构建速度和包体积双重折磨的读者。
1. 构建链路慢在哪:先找准瓶颈再动手
1.1 一次打包到底经历了什么
很多人一上来就翻社区帖子,看到 thread-loader 就加,看到 dll 就上,结果配置写了一堆,构建时间纹丝不动。问题出在没搞明白 webpack 构建说到底是个什么流程。
可以把一次构建想象成厨师备菜:入口文件是菜单,webpack 拿着菜单从入口开始递归找出所有依赖的模块,这个过程叫依赖图构建;每个模块都要经过 loader 转换,相当于把每样食材切好、预处理;最后把所有模块按规则合并输出成 bundle,也就是装盘。大部分项目的慢,都出在第二步——loader 在单线程里对成百上千个文件做字符串处理,比如 Babel 转译 JS、less/sass 编译样式、ESLint 校验代码,这些步骤天然吃 CPU。
还有一个经常被忽略的细节:webpack 默认情况下每个模块都要完整走一遍解析、转换、生成三个环节,中间没有任何复用。也就是说,你本地改了 10 行代码,重新构建时所有模块都要重新来一遍,而不是只处理改动的部分。这也是为什么项目越大,构建越慢的根源所在。
1.2 四条优化主线:缩小范围、缓存复用、并行计算、产物瘦身
在动手改配置之前,脑子里最好先立住一个框架。我一般把所有 webpack 构建优化手段归成四类:
- 缩小范围:让 webpack 少干活,比如限制 loader 的处理目录、精简 resolve 的查找路径、减少依赖编译。
- 缓存复用:让 webpack 不重复干活,比如持久化缓存、loader 级缓存。
- 并行计算:让多个 CPU 核心同时干活,比如多进程压缩、多线程 loader。
- 产物瘦身:让最终生成的代码更小,比如拆包、压缩、按需加载、Tree Shaking。
这四条主线不是并列关系。正确顺序应该是先用“缩小范围”把不必要的工作砍掉,再上缓存,最后才考虑并行。因为并行是有通信开销的,如果项目本身只有 200 个模块,开 8 个线程去跑,反而可能更慢。很多人的问题就是跳过了前两步,直接堆并行插件。
1.3 先量化,再优化
还有一个原则要放在最前面:一切优化都要以数据为准。接手的项目,我会先跑一次完整的构建,记录下总耗时、各个阶段的耗时占比(webpack 5 里可以开 stats 或者用 speed-measure-webpack-plugin),然后用 webpack-bundle-analyzer 看产物体积分布。
这一步非常重要。因为“慢”是一个感性判断,没有基线数据,改了半天根本不知道是变快了还是变慢了。我见过有人为了省 2 秒把缓存配置折腾一整天,结果一查数据,真正的瓶颈是图片没压缩,白白浪费了大量时间。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. loader 与 resolve 配置:把不必要的计算挡在门外
2.1 限定 loader 处理范围:include 与 exclude 是第一步
绝大多数项目的 loader 规则长这样:test: /\.js$/ 然后不加任何限制,让 Babel 去处理整个项目里所有 .js 文件。这包括 node_modules 里那几百 MB 的第三方依赖。
但问题来了:node_modules 里的代码大多数已经是 ES5 或者经过 babel 预编译的,根本不值得再让 Babel 处理一遍。所以在 loader 上做范围限定,是所有优化里操作成本最低、收益却最立竿见影的一步。
javascript复制{
test: /\.jsx?$/,
// 只处理 src 目录下的文件,这能直接砍掉 60% 以上的模块量
include: path.resolve(__dirname, 'src'),
use: ['babel-loader']
}
include 指的是“只要这个目录下的文件”,exclude 是“除了这个目录都处理”,两者一起用也行。但我的习惯是只用 include,因为白名单比黑名单更可控,新增的目录不会意外漏掉处理。
同样的思路用在 resolve.modules 上。webpack 解析模块时默认会一层一层往上找 node_modules,如果你把项目的 node_modules 路径直接指定到绝对路
