跳到主要内容

esbuild 与 SWC

问题

esbuild 和 SWC 分别是什么?它们为什么比传统工具快这么多?在现代前端工具链中扮演什么角色?

答案

esbuildSWC 是新一代前端构建/编译工具的代表,分别使用 GoRust 编写,性能远超基于 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 具有天然的性能优势:

特性JavaScriptGoRust
编译方式JIT 解释执行编译为机器码编译为机器码
并发模型单线程 + Event LoopGoroutine 轻量线程线程 + 零开销抽象
内存管理GC(不可预测暂停)GC(低延迟)所有权系统(无 GC)
数据共享需要序列化共享内存零拷贝

esbuild 深入解析

什么是 esbuild

esbuild 是由 Evan Wallace(Figma 联合创始人)使用 Go 语言编写的超高速 JavaScript/CSS 打包器和转译器。它集打包(bundler)、转译(transpiler)、压缩(minifier)于一体。

esbuild 为什么快

esbuild 的极致性能来源于多个层面的优化:

关键优化策略

  1. Go 原生多线程并行:充分利用多核 CPU,解析、链接、代码生成阶段全部并行化
  2. 内存高效利用:Go 的 goroutine 之间共享内存,无需像 JavaScript Worker 那样序列化传递数据
  3. 最少的 AST 遍历:一次 AST 遍历完成多种转换,而 Babel 每个插件都需要完整遍历一次 AST
  4. 从零实现一切:解析器、链接器、代码生成器全部自研,避免第三方库的抽象开销
性能数字

官方基准测试显示,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 等输出格式:

build.ts
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)

无需打包,直接将代码从一种格式转换为另一种格式:

transform.ts
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:

minify.ts
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 打包与压缩:

css-build.ts
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 配置示例

esbuild.config.ts
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 不适合的场景

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 语言的特性:

  1. Rust 零开销抽象:编译为原生机器码,无运行时开销
  2. 所有权系统(无 GC):内存由编译器管理,没有垃圾回收暂停
  3. 零拷贝设计:尽量避免数据复制,直接操作内存引用
  4. WASM 支持:可编译为 WebAssembly,在 JavaScript 环境中运行,比纯 JS 快 20 倍
  5. 多线程并行:通过 Rust 的线程安全保证实现安全并行
SWC 的定位

SWC 的核心优势是编译/转译(替代 Babel),而非打包。它在 Next.js、Rspack、Parcel 等工具中作为编译层使用。

核心功能

1. 转译(Compilation)

SWC 兼容绝大多数 Babel 转译功能:

swc-transform.ts
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

swc-minify.ts
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,但目前尚不成熟,不推荐在生产环境使用:

spack.config.ts
// swcpack 配置(实验性,不推荐生产使用)
module.exports = {
entry: {
web: __dirname + "/src/index.ts",
},
output: {
path: __dirname + "/dist",
},
};
注意

swcpack 仍处于实验阶段,功能不完整。SWC 的核心价值在于编译和压缩,打包建议使用 Webpack/Vite/Rollup 等成熟工具。

.swcrc 配置示例

.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 对比

对比维度SWCBabel
编写语言RustJavaScript
编译速度比 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-importbabel-plugin-styled-components),迁移到 SWC 前需要确认替代方案。

性能对比

esbuild vs SWC vs Babel vs tsc

以下是对 1000 个 TypeScript 文件(约 10 万行代码)进行转译的基准测试对比:

工具转译速度打包速度压缩速度语言
esbuild~0.3s~0.4s~0.3sGo
SWC~0.5s实验性~0.5sRust
Babel~15s--JavaScript
tsc~20s--JavaScript
Terser--~25sJavaScript
测试数据说明

以上数据为近似参考值,实际性能取决于项目规模、代码复杂度、机器配置等因素。建议在具体项目中进行实测。

在现代工具链中的应用

esbuild 和 SWC 很少被直接使用,更多的是作为现代工具链的底层引擎

Vite 中的 esbuild

Vite 在两个关键环节使用 esbuild:

1. 依赖预构建(Pre-Bundling)

Vite 用 esbuild 将 node_modules 中的依赖预构建为 ESM 格式:

vite.config.ts
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 倍):

vite.config.ts
import { defineConfig } from "vite";

export default defineConfig({
esbuild: {
// esbuild 转译配置
target: "es2020",
jsxFactory: "React.createElement",
jsxFragment: "React.Fragment",
drop: ["console", "debugger"], // 生产环境移除
},
});
Vite 为什么不完全用 esbuild?

Vite 生产环境使用 Rollup 而非 esbuild 打包,因为 esbuild 的代码分割和 CSS 处理能力还不够成熟。不过 Vite 使用 esbuild 做压缩(替代 Terser),兼顾了速度和质量。

Next.js 中的 SWC

从 Next.js 12 开始,SWC 取代 Babel 成为默认编译器:

next.config.ts
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;
Next.js + SWC 的好处
  • 编译速度提升 ~17 倍(官方数据)
  • 无需配置 .babelrc
  • 内置常用 Babel 插件的等效功能
  • Fast Refresh 更快

Rspack 中的 SWC

Rspack 是字节跳动开源的 Rust 构建工具,内部使用 SWC 作为编译层:

rspack.config.ts
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 install -D esbuild-loader
webpack.config.ts
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 插件兼容SWCSWC 兼容大多数 Babel 配置和功能
纯打包/压缩esbuild打包能力比 SWC 成熟
需要装饰器SWCesbuild 不支持旧装饰器语法
Rspack/TurbopackSWC(内置)Rust 生态天然集成
简单脚本/工具esbuildAPI 简洁,上手成本低
选型建议总结
  • 不需要主动选择:大多数情况下,选择上层框架(Vite/Next.js/Rspack)就自动决定了底层引擎
  • esbuild 擅长打包和压缩,适合做构建工具的底层
  • SWC 擅长编译和转译,适合做 Babel 的替代品
  • 两者不是竞争关系,而是各有侧重的互补关系

常见面试问题

Q1: esbuild 为什么比 Webpack/Babel 快这么多?

答案

esbuild 的性能优势主要来自以下四个方面:

1. 使用 Go 语言编写,编译为原生机器码

Go 是编译型语言,直接编译为机器码执行,而 Webpack/Babel 基于 JavaScript,需要 V8 引擎解释执行(虽有 JIT 优化,但仍远不如原生代码)。

2. Go 的多线程并行能力

parallelism-comparison.ts
// 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 遍历中完成所有转换:

ast-traversal.ts
// 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),但在实现和使用上有显著差异:

核心区别

维度SWCBabel
语言Rust(编译为原生二进制 + WASM)JavaScript
速度单线程快 20 倍,多线程快 70 倍基准
配置.swcrc(JSON).babelrc / babel.config.js
插件Rust/WASM 插件(生态较小)JavaScript 插件(生态丰富)
装饰器支持最新提案通过插件支持
Polyfill不直接支持@babel/preset-env + core-js

代码对比

babel-config.ts
// 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-config.ts
// 等效的 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-project.ts
// 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-project.ts
// Next.js 项目 —— 自动使用 SWC
// 只需删除 .babelrc,SWC 自动启用
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
// SWC 默认启用,无需显式配置
compiler: {
styledComponents: true,
},
};

export default nextConfig;

方式二:在 Webpack 项目中引入 esbuild-loader

适用于无法迁移到 Vite 的存量项目:

webpack-migration.ts
// 第一步:安装
// 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 工具、简单项目等场景:

direct-usage.ts
// 使用 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!");
实践建议
  1. 新项目:直接选 Vite(esbuild)或 Next.js(SWC),不用关心底层细节
  2. 旧 Webpack 项目:用 esbuild-loader 替换 babel-loader,立竿见影
  3. 库开发:esbuild 或 tsup(基于 esbuild)打包
  4. Monorepo 大型项目:考虑 Rspack(基于 SWC),兼容 Webpack 配置

相关链接