构建工具三十年演进史

构建工具三十年演进史

写在前面

这个系列的上一篇《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 解决了模块化,但还有更多事情要做:

这催生了任务运行器:

// 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 引入了核心创新:把一切都当作模块。

// 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,画出依赖图,把所有模块打包成静态资源。

这彻底解决了三个问题:

  1. 模块化(所有资源统一处理)
  2. 依赖管理(自动分析)
  3. 代码分割(拆分 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 走了一条不同的路:

// 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,自动:

优势:新人上手快 劣势:复杂项目配置反而麻烦,性能瓶颈明显

五、esbuild 时代(2020-)

5.1 速度瓶颈暴露

2020 年,前端项目的规模到了临界点:

这是体验的瓶颈,不是 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 有几个硬伤:

它更适合做"转换器",不适合做"应用打包器"。

六、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 的局限

七、Rspack / Rsbuild 时代(2023-)

7.1 字节跳动的痛点

字节跳动内部有几千个 webpack 项目,webpack 的速度是工程效率的瓶颈。

字节内部尝试过:

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' },
        ],
    },
};

架构:

速度:同样的 webpack 配置,Rspack 比 webpack 快 5-10 倍。

7.3 Rsbuild

2023 年字节又开源了 Rsbuild——Rspack 之上的封装:

// rsbuild.config.ts
export default {
    plugins: [pluginReact(), pluginTypeScript()],
    html: {
        template: './index.html',
    },
};

特点:

对比 Vite:Rsbuild 的优势是完全兼容 webpack 生态——存量项目可以无痛迁移。

八、Turbo / Bun / 新势力(2023-)

8.1 Turbopack

Vercel 团队(Next.js 母公司)2022 年开源 Turbopack:

但生态局限于 Next.js——独立使用体验不佳。

8.2 Bun

2022 年发布的 Bun,不只是一个打包器,是全栈运行时:

Bun 的打包器基于 Zig,性能接近 esbuild。

8.3 Farm

2024 年开源的 Farm,专注"通用"——不绑定任何框架:

九、演进的核心规律

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 我会选什么?

公司项目:

十一、本篇小结

时代 代表工具 核心创新
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 秒。

参考资料