01|如果说前三篇讲的是“制度为什么分裂”,那第四篇要讲的就是:分裂之后到底谁来维持秩序
写到这里,模块化江湖其实已经出现了一个非常典型的 JavaScript 式局面。
第一篇讲的是,旧时代靠全局变量、命名空间、IIFE 和 module pattern 这些聪明补丁硬撑。
第二篇讲的是,CommonJS 先在 Node.js 里把模块制度做成了运行时现实。
第三篇讲的是,浏览器没有照抄 CommonJS,于是 AMD / RequireJS 按浏览器现实长出了自己的制度。
到这里,问题就会非常直接:
既然 Node 有 Node 的制度,浏览器有浏览器的制度,那跨两边写代码的人到底怎么办?
这就是第四篇真正要讲的起点。
因为历史接下来的走向并不是:
某一方突然宣布胜利,所有人立刻改宗。
真正发生的是:
大家先活在分裂现实里,然后不得不发明一套能把这些分裂暂时收拢起来的工具秩序。
那套秩序,就是 bundler。
所以第四篇不能把 bundler 写成普通构建工具。
更准确的说法应该是:
在标准还没来得及统一天下、运行时现实又彼此不肯服从的时候,bundler 先成了那部临时宪法。
02|bundler 之所以会崛起,不是因为大家突然爱上“打包”,而是因为模块制度已经分裂到必须有人做翻译
这点一定要先立住。
今天很多人一听 bundler,会先想到:
- 压缩代码
- 合并文件
- 构建产物
- 开发服务器
这些当然都对。
但如果把 bundler 只理解成“把很多文件打成一个文件”,那就把它写轻了。
bundler 最早真正重要起来的原因,不是体力活。
而是制度活。
因为模块世界到了那个阶段,已经出现了几种互相并不完全兼容的现实:
Node.js这边默认CommonJS- 浏览器这边有
AMD - 老项目里还有全局脚本和非模块代码
- 社区还在不断发布新包、新格式、新约定
这时候最麻烦的不是“有没有模块”。
而是:
模块太多种了。
而且每一种都不是假的。
每一种背后都有自己的环境逻辑。
于是问题就从“设计一个模块制度”升级成了:
怎么把多套已经活下来的制度,勉强编译进同一个可运行现实里。
这件事,标准当时还做不了。
运行时也做不了。
最后只能由工具链顶上。
这就是 bundler 崛起的真正时机。
不是所有人突然信仰了打包。
而是分裂现实终于逼出了一个总协调员。
03|所以 Browserify 真正做的,不只是“把文件合起来”,而是第一次把 Node 式模块世界批量搬回浏览器
如果要给 bundler 时代找一个真正的开场人物,Browserify 很难绕开。
它最有代表性的一句话,其实已经把自己的历史角色写得非常清楚了:
browser-side require() the node.js way
这句话太关键。
因为它说明 Browserify 最早的野心,并不是重新发明浏览器模块制度。
它是在做另一件事:
既然浏览器不能原生接受 Node 那套 require() 现实,那就由构建工具先帮你把它翻译成浏览器能吃下去的产物。
于是浏览器侧第一次出现了一种非常有冲击力的工作流:
- 开发时按 Node /
CommonJS写 - 构建时静态分析
require()图 - 输出时打成浏览器能直接跑的 bundle
这非常重要。
因为它等于在说:
运行时不统一没关系,先让工具链替你统一。
从这里开始,bundler 的角色就已经不只是“优化器”。
它开始变成:
现实翻译器。
而 Browserify 的历史意义,也就不只是“一个老 bundler”。
更是:
它第一次大规模证明,工具链可以先把制度差异吃掉,再把一个看起来统一的运行结果交给浏览器。
04|这一步一旦成立,模块化的主战场就开始从“运行时 API 长什么样”转向“构建器到底肯替你解释多少现实”
这一层特别重要。
因为很多人回头看模块史,会下意识觉得:
CommonJS 和 AMD 争的是语法。
其实不只是。
真正更深的变化是:
当 bundler 进场之后,模块化的主战场就不再只在运行时,而开始大规模转移到构建阶段。
以前大家争的是:
require还是define- 同步还是异步
- 浏览器还是 Node
后来大家越来越在意的,则变成:
- 构建器能不能理解这种模块格式
- 能不能吃下第三方包
- 能不能处理旧脚本和新模块混用
- 能不能把一堆现实拼成一个可部署结果
注意,这个变化非常大。
因为它意味着“模块制度的执行权”开始部分从运行时转交给工具链。
也就是说,从这个阶段开始,真正回答“你的代码最后怎么算一个模块”的,
很多时候已经不是浏览器,也不是 Node。
而是:
你构建时用的那个 bundler。
这就是为什么我说 bundler 是临时宪法。
因为它不是单纯做后勤。
它开始实际裁定:
- 哪些依赖怎么解析
- 哪些格式如何兼容
- 哪些资源也算模块
- 哪些边界可以被合并
这已经很像制度执行者了。
05|而 webpack 为什么后来会变成真正的“临时宪法”,不是因为它最复杂,而是因为它最像一个总协调政府
如果 Browserify 证明了“工具链可以先翻译现实”,那 webpack 则把这件事推到了另一个层级。
因为它面对的问题,已经不只是:
怎么把 CommonJS 带回浏览器。
它面对的是一个更乱的时代:
CommonJSAMD- 老式全局脚本
- CSS、图片、字体这些前端资源
- 大型 SPA 的代码分裂和按需加载
这时如果 bundler 还只是“把 JS 合起来”,就明显不够了。
webpack 的官方说法其实特别能说明问题:
它会从 entry 开始建立 dependency graph,把项目需要的模块组合成一个或多个 bundles。
这句话表面上很技术。
可它底下真正厉害的地方在于:
它开始把“整个应用如何被拆分、解释、重组、分发”视为一个统一问题。
所以 webpack 为什么后来会让很多人觉得又强又重?
不是因为它爱复杂。
而是因为它接过来的现实本来就已经复杂到了不能靠单一模块格式解决。
它必须同时协调:
- 多种模块系统
- 多种资源类型
- 初始包和异步 chunk
- 开发态和生产态
从这个意义上说,webpack 已经不像一个“模块工具”。
它更像一个:
临时中央政府。
在正式宪法还没完全落地之前,先替大家维持秩序运转。
06|所以 bundler 真正做的,不只是打包,而是把“模块是什么”这件事扩大成了“任何可进入依赖图的东西都可以先算模块”
这也是第四篇不能漏掉的一层。
因为 bundler 一旦开始掌握解释权,它就会自然把“模块”的边界往外扩。
webpack 在这方面尤其典型。
它后来之所以影响那么大,很重要的一点就在于:
它不再把模块只理解成 JavaScript 文件。
而是越来越倾向于把这些东西都拉进同一套依赖图里:
- JS
- JSON
- CSS
- 图片
- 字体
- 预处理语言产物
这一步非常重要。
因为它意味着模块化的含义已经变了。
它不再只是“代码怎么 import 代码”。
它开始变成:
整个前端应用的静态资源,到底如何被统一组织、统一解析、统一输出。
这也就是为什么后来的前端工程化会越来越重。
不是大家无聊。
而是 bundler 一旦成为制度协调者,它就不可能只管一小块事。
它会自然往整个构建现实扩张。
从这里开始,模块化已经不再只是语言内部问题。
它开始吞并应用交付问题本身。
07|可 bundler 的伟大和麻烦,也恰恰在同一个地方:它确实暂时统一了现实,但统一方式是“由工具链替全世界做决定”
这点一定要写出来。
不然第四篇会变成单纯赞歌。
bundler 之所以能当临时宪法,当然有它伟大的一面。
因为它真的收拾了残局:
- 不同模块格式能先共存
- 浏览器不支持的现实能先被编译过去
- 大型应用终于有了可控的构建出口
可问题也正出在这里。
因为这意味着很多关键决定,并不是由语言标准统一给出,也不是由运行时原生提供,而是由某个具体工具链体系决定。
于是新的复杂度也会随之长出来:
- 配置越来越重
- loader / plugin 生态越来越庞大
- 同一个项目越来越依赖某个 bundler 的世界观
- “能不能跑”越来越取决于构建系统是否答应
也就是说,bundler 解决了一轮制度分裂,
但它的解决方式不是“彻底终结复杂度”。
而是:
先把复杂度集中到自己身上。
这就是为什么 bundler 一边像救世主,一边又经常被骂成怪兽。
因为它本来就是临时宪法的典型命运:
在秩序混乱时不可或缺,
可一旦管得越来越多,它自己也会变重。
08|而 Rollup 之所以值得在这一篇出场,是因为它提醒大家:bundler 后来已经不只是兼容机器,还是静态模块制度的解释器
如果第四篇只讲 Browserify 和 webpack,还差一点。
因为 bundler 的历史并没有停在“把不同格式拼起来”。
到了 Rollup 这条线,事情又往前走了一步。
Rollup 官方后来反复强调:
ES modules是明确的前进方向- 静态结构允许更好的分析
- tree-shaking、scope-hoisting 之类优化会因此更高效
这说明什么?
说明 bundler 后来已经不只是“替多套制度擦屁股”的中介。
它开始进一步承担另一种角色:
当 ESM 逐渐成形之后,bundler 还要负责解释、利用和榨干这种静态模块制度的优势。
这就非常关键了。
因为它意味着 bundler 的权力并没有随着标准前进而立刻消失。
恰恰相反。
在很多阶段里,它反而更重要了。
因为标准告诉了你“应然结构”,
但怎么把这种结构变成更小、更快、更适合部署的现实产物,
还得靠 bundler。
所以 Rollup 的出场提醒我们:
bundler 不是只活在前标准时代,它也深度塑造了后标准时代大家如何实际使用标准模块。
09|为什么在标准和运行时都没统一之前,bundler 会先变成临时宪法
把前面这些线叠一起看,第四篇最重要的判断可以压成一句:
在标准来不及统一、运行时又彼此分裂时,bundler 先替 JavaScript 世界执行了统一。
这里最重要的是“执行”两个字。
因为 bundler 并不一定亲自发明所有规则。
可它负责把这些规则变成能落地的现实。
它会实际决定:
- 依赖图怎么走
- 资源怎么被纳入模块边界
- 哪些模块格式怎么互操作
- 哪些代码进初始包,哪些进异步块
这已经不是简单辅助了。
这是制度执行层。
所以第四篇真正要讲的,并不是“前端为什么要打包”。
更要讲的是:
为什么在那段历史里,真正让多套模块现实勉强共存并持续运转下去的,不是标准文本,而是构建工具。
这也就是 bundler 作为“临时宪法”的真正分量。
10|bundler 先统一的,不是文件,而是分裂后的治理
模块化江湖的第四篇,最值得记住的,不是“bundler 可以把很多文件合成一个文件”这种表层定义。
更值得记住的是:
bundler 真正解决的,不只是文件数量问题,而是模块制度分裂后的治理问题。
Browserify 让大家第一次大规模看到:运行时不统一没关系,工具链可以先把 Node 式模块翻译给浏览器。
webpack 则进一步证明:一旦现实复杂到不只是 JS 模块本身,bundler 就会被推成一个总协调政府。
而 Rollup 又提醒大家:即便 ESM 逐步到来,bundler 也不会立刻退场,因为它还负责把静态模块制度解释成真正高效的部署现实。
所以第四篇如果只记一句,就记这句:
当 CommonJS、AMD、浏览器现实和 Node 现实谁都没法统一全场时,bundler 就成了那部临时宪法,它先替 JavaScript 世界把分裂的模块现实执行成了一个还能继续往前走的秩序。
编者注(事实核对):文中关于 Browserify 的定位,主要依据其官方 README 与 handbook 中“browser-side require() the node.js way”“compile node-flavored CommonJS modules for the browser”等表述。关于 webpack 的定位,主要依据官方 Concepts、Why webpack、Under the Hood 等文档,其中明确强调 dependency graph、chunks、loaders 与 plugins 的统一作用。关于 Rollup 与 ES modules、tree-shaking 的关系,主要依据 Rollup FAQ 中对静态分析与 tree-shaking 的说明。正文将 bundler 概括为“临时宪法”,属于基于其在多模块格式并存时期承担的协调与执行角色做出的总结。
关键人物速览
- James Halliday:
Browserify的发起者。第四篇里他代表的是“先让 Node 式模块世界在浏览器里活起来”的那条线。 - Tobias Koppers:
webpack的发起者。理解 bundler 为什么会从模块工具长成总协调系统,绕不开他。 - Rich Harris:
Rollup的发起者。理解 bundler 后来为什么进一步转向静态模块分析与 tree-shaking,他这条线很关键。
参考与延伸阅读
Browserify Handbook
https://github.com/browserify/browserify-handbookBrowserify README
https://github.com/browserify/browserifywebpack Concepts
https://webpack.js.org/concepts/Why webpack
https://webpack.js.org/concepts/why-webpack/webpack Under the Hood
https://webpack.js.org/concepts/under-the-hood/webpack Modules
https://webpack.js.org/concepts/modules/Rollup FAQ
https://rollupjs.org/faqs/
下篇预告:ES Modules 为什么赢了,却没有一夜之间结束战争。