用 Go 重写打包器:esbuild 为什么不用缓存也能比 webpack 快 10–100 倍
项目信息:
- GitHub:
- 官网:
- 文档(Getting Started):
- Go 官网:
1. 为什么需要 esbuild
一个常见场景:中型前端项目,几百个文件,用 webpack 打包。改一行代码,等 30 秒才能看到效果。
常见的优化手段挨个试过:
- 开缓存:有点用,但第一次还是慢
- 配多线程:有点用,但配置复杂
- 增量编译:有点用,但有时候不准
- 换 SSD:有点用,但治标不治本
最后会发现,瓶颈在 webpack 本身。webpack 是 JavaScript 写的,单线程,解析、转换、打包都是串行的,项目一大就慢得受不了。
esbuild 的思路是:用 Go 重写,从头优化。
作者的原话:
Our current build tools for the web are 10-100x slower than they could be.
也就是说,现在的 Web 构建工具比它们应有的速度慢 10–100 倍,esbuild 的目标就是把这个速度提上来。
2. esbuild 是什么
esbuild 是一个用 Go 写的 Web 打包工具。官方的描述:
An extremely fast bundler for the web.
具体来说:
- 打包速度极快
- 内置 JavaScript、CSS、TypeScript、JSX 支持
- 提供 CLI、JavaScript、Go 三种 API
- 支持 ESM 和 CommonJS 模块
核心定位
esbuild 不是 webpack 的插件,而是替代品。它做的事情和 webpack 一样:
- 解析源代码
- 处理依赖关系
- 转换语法(TypeScript → JavaScript)
- 打包输出
- 压缩优化
区别在于速度完全不是一个量级。
3. 为什么这么快
原因一:Go 写的
Go 是编译型语言,性能比 JavaScript 好很多。
- webpack:JavaScript 写的,靠 V8 引擎执行
- esbuild:Go 写的,编译成原生二进制
性能差距在 10 倍以上。
原因二:并行处理
esbuild 充分利用多核 CPU。
- webpack:单线程,串行处理
- esbuild:多线程,并行解析、转换、打包
CPU 有几个核,esbuild 就用几个核。
原因三:内存优化
esbuild 尽量减少内存分配和拷贝。
- webpack:大量字符串操作,内存拷贝多
- esbuild:使用字节切片,减少拷贝
原因四:不用缓存
esbuild 快到不需要缓存。
- webpack:必须开缓存,否则每次全量编译
- esbuild:不用缓存,直接编译就比 webpack 快
这意味着不需要清理缓存,不用担心缓存失效,也不用管缓存策略。
4. 安装和使用
方式一:npm 安装
npm install esbuild --save-dev
方式二:全局安装
npm install -g esbuild
方式三:Go 安装
go install github.com/evanw/esbuild/cmd/esbuild@latest
基本使用
1. 命令行
# 打包 JavaScript
esbuild src/index.js --bundle --outfile=dist/index.js
# 打包 TypeScript
esbuild src/index.ts --bundle --outfile=dist/index.js
# 打包 CSS
esbuild src/index.css --bundle --outfile=dist/index.css
# 压缩
esbuild src/index.js --bundle --minify --outfile=dist/index.js
# 生成 source map
esbuild src/index.js --bundle --sourcemap --outfile=dist/index.js
2. JavaScript API
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/index.js',
minify: true,
sourcemap: true,
}).catch(() => process.exit(1));
3. Go API
package main
import (
"github.com/evanw/esbuild/pkg/api"
)
func main() {
result := api.Build(api.BuildOptions{
EntryPoints: []string{"src/index.js"},
Bundle: true,
Outfile: "dist/index.js",
Minify: true,
Sourcemap: api.SourceMapLinked,
})
if len(result.Errors) > 0 {
// 处理错误
}
}
4. 开发服务器
esbuild src/index.js --bundle --outdir=dist --serve=8000 --servedir=www
5. Watch 模式
esbuild src/index.js --bundle --outfile=dist/index.js --watch
5. 核心功能
功能一:模块打包
同时支持 ESM 和 CommonJS,esbuild 会自动转换:
// ESM
import { foo } from './foo.js';
// CommonJS
const bar = require('./bar.js');
功能二:TypeScript 支持
直接编译 TypeScript,不需要 tsc:
esbuild src/index.ts --bundle --outfile=dist/index.js
支持:
- 类型注解(编译时删除)
- 接口、枚举
- 装饰器(实验性)
- tsconfig.json 配置
功能三:JSX 支持
直接编译 JSX,不需要 Babel,支持 React、Preact 等:
esbuild src/App.jsx --bundle --outfile=dist/App.js
功能四:CSS 支持
打包 CSS,支持 CSS Modules:
esbuild src/index.css --bundle --outfile=dist/index.css
支持:
@importurl()重写- CSS Modules
- 自动前缀(实验性)
功能五:Tree Shaking
自动删除未使用的代码:
// foo.js
export function used() { return 'used'; }
export function unused() { return 'unused'; }
// index.js
import { used } from './foo.js';
console.log(used());
打包后 unused 函数会被删除。
功能六:压缩
内置压缩器,可以替代 terser:
esbuild src/index.js --bundle --minify --outfile=dist/index.js
压缩内容包括变量名缩短、空白删除、语句优化。
6. 实战场景
场景一:替代 webpack
问题是 webpack 打包太慢。配置如下:
// esbuild.config.js
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/index.js',
minify: true,
sourcemap: true,
target: ['es2020'],
loader: {
'.js': 'jsx',
'.ts': 'tsx',
'.png': 'dataurl',
'.svg': 'dataurl',
},
define: {
'process.env.NODE_ENV': '"production"',
},
}).catch(() => process.exit(1));
node esbuild.config.js
打包时间从 30 秒降到 1 秒。
场景二:TypeScript 编译
问题是 tsc 编译太慢:
# 单文件
esbuild src/index.ts --outfile=dist/index.js
# 多文件
esbuild "src/**/*.ts" --outdir=dist
# Watch 模式
esbuild src/index.ts --outfile=dist/index.js --watch
编译时间从 10 秒降到 0.5 秒。
场景三:开发服务器
需要热更新时:
esbuild src/index.js --bundle --outdir=dist --serve=8000 --servedir=public
浏览器访问 http://localhost:8000,自动加载打包后的代码。改代码后自动刷新,毫秒级响应。
场景四:插件扩展
需要自定义处理逻辑时,可以写插件:
const esbuild = require('esbuild');
const { execSync } = require('child_process');
// 自定义插件:运行 Tailwind CSS
const tailwindPlugin = {
name: 'tailwind',
setup(build) {
build.onEnd(() => {
execSync('tailwindcss -i src/index.css -o dist/index.css');
});
},
};
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/index.js',
plugins: [tailwindPlugin],
});
这样可以在 esbuild 流程中集成其他工具。
7. 设计亮点
Go 实现:原生二进制,性能优秀;跨平台,编译一次到处运行;并发支持好。
单二进制:esbuild 是一个单独的二进制文件,没有依赖。安装简单,启动快,不需要 Node.js 环境。
多语言 API:提供 CLI、JavaScript、Go 三种 API,易于集成,适配不同场景。
内置功能:TypeScript、JSX、CSS 都内置支持,不需要额外工具,配置简单,构建链短。
插件系统:支持自定义插件,可扩展,有社区贡献,能适配特殊需求。
8. 和类似方案对比
vs. webpack
| 维度 | esbuild | webpack |
|---|---|---|
| 语言 | Go | JavaScript |
| 速度 | 极快 | 慢 |
| 生态 | 较小 | 丰富 |
| 插件 | 实验性 | 成熟 |
| 配置 | 简单 | 复杂 |
结论:esbuild 快,webpack 生态好。
vs. Rollup
| 维度 | esbuild | Rollup |
|---|---|---|
| 速度 | 极快 | 中等 |
| 定位 | 应用 | 库 |
| Tree Shaking | 好 | 更好 |
| 代码分割 | 支持 | 有限 |
结论:esbuild 快,Rollup 打包库更好。
vs. Vite
| 维度 | esbuild | Vite |
|---|---|---|
| 定位 | 打包工具 | 构建框架 |
| 速度 | 极快 | 快 |
| 开发体验 | CLI | 完整方案 |
| 生产构建 | esbuild | Rollup |
结论:esbuild 是工具,Vite 是框架。Vite 生产构建用的是 Rollup,不是 esbuild。
9. 局限性
- 生态不成熟:插件数量不如 webpack
- 不支持 HMR:需要自己实现热更新
- 代码分割有限:不如 webpack 灵活
- 某些特性是实验性的:CSS 自动前缀、装饰器等
- 调试困难:报错信息不如 webpack 清晰
- 不适合复杂场景:微前端、模块联邦等
10. 总结
从 esbuild 这个项目能看出几个趋势:
- 性能是刚需,开发者受够了慢速构建
- Go 可以写前端工具,不只 JavaScript 能写前端
- 单二进制是趋势:简单、快速、无依赖
- 内置功能受欢迎,不需要一堆配置
- 替代方案有市场,webpack 不是唯一选择
esbuild 生态不如 webpack,但它解决的是真问题。如果受够了 webpack 的慢速,想试更快的构建工具,esbuild 值得一试。
参考资料
- esbuild GitHub:
- esbuild 官网:
- esbuild 文档:
- Go 语言: