879 字
4 分钟
Babel 与 SWC

它们做的是同一件事#

把 JSX 与 TSX 转成浏览器能跑的 JavaScript。浏览器不认识尖括号,所以这一步绕不过去(Writing Markup with JSX)。

严格说,两者叫转译器(transpiler)而不是编译器:输入是高级源码,输出是同一层级的另一份高级源码,只做语法降级与语法翻译,不生成机器码。各自的官方文档里有完整的转换、插件与配置清单。

两种 JSX 转换#

官方在 2020 年改写过一次 JSX 转换,于是有了新旧两套(Introducing the New JSX Transform,该页所在站点已归档)。

旧转换把 JSX 变成 React.createElement(...) 调用。因为是 React. 开头,用 JSX 的文件必须有 React 在作用域里,这就是老代码里那句 import React from 'react' 的来历。

新转换不再调用 React.createElement,而是让编译器自动插入一行 import { jsx as _jsx } from 'react/jsx-runtime',再调用 _jsx('h1', { children: '你好' })。好处是写 JSX 不必再 import React。注意用 Hook 时仍然要从 react 里 import 具体函数,省掉的只是那个默认导入。

开关是配置里的 runtime:

  • classic 走旧转换。
  • automatic 走新转换。Babel 8 起成为默认值,TypeScript 从 4.1 起支持。

新转换带来两个连锁反应:import React from 'react' 变成多余,可以用官方 codemod 批量清掉;ESLint 的 react/react-in-jsx-scope 与 react/jsx-uses-react 两条规则也随之可以关闭。

到 React 19,新转换成为必需项,旧转换不再支持(React 19 升级指南)。

同一段 JSX 的两种产物#

源码:

function App() {
return <h1>你好</h1>
}

classic 转换后:

import React from 'react'
function App() {
return React.createElement('h1', null, '你好')
}

automatic 转换后:

// 这一行由编译器插入,不要自己写
import { jsx as _jsx } from 'react/jsx-runtime'
function App() {
return _jsx('h1', { children: '你好' })
}

react/jsx-runtime 这两个入口只给编译器用。需要手动创建元素时,还是用 React.createElement,它没有下线。

Babel 与 SWC 的差别#

Babel 用 JavaScript 写,插件生态最大,新语法通常先在它这里落地。它管的事不止 JSX:语法转换、polyfill、源码转换、自定义插件都在范围内。

SWC 用 Rust 写,编译成机器码后执行,没有 JavaScript 解释执行与 JIT 的运行时开销,所以同样的转译任务通常更快。快多少取决于代码与配置,官方有自己的基准页,需要数字时看那里,别背二手倍数。

今天的脚手架通常把这一步藏起来。你会直接碰它,一般只有两种情况:要改 runtime,或者要加一个语法插件。

自己跑一次 SWC#

transform.mjs
import swc from '@swc/core'
const { code } = swc.transformFileSync('./test.jsx', {
jsc: {
target: 'es5',
parser: { syntax: 'ecmascript', jsx: true },
transform: { react: { runtime: 'automatic' } },
},
})
console.log(code)

输入 test.jsx:

import { createRoot } from 'react-dom/client'
const App = () => <div>Hello SWC</div>
createRoot(document.getElementById('root')).render(<App />)

输出片断:

import { jsx as _jsx } from 'react/jsx-runtime'
import { createRoot } from 'react-dom/client'
var App = function () {
return /*#__PURE__*/ _jsx('div', { children: 'Hello SWC' })
}
createRoot(document.getElementById('root')).render(/*#__PURE__*/ _jsx(App, {}))

把 runtime 改成 classic,输出里的 _jsx 就换成 React.createElement,并且要求 React 在作用域里。这一行配置的差别,就是老教程里那句 import React 的存废。

转译器都在做这三步#

解析:把源码字符串读成抽象语法树(AST)。JSX 标签在树里是专门的一类节点,不是字符串。

转换:遍历 AST,遇到 JSX 节点就替换成函数调用节点。runtime 决定替换成 React.createElement 还是 _jsx。

生成:把改完的 AST 写回 JavaScript 代码字符串。

第 4 篇,共 39 篇本文属于系列

Babel 与 SWC
https://www.choria.top/posts/react-babel/
作者
乔瑞雅·克林威尔
发布于
2026-04-30
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 192 天

部分内容可能已过时