构建工具三十年演进史
构建工具三十年演进史
写在前面
这个系列的上一篇《create react app 创建的项目改为使用 rsbuild》讲了一次排障故事。这次完整展开前端构建工具的演进脉络——为什么会有 webpack,为什么 esbuild 那么快,为什么 Vite 突然流行,为什么字节又做了 Rspack。理解这条线,后面所有的工程问题都顺理成章。
一、浏览器脚本时代(1995-2005)
1.1 一切的开始
1995 年 Brendan Eich 在网景花了 10 天设计了 JavaScript。最初只是给 HTML 加点小动画,完全不需要构建。
<!-- 那时候的网页 -->
<html>
<head>
<script>
alert("Hello, 1995!");
</script>
</head>
</html>
连文件都不用引入,直接 <script> 标签里写代码。
1.2 多文件引用
代码量变大后,把 JS 拆成多个文件:
<script src="jquery.js"></script>
<script src="app.js"></script>
<script src="analytics.js"></script>
问题:
- 全局变量污染
- 文件加载顺序要小心
- 没有模块化机制
二、CommonJS / RequireJS 时代(2009-2012)
2.1 Node.js 催生 CommonJS
2009 年 Node.js 出现,带来了 CommonJS 模块化:
// a.js
const b = require('./b');
module.exports = function() { return b.run(); };
// b.js
exports.run = () => 'hello';
但浏览器不支持 require——需要工具把模块化代码转换成浏览器能跑的代码。
2.2 RequireJS / SeaJS
最早的解决方案是 AMD(Asynchronous Module Definition):
// 用 RequireJS
define(['jquery', './utils'], function($, utils) {
return {
init() { return utils.doSomething(); }
};
});
特点:
- 异步加载
- 适合浏览器
但是写起来繁琐,生态不温不火。
三、Browserify / Webpack 时代(2012-2016)
3.1 Browserify
2011 年发布的 Browserify,把 Node.js 的 CommonJS 直接搬到浏览器:
browserify main.js -o bundle.js
// 浏览器里也能 require
const $ = require('jquery');
const utils = require('./utils');
第一次让前端开发体验接近 Node。
3.2 Grunt / Gulp
Browserify 解决了模块化,但还有更多事情要做:
- 压缩 JS/CSS
- 编译 Sass / Less
- 压缩图片
- 跑单元测试
这催生了任务运行器:
// Gruntfile.js
grunt.initConfig({
concat: {
dist: {
src: ['src/*.js'],
dest: 'dist/bundle.js',
},
},
uglify: {
dist: {
src: 'dist/bundle.js',
dest: 'dist/bundle.min.js',
},
},
});
grunt.registerTask('default', ['concat', 'uglify']);
// gulpfile.js
gulp.task('scripts', () =>
gulp.src('src/*.js')
.pipe(concat('bundle.js'))
.pipe(uglify())
.pipe(gulp.dest('dist'))
);
Grunt/Gulp 的问题:只关心"任务",不关心"依赖"。改一个文件,需要手动配置哪些任务要重跑。
3.3 Webpack 横空出世
2012 年发布的 webpack 引入了核心创新:把一切都当作模块。
- JS 是模块
- CSS 是模块(
import './style.css') - 图片是模块(
import logo from './logo.png') - 字体、SVG、视频……都是模块
// webpack 入口
import $ from 'jquery';
import './style.css';
import logo from './logo.png';
console.log($);
console.log(logo); // data URL 或 URL
// webpack.config.js
module.exports = {
entry: './src/index.js',
output: './dist/bundle.js',
module: {
rules: [
{ test: /\.css$/, use: 'css-loader' },
{ test: /\.png$/, use: 'file-loader' },
],
},
};
核心思想:依赖图(dependency graph)。
webpack 从入口开始,递归解析所有 import/require,画出依赖图,把所有模块打包成静态资源。
这彻底解决了三个问题:
- 模块化(所有资源统一处理)
- 依赖管理(自动分析)
- 代码分割(拆分 chunk)
3.4 webpack 的早期版本
webpack 1.0(2014 年)非常快,配置也简单:
module.exports = {
entry: './src/index.js',
output: './dist/bundle.js',
module: {
loaders: [
{ test: /\.css$/, loader: 'style!css' },
],
},
};
但从 webpack 2 / 3 / 4 / 5 一路走来,配置越来越复杂,插件生态膨胀到令人恐惧。
四、Rollup / Parcel 时代(2015-2019)
4.1 Rollup
2015 年发布的 Rollup 走了一条不同的路:
- 专注 Library 打包(不是应用)
- Tree Shaking(消除无用代码)做得比 webpack 好
- ES Module 原生支持
// rollup.config.js
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'es',
},
};
Tree Shaking 原理:用 ES Module 的静态结构(import/export 不能动态生成),分析哪些导出没被使用,从 bundle 里删掉。
React、Vue、Lodash 都用 Rollup 打包——库的代码追求极致的小,Tree Shaking 优势巨大。
4.2 Parcel
2017 年发布的 Parcel 打着 “零配置” 的旗号:
parcel index.html
不需要任何 config,自动:
- 处理 JS / CSS / HTML
- 跑 Babel / PostCSS / TypeScript
- 自动 HMR
- 代码分割
优势:新人上手快 劣势:复杂项目配置反而麻烦,性能瓶颈明显
五、esbuild 时代(2020-)
5.1 速度瓶颈暴露
2020 年,前端项目的规模到了临界点:
- 一个中型 React 应用:
node_modules2GB+ - webpack 冷启动:2-5 分钟
- 改一行代码 HMR:5-30 秒
这是体验的瓶颈,不是 webpack 设计的问题——webpack 的 JS 实现、Node.js 的单线程,决定了它的天花板。
5.2 esbuild 出现
2020 年,Evan Wallace(就是 Figma 的 CTO)开源了 esbuild:
esbuild app.js --bundle --outfile=out.js
实测速度:同样的项目,webpack 60 秒,esbuild 0.5 秒。
为什么这么快:
| 优化 | 效果 |
|---|---|
| Go 语言实现 | 比 JS 快 10-100 倍 |
| 并行解析 | 多核 CPU 充分利用 |
| 内存中表示 AST | 不反复解析 |
| 不做 source map 生成(默认) | 节省时间 |
| 不做 type check | TS 不做类型检查 |
| 不打 bundle(默认) | 只转换单文件 |
5.3 esbuild 的局限
快不是万能。esbuild 有几个硬伤:
- 不做 TS 类型检查——只删除类型,不报错
- 不优化 CSS(只压缩)
- 不支持复杂的代码分割
- 不能做完整的 HMR(开发服务器)
它更适合做"转换器",不适合做"应用打包器"。
六、Vite 时代(2020-)
6.1 Vite 的魔法
2020 年底尤雨溪发布 Vite,核心创新:开发环境用浏览器原生 ESM,生产环境用 Rollup。
开发环境:不打包!
// 浏览器直接 import
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
Vite 在开发服务器把每个文件单独处理,返回给浏览器原生支持的 ESM,省掉了打包时间。
冷启动:200ms(几乎瞬开),不管项目多大 HMR:50ms(几乎瞬热)
6.2 生产环境
生产环境用 Rollup:
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue'],
},
},
},
},
};
兼顾开发体验和生产产物。
6.3 Vite 的局限
- 生态还在追赶 webpack——某些 webpack 插件没有 Vite 版本
- **大型项目(几千个文件)**dev 启动后首次加载慢(浏览器要请求很多模块)
- 生产构建速度比纯 esbuild 慢(因为还是 Rollup)
七、Rspack / Rsbuild 时代(2023-)
7.1 字节跳动的痛点
字节跳动内部有几千个 webpack 项目,webpack 的速度是工程效率的瓶颈。
字节内部尝试过:
- 用 esbuild 替代 webpack——生态不兼容,很多 webpack loader/plugin 用不了
- 用 Vite——同上
2022 年字节开源了 Rspack:用 Rust 写的 webpack 兼容方案。
7.2 Rspack 的核心
// rspack.config.js - 和 webpack 配置几乎一样
module.exports = {
entry: './src/index.js',
output: './dist/bundle.js',
module: {
rules: [
{ test: /\.css$/, use: 'css-loader' },
],
},
};
架构:
- Rust 核心(类似 webpack 的打包逻辑)
- swc 替代 Babel(TS/JSX 转换)
- loader 兼容 webpack 生态(90%+ 兼容)
- plugin 兼容 webpack 生态(大部分兼容)
速度:同样的 webpack 配置,Rspack 比 webpack 快 5-10 倍。
7.3 Rsbuild
2023 年字节又开源了 Rsbuild——Rspack 之上的封装:
// rsbuild.config.ts
export default {
plugins: [pluginReact(), pluginTypeScript()],
html: {
template: './index.html',
},
};
特点:
- 开箱即用(Babel、TypeScript、CSS Modules、CSS 预处理器、静态资源——全部内置)
- 约定大于配置(类似 Next.js / Nuxt 的开发体验)
- 底层是 Rspack(速度快)
对比 Vite:Rsbuild 的优势是完全兼容 webpack 生态——存量项目可以无痛迁移。
八、Turbo / Bun / 新势力(2023-)
8.1 Turbopack
Vercel 团队(Next.js 母公司)2022 年开源 Turbopack:
- 用 Rust 写
- Next.js 默认(13.5+)
- 增量编译速度极快
但生态局限于 Next.js——独立使用体验不佳。
8.2 Bun
2022 年发布的 Bun,不只是一个打包器,是全栈运行时:
- JavaScript 运行时(替代 Node)
- 包管理器(替代 npm/yarn/pnpm)
- 打包器(替代 webpack)
- 测试运行器(替代 Jest)
Bun 的打包器基于 Zig,性能接近 esbuild。
8.3 Farm
2024 年开源的 Farm,专注"通用"——不绑定任何框架:
- Rust 实现
- 支持 Webpack-like 配置和 Vite-like 配置
- 多线程并行打包
九、演进的核心规律
9.1 性能 vs 生态的权衡
慢 + 全生态 ──── webpack
│
▼
快 + 兼容性差 ──── esbuild / Turbopack
│
▼
快 + 生态兼容 ──── Rspack / Vite / Rollup
Rspack 的成功在于:既快又兼容。
9.2 每次进步都在解决一个问题
| 工具 | 解决的核心问题 |
|---|---|
| Grunt/Gulp | 任务自动化 |
| Browserify | Node 模块化搬到浏览器 |
| webpack | 一切资源模块化 + 依赖图 |
| Rollup | Tree Shaking + Library 优化 |
| esbuild | 编译速度 |
| Vite | 开发体验(冷启动 + HMR) |
| Rspack | 速度 + 生态兼容 |
没有"最好"的工具,只有"最适合"的工具。
9.3 语言决定上限
| 语言 | 性能上限 | 代表 |
|---|---|---|
| JavaScript | 低 | webpack, Rollup, Parcel |
| Go | 高 | esbuild |
| Rust | 极高 | Rspack, Turbopack, swc |
| Zig | 高 | Bun |
这是 CPU 密集型任务的规律——瓶颈决定上限。
十、选型建议
10.1 决策树
新项目 + 要快 → Vite / Rsbuild
存量 webpack → 评估迁移到 Rspack
打 npm 包 → Rollup
Next.js 项目 → Turbopack(默认)
极致性能要求 → Farm / 自研
10.2 我会选什么?
公司项目:
- 新项目:Rsbuild(速度快 + 生态兼容)
- 存量 webpack:逐步迁移到 Rspack(改配置文件就行)
- 库代码:Rollup(Tree Shaking + ESM)
- 开发小工具:Vite(冷启动快)
十一、本篇小结
| 时代 | 代表工具 | 核心创新 |
|---|---|---|
| 1995-2005 | 无 | 浏览器原生 |
| 2009-2012 | RequireJS, Browserify | 模块化 |
| 2012-2016 | Grunt/Gulp, webpack | 一切资源模块化 |
| 2015-2019 | Rollup, Parcel | Tree Shaking, 零配置 |
| 2020- | esbuild, Vite | Go/Rust, ESM 原生 |
| 2023- | Rspack, Rsbuild | 速度 + 兼容 |
下一篇讲 webpack 的辉煌与瓶颈——大型 SPA 启动 90 秒怎么压到 5 秒。