esbuild 与 SWC
问题
esbuild 和 SWC 分别是什么?它们为什么比传统工具快这么多?在现代前端工具链中扮演什么角色?
答案
esbuild 和 SWC 是新一代前端构建/编译工具的代表,分别使用 Go 和 Rust 编写,性能远超基于 JavaScript 的传统工具(Webpack、Babel、Terser 等),已成为 Vite、Next.js、Rspack 等现代框架的底层基础设施。
esbuild 和 SWC 的出现标志着前端工具链从 JavaScript 时代迈入系统语言时代,它们用 10-100 倍的性能提升,彻底改变了前端开发体验。
为什么需要新一代构建工具
传统工具的性能瓶颈
随着前端项目规模的膨胀,基于 JavaScript 的构建工具暴露出严重的性能问题:
| 瓶颈 | 说明 |
|---|---|
| JavaScript 单线程 | V8 引擎本质上是单线程执行,无法充分利用多核 CPU |
| AST 反复序列化 | Babel 插件之间需要反复将 AST 序列化/反序列化为 JSON,开销巨大 |
| 解释执行 | JavaScript 是动态语言,即使有 JIT,也远不如编译型语言 |
| GC 压力 | 大量中间对象导致频繁垃圾回收,造成不可预测的停顿 |
| 进程间通信开销 | Worker 方案需要序列化数据在进程间传递 |
一个典型的 Webpack + Babel + Terser 构建流程中,仅编译和压缩就可能占据 70% 以上的时间:
在大型项目中,Webpack 冷启动可能需要 30 秒到数分钟,HMR 热更新也可能需要 数秒,严重影响开发效率。
系统语言的性能优势
Go 和 Rust 相比 JavaScript 具有天然的性能优势:
| 特性 | JavaScript | Go | Rust |
|---|---|---|---|
| 编译方式 | JIT 解释执行 | 编译为机器码 | 编译为机器码 |
| 并发模型 | 单线程 + Event Loop | Goroutine 轻量线程 | 线程 + 零开销抽象 |
| 内存管理 | GC(不可预测暂停) | GC(低延迟) | 所有权系统(无 GC) |
| 数据共享 | 需要序列化 | 共享内存 | 零拷贝 |
esbuild 深入解析
什么是 esbuild
esbuild 是由 Evan Wallace(Figma 联合创始人)使用 Go 语言编写的超高速 JavaScript/CSS 打包器和转译器。它集打包(bundler)、转译(transpiler)、压缩(minifier)于一体。
esbuild 为什么快
esbuild 的极致性能来源于多个层面的优化:
关键优化策略:
- Go 原生多线程并行:充分利用多核 CPU,解析、链接、代码生成阶段全部并行化
- 内存高效利用:Go 的 goroutine 之间共享内存,无需像 JavaScript Worker 那样序列化传递数据
- 最少的 AST 遍历:一次 AST 遍历完成多种转换,而 Babel 每个插件都需要完整遍历一次 AST
- 从零实现一切:解析器、链接器、代码生成器全部自研,避免第三方库的抽象开销
官方基准测试显示,esbuild 打包 three.js 库:
- esbuild: 0.33s
- Webpack 5: 41.53s
- Rollup + terser: 34.95s
- Parcel 2: 31.39s
快了约 100 倍。
核心功能
1. 打包(Bundling)
esbuild 支持将多个模块打包为单个文件,支持 ESM、CommonJS、IIFE 等输出格式:
import * as esbuild from "esbuild";
await esbuild.build({
entryPoints: ["src/index.ts"],
bundle: true,
outdir: "dist",
format: "esm",
splitting: true, // 代码分割(仅 ESM 格式)
treeShaking: true, // Tree Shaking
platform: "browser",
target: ["es2020"],
sourcemap: true,
});
2. 转译(Transform)
无需打包,直接将代码从一种格式转换为另一种格式:
import * as esbuild from "esbuild";
const result = await esbuild.transform(
`const fn = (x: number): string => x.toString();`,
{
loader: "ts",
target: "es2015", // 转译为 ES2015
}
);
console.log(result.code);
// "const fn = (x) => x.toString();\n"
3. 压缩(Minification)
esbuild 内置高性能压缩器,可替代 Terser:
import * as esbuild from "esbuild";
const result = await esbuild.transform(
`
function calculateTotal(items) {
let total = 0;
for (const item of items) {
total += item.price * item.quantity;
}
return total;
}
`,
{
minify: true,
}
);
console.log(result.code);
// "function calculateTotal(t){let l=0;for(const o of t)l+=o.price*o.quantity;return l}\n"
4. CSS 处理
esbuild 原生支持 CSS 打包与压缩:
import * as esbuild from "esbuild";
await esbuild.build({
entryPoints: ["src/styles/main.css"],
bundle: true,
minify: true,
loader: { ".png": "dataurl", ".svg": "file" },
outdir: "dist",
});
Build API 与 Transform API
esbuild 提供两个核心 API:
| API | 用途 | 读取文件 | 输出文件 |
|---|---|---|---|
| Build API | 打包整个项目 | 从文件系统读取 | 写入文件系统 |
| Transform API | 转换单个文件内容 | 接受字符串输入 | 返回字符串结果 |
完整的 Build API 配置示例:
import * as esbuild from "esbuild";
const ctx = await esbuild.context({
entryPoints: ["src/index.tsx"],
bundle: true,
outdir: "dist",
format: "esm",
platform: "browser",
target: ["chrome90", "firefox88", "safari14"],
// 代码处理
minify: process.env.NODE_ENV === "production",
sourcemap: true,
treeShaking: true,
// 路径与模块解析
alias: { "@": "./src" },
external: ["react", "react-dom"],
resolveExtensions: [".tsx", ".ts", ".jsx", ".js"],
// Loader 配置
loader: {
".png": "file",
".svg": "dataurl",
".woff2": "file",
},
// 环境变量注入
define: {
"process.env.NODE_ENV": '"production"',
},
// 插件
plugins: [],
});
// 开发模式:启动 watch + serve
if (process.env.NODE_ENV === "development") {
await ctx.watch();
const { host, port } = await ctx.serve({ servedir: "dist" });
console.log(`Dev server: http://${host}:${port}`);
} else {
await ctx.rebuild();
await ctx.dispose();
}
esbuild 的局限性
esbuild 追求极致速度,但在功能覆盖面上有所取舍:
| 局限性 | 说明 |
|---|---|
| 不支持 HMR | 没有内置热模块替换,需要依赖上层框架(如 Vite)实现 |
| TS 只转译不检查 | 只做语法转换,不做类型检查(需配合 tsc --noEmit) |
| 不支持装饰器 | 不支持 TC39 Stage 2 的旧装饰器语法(experimentalDecorators) |
| 代码分割能力弱 | 仅支持 ESM 格式的代码分割,且策略较简单 |
| 插件生态较小 | 插件 API 功能有限,生态远不如 Webpack/Rollup |
| 不支持 Browserslist | 不读取 .browserslistrc,需手动指定 target |
SWC 深入解析
什么是 SWC
SWC(Speedy Web Compiler)是由韩国开发者 Donny(강동윤)使用 Rust 语言编写的超高速 JavaScript/TypeScript 编译器。它定位为 Babel 的替代品,同时提供压缩(替代 Terser)和打包(swcpack,实验性)功能。
SWC 为什么快
SWC 的性能优势来源于 Rust 语言的特性:
- Rust 零开销抽象:编译为原生机器码,无运行时开销
- 所有权系统(无 GC):内存由编译器管理,没有垃圾回收暂停
- 零拷贝设计:尽量避免数据复制,直接操作内存引用
- WASM 支持:可编译为 WebAssembly,在 JavaScript 环境中运行,比纯 JS 快 20 倍
- 多线程并行:通过 Rust 的线程安全保证实现安全并行
SWC 的核心优势是编译/转译(替代 Babel),而非打包。它在 Next.js、Rspack、Parcel 等工具中作为编译层使用。
核心功能
1. 转译(Compilation)
SWC 兼容绝大多数 Babel 转译功能:
import { transformSync } from "@swc/core";
const output = transformSync(
`const greet = (name: string): void => {
console.log(\`Hello, \${name}!\`);
};`,
{
jsc: {
parser: {
syntax: "typescript", // 支持 TypeScript
tsx: false,
decorators: true, // 支持装饰器
},
target: "es2020",
},
module: {
type: "es6",
},
}
);
console.log(output.code);
2. 压缩(Minification)
SWC 内置压缩器,可替代 Terser:
import { minifySync } from "@swc/core";
const result = minifySync(
`function calculateTotal(items) {
let total = 0;
for (const item of items) {
total += item.price * item.quantity;
}
return total;
}`,
{
compress: true, // 启用压缩
mangle: true, // 变量名混淆
}
);
console.log(result.code);
3. 打包(swcpack — 实验性)
SWC 还提供了实验性的打包工具 swcpack,但目前尚不成熟,不推荐在生产环境使用:
// swcpack 配置(实验性,不推荐生产使用)
module.exports = {
entry: {
web: __dirname + "/src/index.ts",
},
output: {
path: __dirname + "/dist",
},
};
swcpack 仍处于实验阶段,功能不完整。SWC 的核心价值在于编译和压缩,打包建议使用 Webpack/Vite/Rollup 等成熟工具。
.swcrc 配置示例
{
"$schema": "https://json.schemastore.org/swcrc",
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true,
"decorators": true,
"dynamicImport": true
},
"transform": {
"react": {
"runtime": "automatic",
"importSource": "react"
},
"decoratorVersion": "2022-03"
},
"target": "es2020",
"loose": false,
"minify": {
"compress": {
"unused": true,
"dead_code": true
},
"mangle": true
}
},
"module": {
"type": "es6",
"strict": true
},
"minify": true,
"sourceMaps": true
}
SWC vs Babel 对比
| 对比维度 | SWC | Babel |
|---|---|---|
| 编写语言 | Rust | JavaScript |
| 编译速度 | 比 Babel 快 20-70 倍 | 基准速度 |
| TypeScript | 原生支持(只转译,不检查类型) | 通过 @babel/preset-typescript |
| JSX | 原生支持 | 通过 @babel/preset-react |
| 装饰器 | 支持(包括最新提案) | 通过 @babel/plugin-proposal-decorators |
| 插件系统 | Rust 编写插件或 WASM 插件(生态小) | JavaScript 插件(生态丰富) |
| 配置兼容 | .swcrc,类似 Babel | .babelrc / babel.config.js |
| Polyfill | 不支持(需额外工具) | 通过 @babel/preset-env + core-js |
| Source Map | 支持 | 支持 |
| WASM | 支持在浏览器中运行 | 不适用 |
Babel 最大的优势是其丰富的插件生态。如果项目依赖大量 Babel 插件(如 babel-plugin-import、babel-plugin-styled-components),迁移到 SWC 前需要确认替代方案。
性能对比
esbuild vs SWC vs Babel vs tsc
以下是对 1000 个 TypeScript 文件(约 10 万行代码)进行转译的基准测试对比:
| 工具 | 转译速度 | 打包速度 | 压缩速度 | 语言 |
|---|---|---|---|---|
| esbuild | ~0.3s | ~0.4s | ~0.3s | Go |
| SWC | ~0.5s | 实验性 | ~0.5s | Rust |
| Babel | ~15s | - | - | JavaScript |
| tsc | ~20s | - | - | JavaScript |
| Terser | - | - | ~25s | JavaScript |
以上数据为近似参考值,实际性能取决于项目规模、代码复杂度、机器配置等因素。建议在具体项目中进行实测。
在现代工具链中的应用
esbuild 和 SWC 很少被直接使用,更多的是作为现代工具链的底层引擎:
Vite 中的 esbuild
Vite 在两个关键环节使用 esbuild:
1. 依赖预构建(Pre-Bundling)
Vite 用 esbuild 将 node_modules 中的依赖预构建为 ESM 格式:
import { defineConfig } from "vite";
export default defineConfig({
optimizeDeps: {
// 使用 esbuild 预构建依赖
include: ["lodash-es", "axios"],
exclude: ["my-local-lib"],
esbuildOptions: {
target: "es2020",
// 处理某些库的 JSX
loader: { ".js": "jsx" },
},
},
});
2. TypeScript / JSX 转译
开发阶段,Vite 使用 esbuild 转译 .ts、.tsx、.jsx 文件(比 Babel 快 20-30 倍):
import { defineConfig } from "vite";
export default defineConfig({
esbuild: {
// esbuild 转译配置
target: "es2020",
jsxFactory: "React.createElement",
jsxFragment: "React.Fragment",
drop: ["console", "debugger"], // 生产环境移除
},
});
Vite 生产环境使用 Rollup 而非 esbuild 打包,因为 esbuild 的代码分割和 CSS 处理能力还不够成熟。不过 Vite 使用 esbuild 做压缩(替代 Terser),兼顾了速度和质量。
Next.js 中的 SWC
从 Next.js 12 开始,SWC 取代 Babel 成为默认编译器:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
compiler: {
// SWC 编译器配置
styledComponents: true, // 替代 babel-plugin-styled-components
removeConsole: {
exclude: ["error", "warn"], // 移除 console(保留 error/warn)
},
reactRemoveProperties: true, // 移除 data-testid
},
// SWC 压缩(默认开启)
swcMinify: true,
};
export default nextConfig;
- 编译速度提升 ~17 倍(官方数据)
- 无需配置
.babelrc - 内置常用 Babel 插件的等效功能
- Fast Refresh 更快
Rspack 中的 SWC
Rspack 是字节跳动开源的 Rust 构建工具,内部使用 SWC 作为编译层:
import { defineConfig } from "@rspack/cli";
export default defineConfig({
module: {
rules: [
{
test: /\.tsx?$/,
// 使用内置的 SWC loader
use: {
loader: "builtin:swc-loader",
options: {
jsc: {
parser: {
syntax: "typescript",
tsx: true,
},
transform: {
react: { runtime: "automatic" },
},
},
},
},
type: "javascript/auto",
},
],
},
});
esbuild-loader 替代 babel-loader
对于已有的 Webpack 项目,可以使用 esbuild-loader 无缝替换 babel-loader,获得显著提速:
- npm
- Yarn
- pnpm
- Bun
npm install -D esbuild-loader
yarn add --dev esbuild-loader
pnpm add -D esbuild-loader
bun add --dev esbuild-loader
import type { Configuration } from "webpack";
import { EsbuildPlugin } from "esbuild-loader";
const config: Configuration = {
module: {
rules: [
{
test: /\.tsx?$/,
// 替换 babel-loader 为 esbuild-loader
loader: "esbuild-loader",
options: {
target: "es2020",
},
},
],
},
// 替换 TerserPlugin 为 ESBuildMinifyPlugin
optimization: {
minimizer: [
new EsbuildPlugin({
target: "es2020",
css: true, // 同时压缩 CSS
}),
],
},
};
export default config;
esbuild-loader 是从 Webpack + Babel 迁移到新一代工具链的最低成本方案,无需重构项目配置,只需替换 loader 即可获得 2-5 倍的构建提速。
esbuild vs SWC 选型建议
| 场景 | 推荐工具 | 原因 |
|---|---|---|
| Vite 项目 | esbuild(内置) | Vite 默认使用 esbuild 预构建和转译 |
| Next.js 项目 | SWC(内置) | Next.js 默认使用 SWC 编译 |
| Webpack 项目提速 | esbuild-loader | 最低成本替换 babel-loader |
| 需要 Babel 插件兼容 | SWC | SWC 兼容大多数 Babel 配置和功能 |
| 纯打包/压缩 | esbuild | 打包能力比 SWC 成熟 |
| 需要装饰器 | SWC | esbuild 不支持旧装饰器语法 |
| Rspack/Turbopack | SWC(内置) | Rust 生态天然集成 |
| 简单脚本/工具 | esbuild | API 简洁,上手成本低 |
- 不需要主动选择:大多数情况下,选择上层框架(Vite/Next.js/Rspack)就自动决定了底层引擎
- esbuild 擅长打包和压缩,适合做构建工具的底层
- SWC 擅长编译和转译,适合做 Babel 的替代品
- 两者不是竞争关系,而是各有侧重的互补关系
常见面试问题
Q1: esbuild 为什么比 Webpack/Babel 快这么多?
答案:
esbuild 的性能优势主要来自以下四个方面:
1. 使用 Go 语言编写,编译为原生机器码
Go 是编译型语言,直接编译为机器码执行,而 Webpack/Babel 基于 JavaScript,需要 V8 引擎解释执行(虽有 JIT 优化,但仍远不如原生代码)。
2. Go 的多线程并行能力
// JavaScript(Webpack/Babel):单线程处理
// 文件 A → 文件 B → 文件 C → 文件 D(串行)
// Go(esbuild):多线程并行处理
// 线程1: 文件 A ─┐
// 线程2: 文件 B ─┤── 合并结果
// 线程3: 文件 C ─┤
// 线程4: 文件 D ─┘
Go 的 goroutine 可以轻松创建成千上万个轻量级线程,并且线程间可以共享内存,无需像 JavaScript Worker 那样通过序列化传递数据。
3. 最少的 AST 遍历次数
Babel 的插件架构要求每个插件独立遍历 AST,10 个插件就需要遍历 10 次。而 esbuild 在一次 AST 遍历中完成所有转换:
// Babel 的处理方式:
// 源码 → Parse → AST
// → Plugin 1 遍历 AST → 序列化 → AST
// → Plugin 2 遍历 AST → 序列化 → AST
// → Plugin 3 遍历 AST → 序列化 → AST
// → Generate → 输出
// esbuild 的处理方式:
// 源码 → Parse → AST → 一次遍历完成所有转换 → Generate → 输出
4. 从零自研,没有第三方依赖
esbuild 的解析器、链接器、代码生成器全部自己实现,避免了第三方库之间的抽象层开销和数据转换成本。
Q2: SWC 和 Babel 的区别是什么?
答案:
SWC 和 Babel 都是 JavaScript/TypeScript 编译器(transpiler),但在实现和使用上有显著差异:
核心区别:
| 维度 | SWC | Babel |
|---|---|---|
| 语言 | Rust(编译为原生二进制 + WASM) | JavaScript |
| 速度 | 单线程快 20 倍,多线程快 70 倍 | 基准 |
| 配置 | .swcrc(JSON) | .babelrc / babel.config.js |
| 插件 | Rust/WASM 插件(生态较小) | JavaScript 插件(生态丰富) |
| 装饰器 | 支持最新提案 | 通过插件支持 |
| Polyfill | 不直接支持 | @babel/preset-env + core-js |
代码对比:
// Babel 配置
const babelConfig = {
presets: [
["@babel/preset-env", { targets: "> 0.25%, not dead" }],
"@babel/preset-typescript",
["@babel/preset-react", { runtime: "automatic" }],
],
plugins: [
["@babel/plugin-proposal-decorators", { version: "2023-05" }],
],
};
// 等效的 SWC 配置
const swcConfig = {
jsc: {
parser: {
syntax: "typescript",
tsx: true,
decorators: true,
},
transform: {
react: { runtime: "automatic" },
decoratorVersion: "2022-03",
},
target: "es2020",
},
};
什么时候选 SWC:
- 项目不依赖特殊的 Babel 插件
- 使用 Next.js(默认内置)
- 追求更快的编译速度
- 需要装饰器支持
什么时候留在 Babel:
- 项目依赖大量自定义 Babel 插件
- 需要精细的 Polyfill 控制(
useBuiltIns: "usage") - 项目已稳定运行,迁移成本大于收益
Q3: 在实际项目中如何使用 esbuild 和 SWC?
答案:
在实际项目中,esbuild 和 SWC 的使用方式主要分为三种:
方式一:选择内置了它们的上层框架(推荐)
这是最常见也最省心的方式,无需手动配置:
// Vite 项目 —— 自动使用 esbuild
// 1. 预构建依赖(esbuild)
// 2. 转译 TS/JSX(esbuild)
// 3. 生产压缩(esbuild)
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
// esbuild 自动接管编译,无需额外配置
});
// Next.js 项目 —— 自动使用 SWC
// 只需删除 .babelrc,SWC 自动启用
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
// SWC 默认启用,无需显式配置
compiler: {
styledComponents: true,
},
};
export default nextConfig;
方式二:在 Webpack 项目中引入 esbuild-loader
适用于无法迁移到 Vite 的存量项目:
// 第一步:安装
// npm install -D esbuild-loader
// 第二步:替换 babel-loader
const config = {
module: {
rules: [
{
test: /\.tsx?$/,
// 之前: loader: 'babel-loader'
loader: "esbuild-loader",
options: { target: "es2020" },
},
],
},
};
方式三:直接使用 esbuild/SWC 的 API
适用于构建脚本、CLI 工具、简单项目等场景:
// 使用 esbuild 构建一个简单的库
import * as esbuild from "esbuild";
// 同时输出 ESM 和 CJS 两种格式
const sharedOptions: esbuild.BuildOptions = {
entryPoints: ["src/index.ts"],
bundle: true,
sourcemap: true,
minify: true,
target: "es2020",
external: ["react", "react-dom"],
};
await Promise.all([
esbuild.build({ ...sharedOptions, format: "esm", outfile: "dist/index.mjs" }),
esbuild.build({ ...sharedOptions, format: "cjs", outfile: "dist/index.cjs" }),
]);
console.log("Build complete!");
- 新项目:直接选 Vite(esbuild)或 Next.js(SWC),不用关心底层细节
- 旧 Webpack 项目:用
esbuild-loader替换babel-loader,立竿见影 - 库开发:esbuild 或 tsup(基于 esbuild)打包
- Monorepo 大型项目:考虑 Rspack(基于 SWC),兼容 Webpack 配置