
最近在浏览各个技术平台的前端热门文章,发现大家正陷入一种极其普遍的技术焦虑里。
只要 Rsbuild、Rolldown 或者是某个底层的 Rust 构建工具发了新版本,必然会有一批文章晒出 benchmark 跑分图:冷启动零点几秒,热更新一百多毫秒,全方位碾压过去的 Webpack 甚至早期的 Vite。
甚至在很多团队的半年 OKR 规划里,技术负责人们为了凑出一个亮眼的工程优化指标,硬生生把 迁移最新构建工具,提效 50% 写了进去。
圈子里正在蔓延一种极其危险的错觉:似乎只要把底层的打包引擎换成带 Rust/Go 基因的新玩意儿,前端工程化所有的历史包袱和性能痛点就能迎刃而解🤔。
作为这两年亲自下场,主导过几十万行代码的巨石应用改造的老兵,我今天想跟你们聊一聊这个话题。
先听我一句劝:如果你们公司的业务线还能正常迭代,冷启动没有卡到让你想砸键盘的地步,千万别再瞎折腾换打包工具了🤷♂🤔。
迁移是需要成本的
在 Demo 阶段,换个打包工具确实就是改一下 package.json 里的命令,把配置文件换个名字。 但在真实的商业级工程里,这叫排雷。
现代前端工程最大的毒瘤,是我们强依赖的那些良莠不齐的 npm 包。Webpack 为什么慢?因为它背负了前端十多年的历史债务,它内部有极其宽容的降级机制和模块解析策略。你塞给它一段极不规范的代码,它能连滚带爬地帮你打成产物。
而那些追求极致性能的现代 ESM 构建工具链,底层逻辑往往是非常严苛的静态分析。
我给你举一个我们在真实项目中踩过的深坑。 业务里有一个极度复杂且年代久远的 Excel 纯前端导出依赖,由于作者当年为了兼容服务端同构,在源码的深处写了这种逻辑:
javascript
// 某个老旧依赖代码
let streamLib;
try {
// 纯浏览器端根本不需要它,但在做静态分析时会报错
streamLib = require('stream');
} catch (e) {
streamLib = null;
}
在 Webpack 时代,我们在配置里加一句 fallback: { stream: false } 就天下太平了。
但当我们试图把这个几万个模块的项目迁移到要求严格的极速构建工具时,灾难降临了。新的底层引擎在处理 CommonJS 向 ESM 转换(比如依赖 Rollup 的 resolve 机制)时,死活绕不过这种动态注入的模块。
为了让本地那句 冷启动 0.8s 的提示跑通,团队不得不花了好几天时间,在新的配置里写下了一堆极其别扭、极易失效的补丁:
javascript
// 为了强行抹平历史债务,手写的配置
export default defineConfig({
resolve: {
alias: {
// 强行塞入浏览器端的 polyfill 去填坑
stream: 'stream-browserify',
buffer: 'buffer/'
}
},
build: {
rollupOptions: {
// 排除外部依赖,生怕影响线上的其他模块
external:['fsevents', 'crypto']
}
}
})
本地确实跑绿了,但代价是什么?
是构建产物里混入了我们根本无法完全把控的 polyfill。 你确定底层切换后,Tree Shaking 还会按原先的逻辑安全剔除无用代码吗?你确定 CSS 抽取插件的 Hash 生成策略,不会导致线上某两个按钮的样式发生极其偶发的覆盖吗?
如果没有覆盖率极高的 E2E 自动化测试兜底,在成熟业务里换底层打包工具,就是在赌命。
算一算极其不划算的投入产出比
我们理性地算一笔账。
你们团队投入了两个高级前端,看了无数篇文档,提了各种 Issue,用各种 Hack 手段处理了 50 多个依赖冲突,终于用两周时间完成了新打包工具的迁移。
你们的收益是什么? 是开发每天本地启动项目时,少等了那 10 秒钟;是 CI 流水线打包时,快了那 1 分钟。
但这带来的隐性成本是极其恐怖的:
线上跑着的核心业务,它的地基被换了,任何一个没测到的边缘场景都可能引发 P0 级白屏。
商业公司的技术迭代是有成本边界的。老板发工资,是让你去解决复杂的业务流转逻辑、去提升 C 端用户的首屏体验,而不是让你为了开发环境快那几秒钟,去把整个技术地基挖开重新填一次。
如果真的慢到无法忍受,高级前端该怎么解?😢
当然,如果你的项目冷启动真的要 3 分钟,热更新保存一下要等 10 秒,那确实严重阻碍了产出。
但这根本不是打包工具的错,这是架构的错。
编译慢的本质,是你们把所有的烂代码、重型库、甚至不同业务线的逻辑,全部揉在了一个单体巨石应用里。
解决这个问题最干净、最彻底的方法,是业务解耦与物理隔离,而不是去奢求一个跑得更快的编译器。
老老实实把项目拆了。把几万年都不改的底层组件库、富文本编辑器引擎、大型第三方黑盒依赖,抽离成独立的 npm 包,或者引入 Monorepo(比如 pnpm workspace)做物理级别的隔离与预编译。
json
// 物理级别的架构解耦,才是解决构建慢的终极手段
{
"name": "company-core-monorepo",
"workspaces":[
"packages/shared-utils", // 纯逻辑,预编译好,主项目无需二次解析
"packages/heavy-components", // 静态重型组件,隔离打包
"apps/main-admin" // 真正的业务外壳,极其轻量
]
}
当你把业务主干剥离干净,让应用层只保留纯粹的视图拼接和轻量状态流转时。你会发现,哪怕你用着最老旧的 Webpack,项目的冷启动和热更新照样是秒开。
最后说一说
在如今这个技术迭代快到让人窒息的年代,保持定力,是一个资深开发者最稀缺的品质。
对新工具保持敏感度绝对是好事,你在新开的个人项目里,尽情去享受 Rust 引擎带来的极致速度,那是一种享受。
但面对跑着几万、几十万日活的商业项目,请保持绝对的敬畏。工具终究只是个锤子,别天天纠结你手里的锤子是铁打的还是什么,先看看眼前业务 BUG 修好了没有🤷♂️。
在复杂的工程世界里,技术选型的第一铁律永远是:
只要没坏,就不要改🫡
