跳转到内容

SWC — Rust 写的 TS/JS 编译器

待复核

SWC(Speedy Web Compiler)是用 Rust 重写 Babel 核心能力的编译器——做 TypeScript / JSX 转译、minify 压缩、bundle 打包。日常类比:

同样一道菜,Babel 用电饭锅 30 分钟煮出来,SWC 用高压锅 1 分钟搞定。

锅形状(API)几乎不变,菜谱(要做的转换)也一样。换个材料的锅(JS → Rust),加压(编译期 trait dispatch + 真并行),同一份输入 10-20 倍出锅。

不理解 SWC 解释不了下面这些:

  • 为什么 Next.js 12+ 启动快了——2021 年起默认编译器从 Babel 切到 SWC(13 继续沿用)
  • 为什么 Vercel 的下一代 bundler Turbopack 跑得动——它底层把 SWC 当库链接进去
  • 为什么 ByteDance 出的 webpack 替代品 Rspack 也能拉到几十倍速度——它把 SWC 选作 transformer
  • 为什么 Node 能直接调 Rust 编译器不卡——napi-rs 是给 Node 用的原生扩展桥,把 Rust 产物包成 binary 供 require

SWC 的设计可以拆成 三件事

  1. 换语言(Rust > Go > JS):Babel 用 JS 写——单线程 + V8 GC 卡顿。esbuild 用 Go 写,能开多核。SWC 用 Rust:编译期 trait dispatch 像「出门前就定好走哪扇门」,Go interface 更像「到门口再翻目录找门牌」。早期宣传常说单核可比 esbuild 快一截,但 2020 前后口径、场景相关,不宜当永恒排名。

  2. Visitor 模式的 Rust 化:Babel traverse 每碰一个 AST 节点都要查 visitor[node.type](像翻通讯录找处理人)。SWC 用 Rust trait + #[ast_node] 宏自动派生 VisitMut(「可变地走访语法树」)——访问变成「直接打电话 + 按偏移读字段」。节点一多,差距就放大。

  3. API 对齐 Babel@swc/coretransform(code, opts) 几乎是 Babel transformSync 的镜像。这降低了下游迁移成本——webpack / Next.js 把 Babel 替换成 SWC 时,调用代码改动极小。这是 SWC 在 2020-2022 年快速被采纳的关键

最常见的用法——把整个 src 目录的 TS/JSX 编译到 dist:

Terminal window
swc src -d dist

逐部分解释:

  • src 是源码目录
  • -d dist 是输出目录
  • 没指定 config 时 SWC 会自动找 .swcrc

跑完 dist 里就是纯 ES5/ES2015 的 JS 文件 + sourcemap。中型项目(500 文件)大约 1-2 秒。

{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true,
"decorators": true
},
"target": "es2020",
"transform": {
"react": {
"runtime": "automatic"
}
}
},
"module": {
"type": "es6"
}
}

逐字段解释:

  • parser.syntax"typescript" 让 SWC 认 TS 类型语法(Babel 要装 @babel/preset-typescript,SWC 内置)
  • tsx: true 同时打开 TSX 支持
  • target: 'es2020' 控制语法降级目标——async/await、可选链都保留
  • transform.react.runtime: 'automatic' 用 React 17+ 的新 JSX 运行时,省掉 import React

案例 3:最小 SWC plugin(Rust → Wasm)

Section titled “案例 3:最小 SWC plugin(Rust → Wasm)”

SWC plugin 是 Wasm 模块。下面用伪代码说明「碰到 console.log 就清空参数」:

impl VisitMut for ConsoleRemover {
fn visit_mut_call_expr(&mut self, n: &mut CallExpr) {
// 1) 先递归处理子节点
n.visit_mut_children_with(self);
// 2) 若 callee 是 console.log(成员表达式 obj=console, prop=log)
// 3) 则 n.args.clear() —— 调用还在,参数被掏空
}
}
#[plugin_transform]
pub fn process(mut program: Program, _: ()) -> Program {
program.visit_mut_with(&mut ConsoleRemover);
program
}

#[plugin_transform] 生成 Wasm 入口与 host 约定。cargo build --target wasm32-wasi --release 得到 .wasm,在 .swcrcjsc.experimental.plugins: [['./my-plugin.wasm', {}]] 即生效。

  1. 不做类型检查:和 esbuild 一样,SWC 编译 TS 时只 剥掉类型注解——const x: number = "hello" 它照样编译过去。CI 必须配 tsc --noEmit 双跑,IDE 也要开 TS 检查。否则你以为类型对的代码运行时会爆炸

  2. Decorator 实现细节不一致:SWC 同时支持 legacy decorator(TC39 stage 1,老版本)和 2022-03 decorator(TC39 新版本),两者语义有差别。Babel / tsc 的实现也不完全相同——同一份 NestJS 代码用 Babel 跑通,切到 SWC 可能某个装饰器顺序不对。配置选错会导致运行时差异

  3. Rust plugin ABI 不稳定:每次升级 SWC 主版本,AST 节点结构可能微调,旧 plugin 需要重新编译对齐新版本。开发者发了一个 plugin,几个月后 SWC 升级 plugin 就跑不起来。这是 SWC 插件生态发展慢的根本阻力(10000+ Babel plugin vs 100+ SWC plugin)

  4. minify 输出比 terser 略大:SWC 自带的 minifier 与 terser API 兼容,但在边角 case(特定的 dead code 消除、变量名 mangle 策略)上输出会大 1-3%。对极致追求 bundle size 的库作者来说仍倾向用 terser

适用

  • Next.js / Parcel 2 / Storybook 这类「需要把 transformer 当库链接」的工具——SWC 是 Rust crate,能内嵌进任何 Rust 项目
  • 中大型 TS 项目的 transform 提速(500+ 文件)——速度收益显著
  • 需要保留 Babel 风格 API 的项目迁移——transform(code, opts) 一对一替换

不适用

  • 重度依赖冷门 Babel plugin 的项目——要么 Wasm 重写要么放弃 SWC
  • 需要类型检查的 TS 项目——必须额外配 tsc,SWC 永远不做这件事
  • 库发包到 npm 追求最优 tree-shake 与 bundle size——用 rollup 仍是首选
  • 2017 年:DongYoon Kang(GitHub @kdy1,韩国开发者)作为个人项目开始写 SWC,命题是「用 Rust 重写 Babel」
  • 2020 年:v1.0 发布,README 直接对标 Babel 性能
  • 2021 年:Vercel 决定把 Next.js 12 的默认编译器从 Babel 切到 SWC,并直接资助 kdy1 全职维护——SWC 从「个人项目」升级为「Vercel 战略基础设施」
  • 至今:直接 + 间接调用每周 50M+,被 Next.js / Parcel 2 / Storybook 7+ / Deno 部分 / Vite 部分采纳;项目仍主要由 kdy1 一人主导

「Rust 重写 JS 工具链」浪潮里,SWC 是最完整的一份成果——既覆盖 transform/minify/bundle 三件套,又被 Next.js 全面采纳,事实上是「下一代 Babel」。

  1. 「换语言」+「数据结构内嵌」是性能终极武器——Rust 的 trait dispatch + struct 偏移读,比 JS 的 hashmap lookup 快一个数量级。算法层能压的极限有限,runtime + 数据结构这两层才是天花板
  2. API 兼容是采纳的关键——SWC API 对齐 Babel 是有意识的,没这一步 Vercel 也未必选它做 Next.js 默认编译器
  3. Wasm plugin 是赌注——好处(跨语言 + 隔离 + 性能)短期看不出,坏处(生态启动慢 + ABI 不稳)立刻有。这是个长期才能赢的设计选择
  4. Rust 不是 Go 的替代品,是更下层的选项——Rust 写出来的库能被任何 Rust 项目当 crate 链接(Turbopack / Rspack 都这么用),Go binary 只能外部调用。这是 SWC 战略价值的核心
  5. 不做类型检查是性能选择——tsc 类型检查占 80% 时间,删掉这部分才能跑到几毫秒。esbuild / SWC / Babel + preset-typescript 都做这个取舍
  • esbuild —— Go vs Rust 的同代之争;esbuild 速度接近但不能内嵌进 Rust 项目
  • turbopack —— Vercel 的 Rust bundler,SWC 是它的 transformer 引擎
  • rspack —— ByteDance 的 Rust webpack 替代,也用 SWC 做 transform
  • rollup —— 库发包场景的对照组,tree-shake 仍比 SWC 强
  • ast-grep —— ast-grep — 按语法树搜代码、改代码的命令行工具
  • biome —— Biome — JS/TS 工具链一体化(Rust 写的 linter+formatter)
  • bun —— Bun — JS 全能运行时
  • dust —— dust — du 的可视化替代,按目录大小排树状条形图
  • engine262 —— engine262 — 用 JavaScript 实现的 ECMA-262 参考引擎
  • jest —— Jest — 一个包就能跑 JS 测试的全家桶
  • lightningcss —— lightningcss — 用 Rust 把 CSS 工具链一遍跑完的编译器
  • lingui —— Lingui — 写自然字符串,编译期自动提取 i18n msgid
  • oxc —— oxc — Rust 写一整套 JS/TS 工具链的勇气
  • ripgrep —— ripgrep — Rust 写的现代 grep
  • rolldown —— rolldown — 用 Rust 给 Vite 当统一引擎的打包器
  • rspack —— rspack — 用 Rust 重写 webpack 的内核,但留下整个 plugin 生态
  • turbopack —— Turbopack — 把 bundler 重做成增量计算应用
  • webpack —— webpack 模块打包