SWC:Rust 编译器在构建链路中的转换边界
核验日期:2026-07-09。示例版本来自
examples/build-tools/package.json:@swc/core1.15.43、@swc/cli0.8.1。本文以@swc/core1.x 当前稳定线为基准,重点说明编译转换、宿主集成、Wasm plugin 兼容和类型检查边界。
需要掌握的问题
| 主题 | 读者必须回答的问题 | 原理与边界 | 高频错误 | 代码或观察证据 | 来源 |
|---|---|---|---|---|---|
| 定位与职责 | SWC 在工具链里负责哪一段? | SWC 是 Rust 实现的 JavaScript/TypeScript 编译器,核心链路是 parser → AST → transform → fixer/hygiene → codegen/minifier;它常被上层工具作为转换内核 | 把 SWC 当完整应用打包器、开发服务器或类型检查器 | 共享示例只生成 .js 与 .map | SWC Getting Started |
| parser 与 AST | jsc.parser 决定哪些语法能进 AST? | parser 根据 syntax、tsx/jsx、dynamicImport 等选项解析源码;不能解析就不会进入 transform | TypeScript/JSX 配置错线导致语法错误 | swc.config.json 的 TypeScript parser | Compilation |
| transform、fixer、hygiene | AST 改写后为什么还需要修复和作用域处理? | transform 改 AST;hygiene 处理标识符作用域;fixer 修正语法细节;最后 codegen 打印 JS | 自定义 Wasm plugin 生成非法 AST 或作用域冲突 | pipeline Mermaid 和插件边界 | Implementing a plugin |
| codegen 与 minifier | SWC 如何生成代码和压缩? | codegen 把 AST 输出 JS 与 Source Map;minifier 是独立配置,可处理压缩和 mangle | 以为 jsc.target 会自动 minify;压缩后函数名/错误栈变化 | .swcrc minify、source map 验证 | Minification |
jsc.target 与 env.targets | 语法 target 和环境组合有什么区别? | jsc.target 控制语法输出目标;env.targets 可接 Browserslist,并可配合 core-js 处理部分 polyfill 策略 | target 当 polyfill;Browserslist 路径不对导致本地/CI 输出不同 | 示例 jsc.target: "es2022",不启用 env | Supported Browsers |
| TS/JSX/React | SWC 处理 TypeScript、JSX 和 React 到什么程度? | parser/transform 可以处理 TS/JSX/React 语法和 runtime 选项;类型证明仍属于 TypeScript 工具链 | @swc/core 构建通过就删掉 tsc --noEmit | catalog.ts 类型删除但不做类型检查 | Compilation options |
| modules | module.type 改变什么? | SWC 可把模块语法保留为 ES6,或转换为 CommonJS、UMD、AMD;默认模块语句不一定按预期改写 | 把 ESM 转 CJS 后交给打包器,Tree Shaking 变差 | 共享示例 module.type: "es6" | Modules |
| 宿主工具分工 | SWC 如何进入 Next.js、Rspack、Jest、loader 等宿主? | 宿主决定文件图、缓存、watch、HMR、CSS、HTML 和类型检查;SWC 只负责被调用的转换段 | 在 SWC 配置里找不到 bundling/HMR 选项就误改上层工具 | 边界表和故障定位表 | @swc/core |
| native binary 与缓存 | Rust native binding 带来什么运维边界? | @swc/core 使用平台相关 native 包;缓存键应包含 SWC 版本、配置、Node、平台和宿主选项 | CI 平台包缺失、跨平台缓存复用、升级后旧缓存命中 | 版本和缓存键清单 | @swc/cli |
| Wasm plugin ABI/AST 兼容 | 1.15 对 Wasm plugin 做了什么重大变化? | 1.15 起 Wasm plugin 在 @swc/core 版本之间具备一定兼容性;通过自描述序列化和可扩展 enum 降低 AST 变化带来的 break | 以为所有旧插件永久兼容;插件没处理 Unknown AST variant | Wasm plugin compatibility 文档 | Compatibility for wasm plugin |
| Source Map 与严重问题 | 怎样证明转换可追溯,而不是只看 CLI 退出 0? | CLI 输出 JS 与 .map;map 要指回 .ts,不能泄露不必要路径 | map 链断、发布源码泄露、配置路径错 | artifact test 检查 sources 和 mappings | Configuring SWC |
内容边界说明:
| 分类 | 本文落点 |
|---|---|
| 编程语言与类型系统 | 覆盖 TS/JSX/React 语法解析、类型删除、模块输出和类型检查边界;不重复教学 TypeScript 类型系统 |
| Browser APIs 与持久化能力 | 只覆盖 ESM、dynamic import、Source Map 和运行时 API polyfill 边界;不讨论 IndexedDB、Storage 或权限 API |
| UI 框架与元框架 | 说明 SWC 作为 React/Next/Rspack 等宿主转换内核的接口;组件调度、渲染、水合不在本文重复 |
| 构建工具与运行时 | 覆盖 parser、transform、codegen、minifier、配置、缓存、宿主接口、产物和故障定位 |
| 后端、数据与 Agent 技术 | 只覆盖 native binary、Wasm plugin、构建配置以开发者权限执行和 CI 平台边界;请求生命周期、数据库一致性和 Agent 评测不适用 |
SWC 的主问题:快的编译段,不是完整应用工具链
SWC 适合替换“把 JS/TS/JSX 变成目标 JS”的那一段。它经常被 Next.js、Rspack、Jest、loader 或自研脚本调用,但宿主仍然负责文件图、CSS、HTML、HMR、缓存策略和类型检查。
和相邻工具的边界:
| 工具 | 负责 | 不负责 |
|---|---|---|
| SWC CLI/core | 单文件或目录转换、语法目标、模块格式、Source Map、minify | 浏览器 bundle、CSS/HTML、异步 chunk、HMR |
| Rspack/Next/Jest | 调用 SWC 并管理文件图、缓存、运行时 | SWC 内部 AST 兼容策略 |
| esbuild | 转换和打包都覆盖一部分 | SWC Wasm plugin、Babel 兼容插件生态 |
TypeScript tsc | 类型检查、声明文件 | 高速 JS 转换 |
严重问题完整流程:
| 症状 | 根因 | 观察命令 | 修正 |
|---|---|---|---|
dist/swc 没有 index.html 或 CSS | SWC 示例只做转换 | find examples/build-tools/dist/swc -maxdepth 1 -type f | 后接 bundler 或发布原生 ESM |
| 构建通过但类型错 | SWC 删除类型,不做类型证明 | 单独运行 tsc --noEmit | 类型检查保留在 CI |
| JSX/TSX 解析失败 | jsc.parser 没启用对应语法 | 缩小到单个 .tsx fixture | 配置 syntax: "typescript" 和 tsx: true 等选项 |
parser 与 AST:配置错了,后面都没有机会修
共享示例的配置很小:
{
"$schema": "https://swc.rs/schema.json",
"jsc": {
"parser": {
"syntax": "typescript",
"dynamicImport": true
},
"target": "es2022"
},
"module": {
"type": "es6"
},
"sourceMaps": true
}jsc.parser 是入口门禁:
| 选项 | 作用 | 错误表现 |
|---|---|---|
syntax: "typescript" | 允许 TS 类型语法 | Unexpected token 或类型标注无法解析 |
tsx: true | 允许 TSX | .tsx 中 JSX 报错 |
jsx: true | ECMAScript parser 下允许 JSX | JSX 被当成小于号表达式 |
dynamicImport: true | 允许 import() | 动态导入语法错误 |
decorators | 允许装饰器语法 | decorator 报错或语义不符 |
错误完整流程:
| 症状 | 根因 | 观察命令 | 修正 |
|---|---|---|---|
.ts 里类型标注报错 | parser 仍按 ECMAScript 解析 | swc src/file.ts --config-file configs/swc.config.json | 设置 jsc.parser.syntax: "typescript" |
| dynamic import 报错 | parser 没启用 dynamic import | 最小 import('./x') fixture | 开 dynamicImport: true |
| 本地可用 CI 不可用 | CI 读取了另一份 .swcrc 或 cwd 不同 | 打印 CLI --config-file 路径 | 脚本显式传 --config-file |
transform、hygiene、fixer、codegen:AST 改写后要回到合法 JS
SWC 的 transform 不是字符串替换。Rust pass 读取 AST、改写节点,再由 hygiene/fixer/codegen 保证输出能运行。
一个 Wasm plugin 的心智模型:
use swc_core::ecma::{
ast::Program,
visit::{visit_mut_pass, VisitMut, VisitMutWith},
};
struct Rename;
impl VisitMut for Rename {
fn visit_mut_program(&mut self, program: &mut Program) {
program.visit_mut_children_with(self);
}
}
pub fn plugin() -> impl Pass {
visit_mut_pass(Rename)
}边界表:
| 阶段 | 输入 | 输出 | 常见问题 |
|---|---|---|---|
| parser | 源码 + parser options | AST | 语法选项错 |
| transform | AST + transform options/plugin | 新 AST | 插件生成非法 AST |
| hygiene | AST | 作用域安全的 AST | 标识符冲突、shadowing |
| fixer | AST | 可打印 AST | 自动分号、括号、语法细节 |
| codegen | AST + map 选项 | JS + Source Map | map 丢失、输出路径泄露 |
严重问题完整流程:
| 症状 | 根因 | 观察命令 | 修正 |
|---|---|---|---|
| 插件后输出语法非法 | transform 产生不符合 AST 约束的节点 | 最小输入跑 plugin | 让 pass 只改必要节点,保留 hygiene/fixer |
| 变量名冲突 | 插件手写 ident 未考虑作用域 | 构造 shadowing fixture | 使用 SWC 的作用域/mark 机制 |
| Source Map 指向生成代码 | plugin 或 codegen 未保留位置信息 | 打开 .map.sources 和 mappings | 保留 span,开启 sourceMaps |
jsc.target、env.targets 和 polyfill 边界
jsc.target 是语法输出目标。共享示例写 es2022,意思是保留现代语法到 ES2022 左右,不做旧浏览器降级:
{
"jsc": {
"target": "es2022"
}
}env.targets 是环境组合,可接 Browserslist,也可配合 mode/coreJs 处理部分 polyfill 策略:
{
"env": {
"targets": "> 0.25%, not dead",
"mode": "entry",
"coreJs": "3.22"
}
}不要混用这三件事:
| 需求 | 配置入口 | 边界 |
|---|---|---|
| 降级 class/private field/可选链等语法 | jsc.target 或 env.targets | 不自动证明类型正确 |
| 按浏览器验证范围选择 transforms | env.targets/Browserslist | Browserslist 路径会影响 CI/local 一致性 |
| 注入 runtime polyfill | env.mode + coreJs | usage 模式有表达式分析边界;仍需产物验证 |
| 保留现代 bundle 给上层打包器 | jsc.target: "es2022"、module.type: "es6" | 老浏览器不兼容 |
严重问题完整流程:
| 症状 | 根因 | 观察命令 | 修正 |
|---|---|---|---|
| 老 WebView 语法报错 | jsc.target 太现代 | 检查产物是否保留 class/optional chaining | 明确目标环境或交给上层工具 |
缺 String.prototype.at polyfill | target 不等于 polyfill;env.usage 有分析限制 | 浏览器报错或搜索 core-js | 入口显式 polyfill,或配置 env.mode: "entry" |
| 本地和 CI 输出不同 | Browserslist 文件查找路径不同 | 打印 cwd 和 config path | 显式 env.path 或统一 cwd |
modules、TS/JSX/React:保留 ESM 通常更适合打包器
SWC 可以把模块语法输出为 commonjs、es6、amd、umd。共享示例选择:
{
"module": {
"type": "es6"
}
}原因很简单:这份输出还可能交给 Rollup/Vite/Rspack 等打包器,保留 ESM 更利于后续依赖图和 Tree Shaking。把 ESM 提前转成 CommonJS 会让下游少很多静态信息。
TypeScript 和 React 边界:
| 能力 | SWC 能做 | 仍需要 |
|---|---|---|
| 删除 TS 类型 | 能 | tsc --noEmit 做类型检查 |
| 解析 TSX/JSX | 能,按 parser/transform 配置 | 框架运行时和 JSX runtime 策略 |
| React transform | 能配置 classic/automatic 等 | 框架级 Fast Refresh/HMR |
| 生成声明文件 | 不负责 | tsc 或 API extractor |
| 路由/组件水合 | 不负责 | 框架工具链 |
错误完整流程:
| 症状 | 根因 | 观察命令 | 修正 |
|---|---|---|---|
| 下游 Tree Shaking 变差 | SWC 先把 ESM 转成 CJS | `rg "require\( | exports\." dist/swc` |
| 类型声明缺失 | SWC 只输出 JS/map | 查 dist 是否有 .d.ts | 单独运行 tsc --emitDeclarationOnly |
| React refresh 不工作 | SWC 只做 transform,HMR 由宿主负责 | 看宿主插件配置 | 在 Vite/Next/Rspack 层配置 Fast Refresh |
宿主工具、native binary 与缓存
SWC 常以三种方式进入项目:
| 入口 | 负责范围 | 常见缓存来源 |
|---|---|---|
@swc/cli | 批量转换目录或文件 | CLI 输出目录、CI 缓存 |
@swc/core | Node API,被工具调用 | 宿主工具缓存 |
swc-loader/Rspack/Next/Jest | 作为宿主的一段 transform | webpack/Rspack/Next/Jest cache |
缓存键至少包含:
const swcCacheKey = [
process.version,
process.platform,
process.arch,
'@swc/core@1.15.43',
JSON.stringify(swcConfig),
process.env.NODE_ENV ?? 'development',
].join('|');严重问题完整流程:
| 症状 | 根因 | 观察命令 | 修正 |
|---|---|---|---|
| CI 安装后 native binding 报错 | 平台包缺失或缓存跨平台复用 | 查看 process.platform、lockfile、install 日志 | 不跨平台复用 node_modules,保留 lockfile |
改 .swcrc 输出没变 | 宿主缓存 key 漏配置 | 清缓存前后比较输出 | 把 .swcrc、SWC 版本、env 加入 cache key |
| 升级 SWC 后插件崩溃 | Wasm plugin 和 @swc/core AST/ABI 不兼容 | 查看 plugin 依赖的 swc_core | 升级插件或按 1.15 兼容规则处理 |
Wasm plugin ABI/AST 兼容:1.15 降低成本,不取消回归
SWC Wasm plugin 过去常被 @swc/core 版本变化打断,因为 host 和 plugin 都要序列化/反序列化 AST。官方兼容文档说明,从 @swc/core v1.15.0 开始,Wasm plugin 在版本之间具备一定兼容性,核心变化包括使用自描述序列化方案和给 AST enum 增加可扩展处理。
但这不是“所有插件永远兼容”。官方也说明字段删除、字段类型变化等情况仍可能造成不兼容。插件作者需要依赖足够新的 swc_core,并处理 Unknown variant。
[target.'cfg(target_arch = "wasm32")']
rustflags = [
"--cfg=swc_ast_unknown"
]迁移判断:
| 场景 | 动作 |
|---|---|
| 只用官方 transform,不用 Wasm plugin | 正常升级 @swc/core,跑转换 fixture |
| 使用第三方 Wasm plugin | 检查插件是否支持 SWC 1.15 兼容策略 |
| 自研 Wasm plugin | 升级 swc_core >= 47,开启 swc_ast_unknown,补 Unknown match |
| 插件处理新 ECMAScript 语法 | 增加新语法 fixture,不能只看旧代码 |
严重问题完整流程:
| 症状 | 根因 | 观察命令 | 修正 |
|---|---|---|---|
升级 @swc/core 后 Wasm plugin 直接失败 | 旧插件依赖旧 AST 序列化 | 看错误和插件 swc_core 版本 | 升级插件,按兼容文档启用 Unknown |
| 新语法输入导致 plugin panic | plugin 不认识新 AST node | 最小新语法 fixture | 补 Unknown 分支和兼容测试 |
| 性能下降 | 新序列化方案有兼容性成本 | benchmark 插件前后耗时 | 只在需要插件时启用,避免全量插件链 |
Source Map:转换可追溯,也可能泄露源码路径
共享示例开启 sourceMaps: true,CLI 也传了 --source-maps:
swc src --out-dir dist/swc \
--config-file configs/swc.config.json \
--source-maps \
--extensions .ts \
--strip-leading-paths \
--delete-dir-on-startartifact test 要求:
| 观察点 | 预期结果 |
|---|---|
| 文件列表 | catalog.js、detail.js、main.js、side-effect.js 和对应 .map |
catalog.js | 不包含 export type 或 : Tool,保留 ESM export |
.map.version | 等于 3 |
.map.sources | 能追溯到对应 .ts |
.map.mappings | 非空 |
严重问题完整流程:
| 症状 | 根因 | 观察命令 | 修正 |
|---|---|---|---|
| DevTools 指向生成代码 | source map 未生成或 URL 丢失 | tail -n 2 dist/swc/main.js | 开启 sourceMaps 和 CLI --source-maps |
| map 泄露绝对路径 | source path 未处理 | `rg "$PWD | file:" dist/swc` |
map 指向 .js 而非 .ts | 上游/插件 span 丢失 | 解析 .map.sources | 保留 span,减少破坏性插件 |
共享示例:SWC 只转换 TypeScript 模块
运行命令:
pnpm --filter @roadmap/build-tools-slice build:swc输入:
examples/build-tools/src/main.ts
examples/build-tools/src/catalog.ts
examples/build-tools/src/detail.ts
examples/build-tools/src/side-effect.ts预期结果:
examples/build-tools/dist/swc/catalog.js
examples/build-tools/dist/swc/catalog.js.map
examples/build-tools/dist/swc/detail.js
examples/build-tools/dist/swc/detail.js.map
examples/build-tools/dist/swc/main.js
examples/build-tools/dist/swc/main.js.map
examples/build-tools/dist/swc/side-effect.js
examples/build-tools/dist/swc/side-effect.js.map不会出现:
index.html
assets/*.css
assets/*.js
manifest.json验证观察:
find examples/build-tools/dist/swc -maxdepth 1 -type f | sort
node --test --test-name-pattern swc examples/build-tools/test/artifacts.test.mjs预期输出要点:
| 观察点 | 预期结果 |
|---|---|
| TypeScript | 类型声明被删除 |
| ESM | 仍有 export |
| Source Map | 每个 .js 都有对应 .js.map |
| CSS/HTML | 不生成,因为 SWC 示例只做转换 |
迁移、重大变化和严重问题总表
SWC 1.x 的工程风险主要来自宿主集成、配置错线、native binary、Wasm plugin 和 source map,而不是“能不能解析一段 TypeScript”。
| 重大变化或风险 | 表现 | 根因 | 最小观察 | 修正 |
|---|---|---|---|---|
| Wasm plugin 1.15 兼容变化 | 升级后插件行为变化 | AST 序列化/Unknown variant 处理 | 插件 fixture | 升级插件并按兼容文档改造 |
jsc.target/env.targets 错线 | 产物语法或 polyfill 不符合预期 | target 与环境组合混淆 | 对比输出和浏览器 fixture | 明确使用 jsc.target 或 env |
| 类型检查缺失 | 构建通过但类型错误 | SWC 只转换 | tsc --noEmit | CI 保留类型检查 |
| ESM 被转 CJS | 下游 Tree Shaking 变差 | module.type 设置错误 | 搜索 require(/exports. | 给 bundler 输入保留 ES6 |
| native binding 缺失 | CI 或新平台构建失败 | 平台包/缓存问题 | install 日志和 process.platform | 不复用跨平台 node_modules |
| Source Map 泄露 | 公开源码或绝对路径 | map 发布策略粗糙 | `rg "$PWD | file:" dist/swc` |