外观
Babel 系统学习文档
适合人群:已经掌握 JavaScript 基础,想理解现代语法转换、浏览器兼容、构建链路、AST 与前端工程化编译体系的学习者。
学习目标:从会使用 Babel 编译代码,逐步掌握配置文件、preset / plugin、
@babel/preset-env、polyfill、React / TypeScript 集成、Babel 8 迁移和自定义插件开发。核对时间:2026-06-30。Babel 8 已于 2026-06-16 发布,本文以 Babel 8 为主,同时说明 Babel 7 项目迁移时需要注意的差异。
相关文档:建议配合 前端架构师技术栈全景学习文档.md、ESLint系统学习文档.md、前端工程化与构建发布系统学习文档.md 一起阅读,理解 Babel 在前端编译、兼容和构建链路中的位置。
目录
- Babel 是什么
- 为什么还需要 Babel
- Babel 的工作流程
- 版本与生态现状
- 快速开始
- 配置文件体系
- plugins 与 presets
@babel/preset-env详解- targets 与 Browserslist
- polyfill 与语法转换
- React、JSX 与 Babel
- TypeScript 与 Babel
- Babel 与构建工具
- Babel 8 迁移重点
- 进阶:AST 基础
- 高级:自定义 Babel 插件
- 性能优化与排错
- 学习路线与练习
- 官方参考资料
1. Babel 是什么
Babel 是 JavaScript 编译器。它最常见的用途是把较新的 JavaScript、JSX、TypeScript 或实验性语法转换成目标环境可以运行的 JavaScript。
一句话理解:
Babel 负责“把一种 JavaScript 代码形态转换成另一种 JavaScript 代码形态”。
它可以做:
- 把现代 JavaScript 语法转换成旧环境支持的语法。
- 把 JSX 转换成函数调用。
- 去掉 TypeScript 类型语法,输出普通 JavaScript。
- 按目标浏览器决定需要转换哪些语法。
- 通过插件完成代码分析、代码注入、代码重写。
- 为框架、构建工具、测试工具提供底层编译能力。
它不能直接做:
- 类型检查。
- 模块打包。
- 代码压缩。
- 测试断言。
- 运行时代码质量监控。
这些通常由 TypeScript、Rollup、Vite、Webpack、esbuild、SWC、Terser、Vitest 等工具负责。
2. 为什么还需要 Babel
2.1 兼容不同运行环境
JavaScript 标准持续演进,但用户浏览器、Node.js 版本、WebView、企业内嵌浏览器不一定同步升级。
Babel 的核心价值之一是:让开发者可以使用现代语法,同时把输出控制在目标环境能执行的范围内。
示例:
js
const name = user?.profile?.name ?? "anonymous";在不支持 optional chaining 或 nullish coalescing 的环境中,需要转换为更基础的写法。
2.2 支撑框架语法
React JSX 本身不是浏览器原生 JavaScript:
jsx
function App() {
return <h1>Hello</h1>;
}Babel 会把它转换成运行时函数调用。现代 React 默认使用 automatic runtime,不需要手写 import React from "react" 才能使用 JSX。
2.3 支撑工具链生态
很多工具底层会用 Babel:
- Jest / Vitest 处理测试文件。
- Webpack 的
babel-loader处理 JS / JSX。 - Rollup 插件处理库构建。
- Storybook 处理组件代码。
- Codemod 工具批量重构代码。
- 框架脚手架处理项目默认编译。
即使你平时不直接写 Babel 配置,理解 Babel 仍然有助于看懂构建链路。
2.4 和 esbuild / SWC 的关系
现代构建工具常使用 esbuild 或 SWC 提升速度,但 Babel 仍有价值:
- Babel 插件生态非常成熟。
- 很多复杂框架转换仍依赖 Babel 插件。
- 自定义 AST 变换时 Babel 更容易上手。
- 一些代码迁移和 codemod 场景更适合 Babel。
架构师需要理解:Babel 不一定是所有项目的最快选择,但它仍是 JavaScript 编译生态的重要基础。
3. Babel 的工作流程
Babel 主要经过三个阶段:
- Parse:把源码解析成 AST。
- Transform:遍历 AST,并通过插件修改 AST。
- Generate:把修改后的 AST 重新生成代码和 sourcemap。
3.1 Parse
输入:
js
const total = price * count;Babel parser 把代码解析为抽象语法树。语法树中包含变量声明、标识符、二元表达式、字面量等节点。
解析实际分为两个阶段,理解它们有助于看懂解析报错:
text
源码 -> 词法分析(Lexing):切成 token 流
-> 语法分析(Parsing):token 流组装成 AST| 阶段 | 输入 | 输出 | 典型报错 |
|---|---|---|---|
| 词法分析 | 字符流 | Token 流(关键字、标识符、运算符、字面量) | 非法字符 |
| 语法分析 | Token 流 | AST(Program 为根) | Unexpected token、结构不完整 |
直接调用 @babel/parser 验证:
js
import { parse } from "@babel/parser";
const ast = parse("const total = price * count;", {
sourceType: "module",
plugins: ["jsx", "typescript"], // 按需启用扩展语法
});
console.log(ast.type); // "Program"
console.log(ast.body.length); // 顶层语句数量要点:
sourceType: "module"允许import/export;"script"不允许。- 实验性或扩展语法(JSX、TypeScript、decorators)必须通过
plugins显式启用,否则解析报错。 - Babel AST 在 ESTree 规范基础上做了扩展:节点带
start、end、loc位置信息,字面量细分为StringLiteral、NumericLiteral等,这对 sourcemap 和错误定位至关重要。
3.2 Transform
插件会访问 AST 节点:
js
export default function myPlugin() {
return {
visitor: {
Identifier(path) {
if (path.node.name === "oldName") {
path.node.name = "newName";
}
},
},
};
}Transform 阶段的内部机制:
- 深度优先遍历:
@babel/traverse从 Program 出发深度优先访问节点。每个节点类型对应一组访问器,可以分别定义enter和exit:
js
visitor: {
FunctionDeclaration: {
enter(path) { /* 进入节点时,父先于子 */ },
exit(path) { /* 离开节点时,子先于父 */ },
},
}- path 是上下文包装:
path.node是当前节点,path.parent是父节点,还提供replaceWith、remove、insertBefore、skip、stop等操作方法。修改要尽量通过 path 的方法进行,这样遍历状态才一致。 - 动态修改:在遍历中新增的节点也会被访问(除非
path.skip()),所以插件要防止无限递归,例如重命名后不再处理已处理的节点。
辅助函数(helpers):
很多转换需要生成一段“运行时支撑代码”,例如 _typeof、_classCallCheck、_extends。Babel 不会在每个文件里复制这段代码,而是把它抽成 helper:
json
{
"plugins": [["@babel/plugin-transform-runtime", { "helpers": true }]]
}- 不使用
@babel/plugin-transform-runtime时,helper 会内联到每个需要的文件里,文件多时明显重复。 - 使用
transform-runtime后,helper 变成import自@babel/runtime,多个文件共享一份,减小产物体积。 - 库项目几乎都应该使用
transform-runtime,并避免污染全局。
3.3 Generate
Babel generator 把 AST 再转回代码:
js
const total = price * count;如果启用 sourcemap,调试器可以把生成代码映射回源码。
3.4 Babel 的本质
Babel 的本质不是“兼容旧浏览器”这么窄,而是一个可插拔的 JavaScript AST 转换平台。
浏览器兼容只是 Babel 最常见的使用场景之一。
4. 版本与生态现状
4.1 Babel 8 重点变化
截至 2026-06-30,Babel 8 的关键变化包括:
- Babel 8 于 2026-06-16 发布。
- Babel 包以原生 ESM 发布。
- Babel 8 包要求 Node.js
^22.18.0 || >=24.11.0。 - Babel 自身不再支持较旧 Node.js 运行环境,但仍可以输出给旧环境运行的代码。
@babel/preset-env不再默认编译到 ES5。- 默认目标改为 Browserslist 的
defaults。 loose与spec从@babel/preset-env中移除,推荐使用顶层assumptions。@babel/preset-react默认使用 automatic runtime。@babel/plugin-transform-runtime的corejs与@babel/preset-env的useBuiltIns迁移到babel-plugin-polyfill-corejs3等 polyfill 插件方案。
官方“升级前准备”清单(Getting ready)值得逐条对照:
- 没有
.browserslistrc时,显式配置顶层targets(Babel 7 默认>= 0%,Babel 8 默认defaults)。 @babel/preset-env先在 Babel 7 里开启bugfixes: true。loose/spec迁移到顶层assumptions。- React 项目显式设置
runtime(Babel 7 默认classic,Babel 8 默认automatic);继续用 classic 时设置useSpread: true。 - TypeScript / Flow preset 把
isTSX、allExtensions换成ignoreExtensions。 - 处理 TS/Flow 时把
allowDeclareFields设为true(Babel 7 迁移步骤;Babel 8 中该选项已移除并默认启用此行为,届时直接删掉该选项)。 transform-runtime的corejs或useBuiltIns迁移到babel-plugin-polyfill-corejs3。@babel/plugin-proposal-decorators显式设置version: "legacy"或"2023-11"。- 移除已废弃的
@babel/plugin-proposal-record-and-tuple和@babel/plugin-syntax-import-assertions。
另外两类容易忽略的改动:
| 类别 | 内容 | 应对 |
|---|---|---|
| 插件改名 | 已达 Stage 4 的 @babel/plugin-proposal-* 统一改名为 @babel/plugin-transform-*(如 plugin-proposal-class-properties → plugin-transform-class-properties),改名在 Babel 7.22 就已落地 | 升级前先改名,include/exclude 里引用的名字也要同步改 |
| 语法插件内置 | @babel/plugin-syntax-async-functions、plugin-syntax-dynamic-import、plugin-syntax-top-level-await 等 20 多个 syntax 插件不再需要 | 直接从配置和依赖中删除 |
💡 这些改动大多可以在 Babel 7 阶段提前完成(改名、bugfixes、runtime 显式声明)。先把 Babel 7 配置调整到接近 Babel 8 默认,再升主版本,升级 PR 的风险最小。
4.2 Babel 7 项目的现实情况
很多存量项目仍会使用 Babel 7,因为:
- Node.js 版本较旧。
- 依赖插件尚未完全适配 Babel 8。
- 构建工具链版本锁定。
- 升级成本较高。
学习时建议:
- 新项目优先按 Babel 8 思维学习。
- 维护旧项目时理解 Babel 7 配置。
- 迁移时先让 Babel 7 配置靠近 Babel 8 默认行为,再升级主版本。
5. 快速开始
5.1 安装 CLI
bash
pnpm add -D @babel/core @babel/cli @babel/preset-env5.2 创建源码
src/index.js:
js
const user = {
profile: {
name: "Ada",
},
};
console.log(user?.profile?.name ?? "anonymous");5.3 创建配置
babel.config.json:
json
{
"targets": "defaults",
"presets": ["@babel/preset-env"]
}5.4 编译
bash
npx babel src --out-dir lib5.5 配置 npm scripts
json
{
"scripts": {
"build:babel": "babel src --out-dir lib --source-maps"
}
}运行:
bash
pnpm build:babel6. 配置文件体系
6.1 项目级配置
项目级配置通常使用:
text
babel.config.json
babel.config.js
babel.config.cjs
babel.config.mjs
babel.config.ts推荐优先使用 babel.config.json,因为静态 JSON 配置更利于缓存、工具分析和跨工具稳定读取。
适合:
- 应用项目。
- Monorepo 根配置。
- 需要让多个 package 共享同一套 Babel 配置。
- 需要编译
node_modules或 symlink package 的特殊场景。
6.2 文件相对配置
文件相对配置包括:
text
.babelrc
.babelrc.json
.babelrc.js
.babelrc.cjs
.babelrc.mjs
package.json 中的 "babel" 字段适合:
- 单个 package 有独立配置。
- 发布库时需要让消费者工具识别局部配置。
但在 Monorepo 中容易遇到包边界问题,通常建议根目录使用 babel.config.json。
6.3 Monorepo 配置
推荐:
text
repo/
babel.config.json
packages/
ui/
app/
utils/如果在子包内执行 Babel 命令,可能需要:
bash
babel --root-mode upward src -d lib或在 loader 中配置:
js
{
loader: "babel-loader",
options: {
rootMode: "upward"
}
}6.4 JS 配置的缓存
如果使用 babel.config.js:
js
export default function (api) {
api.cache.using(() => process.env.NODE_ENV);
return {
targets: "defaults",
presets: ["@babel/preset-env"],
};
}必须认真处理 api.cache,否则配置函数可能影响构建性能和缓存准确性。
7. plugins 与 presets
7.1 plugin 是什么
plugin 是最小的转换单元。每个 plugin 负责一种或一组 AST 转换。
示例:
json
{
"plugins": ["@babel/plugin-transform-arrow-functions"]
}把箭头函数转换为普通函数是一个转换任务。
7.2 preset 是什么
preset 是一组 plugin 的集合。
示例:
json
{
"presets": ["@babel/preset-env"]
}@babel/preset-env 会根据目标环境自动决定启用哪些转换插件。
常见 preset:
@babel/preset-env@babel/preset-react@babel/preset-typescript
7.3 执行顺序
Babel 官方规则:
- plugins 先于 presets 执行。
- plugins 按数组从前到后执行。
- presets 按数组从后到前执行。
示例:
json
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}执行顺序是:
@babel/preset-react@babel/preset-env
这点很重要,尤其是在 decorators、class fields、React JSX、TypeScript 等语法组合时。
7.4 plugin 选项
json
{
"plugins": [
["@babel/plugin-proposal-decorators", { "version": "2023-11" }]
]
}7.5 syntax plugin 与 transform plugin
syntax plugin 只让 Babel 能“解析”某种语法,不负责转换。
transform plugin 会解析并转换语法。通常 transform plugin 会自动启用对应 syntax 能力。
实践中:
- 只做代码分析或 codemod 时,可能只需要 syntax plugin。
- 要输出给目标环境运行时,需要 transform plugin。
8. @babel/preset-env 详解
8.1 它解决什么问题
如果手动配置每个语法转换插件,会非常麻烦:
json
{
"plugins": [
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-transform-classes",
"@babel/plugin-transform-template-literals"
]
}@babel/preset-env 会根据目标环境自动判断需要哪些转换。
8.2 基础配置
json
{
"targets": "defaults",
"presets": ["@babel/preset-env"]
}8.3 指定旧浏览器目标
如果确实需要兼容旧环境,要明确写出目标:
json
{
"targets": {
"ie": "11"
},
"presets": ["@babel/preset-env"]
}注意:Babel 8 不再默认编译到 ES5。如果项目仍需 ES5,必须显式配置目标。
8.4 模块转换
json
{
"presets": [
["@babel/preset-env", { "modules": false }]
]
}对于 Webpack、Rollup、Vite 等打包工具,通常保留 ESM 更利于 tree shaking。
如果构建 Node CommonJS 输出,可以使用:
json
{
"presets": [
["@babel/preset-env", { "modules": "commonjs" }]
]
}8.5 bugfix 转换
Babel 8 中 bugfixes 选项已被移除为显式选项,新行为会尽量把存在浏览器实现 bug 的语法转换为目标环境中最近似、可用的现代语法。
如果维护 Babel 7 项目,迁移前可以先启用:
json
{
"presets": [
["@babel/preset-env", { "bugfixes": true }]
]
}这能更接近 Babel 8 的行为。
8.6 @babel/preset-env 内部怎么判断要转换什么
preset-env 不是“写死一张转换表”,它的判断链路是:
text
targets(Browserslist 查询)
-> @babel/compat-data(每个语法特性在各环境的支持数据)
-> 对比得出“目标环境不支持的特性列表”
-> 启用对应的 @babel/plugin-transform-* 插件@babel/compat-data是一份持续更新的兼容性数据(来源于 compat-table 等),记录每种语法在 Chrome、Safari、Node 等环境从哪个版本开始支持。- 升级
@babel/preset-env和@babel/compat-data版本,同样的targets可能少转换一些语法(因为新数据表明更多环境已原生支持)。 - 可以用
debug: true直接打印判断结果:
json
{
"presets": [["@babel/preset-env", { "debug": true }]]
}输出会列出:使用的 targets、每个特性由哪个插件转换、哪些插件被跳过。这是排查“为什么某段代码没被转换”的第一手段。
常用选项速查:
| 选项 | 作用 | 常见取值 |
|---|---|---|
targets | 覆盖顶层 targets | 字符串查询或对象 |
modules | 模块转换目标 | false(保留 ESM)、"commonjs"、"amd"、"umd"、"systemjs"、"auto" |
bugfixes | 尽量用“无 bug 的现代语法”替代整段降级 | true(Babel 7 迁移建议;Babel 8 已移除,行为内置) |
include / exclude | 强制包含/排除某个转换插件 | 如 ["transform-optional-chaining"] |
useBuiltIns | polyfill 注入方式(Babel 7 写法) | false / "entry" / "usage" |
corejs | 搭配 useBuiltIns 的 core-js 版本 | Babel 8 中必须写次版本号,如 "3.42" |
forceAllTransforms | 强制启用所有转换 | 替代已删除的 uglify target |
⚠️ Babel 8 中
modules: "auto"的行为变了:默认假定运行环境支持 ESM,不再自动转 CommonJS。直接用命令行编译且需要 CJS 时,要显式写"modules": "commonjs"。通过 Webpack / Rollup / Vite 使用不受影响,这些工具会自己声明支持 ESM。
9. targets 与 Browserslist
9.1 targets 是什么
targets 告诉 Babel 输出代码要支持哪些运行环境。
示例:
json
{
"targets": "> 0.5%, last 2 versions, not dead"
}Babel 会根据目标环境决定是否转换某些语法。
9.2 Browserslist
可以在 package.json 写:
json
{
"browserslist": [
"> 0.5%",
"last 2 versions",
"not dead"
]
}也可以使用 .browserslistrc:
text
> 0.5%
last 2 versions
not dead9.3 不同项目的目标策略
后台管理系统:
text
last 2 Chrome versions
last 2 Edge versions
last 2 Firefox versions
last 2 Safari versions移动 H5:
text
iOS >= 15
Android >= 8
last 2 ChromeAndroid versions对外营销页:
text
> 0.2%
not dead
not op_mini all企业旧系统:
text
ie 11如果目标包含 IE 11,构建产物、polyfill、CSS 兼容和运行时都要一起考虑,不能只改 Babel。
9.4 Babel 8 的 targets 变化
Babel 7 默认近似“所有浏览器”,会导致过度转换。Babel 8 默认使用 Browserslist defaults,更偏现代环境。
架构师要明确写出团队支持矩阵,避免构建结果随着默认值变化而不可控。
9.5 Browserslist 查询语法与优先级
Browserslist 查询是声明式字符串,常见操作符:
| 查询 | 含义 |
|---|---|
defaults | 官方默认:> 0.5%, last 2 versions, Firefox ESR, not dead |
> 0.5% | 全球市场份额大于 0.5% 的浏览器 |
last 2 versions | 每个浏览器的最近 2 个版本 |
not dead | 排除官方已停止维护的浏览器(如 IE 11、老版 Blackberry) |
not op_mini all | 排除 Opera Mini(它的 JS 引擎能力极弱,会拉低整体目标) |
since 2020 | 2020 年以来发布的版本 |
Chrome >= 90 | 指定浏览器版本下限 |
node 22 | Node.js 目标(可写 current node 表示当前运行版本) |
cover 99.5% | 覆盖全球 99.5% 用户的最少浏览器集合 |
多个查询的组合语义:
- 逗号或换行分隔表示并集(OR):
> 0.5%, last 2 versions会包含两个查询命中的所有版本;not开头的查询表示从中排除。 and表示交集:last 2 versions and not dead只保留同时满足两边的版本。
验证和调试:
bash
# 查看当前项目 targets 解析出的实际浏览器列表
npx browserslist
# 查看某个查询解析结果
npx browserslist "> 0.5%, not dead"
# 查看查询覆盖的全球用户比例
npx browserslist --coverage "> 0.5%, not dead"配置优先级(从高到低):
text
工具自己的 targets 选项(如 Babel 的顶层 targets)
-> package.json 的 "browserslist" 字段
-> .browserslistrc 文件
-> 内置默认(Babel 8 为 defaults;Babel 7 无配置时是 >= 0%)💡 同一个仓库里,Babel、Autoprefixer、ESLint(部分环境判断)等工具都会读 Browserslist。统一写在
.browserslistrc里,保证编译目标、CSS 前缀、polyfill 三者一致;如果某个工具需要不同目标,再用它自己的选项单独覆盖。
10. polyfill 与语法转换
10.1 两类兼容问题
Babel 转换主要解决“语法”问题:
js
const value = obj?.name;但有些能力是运行时 API:
js
Promise.any(tasks);
array.at(-1);
structuredClone(data);这些不是单纯语法转换能解决的,需要 polyfill。
10.2 语法转换示例
输入:
js
const add = (a, b) => a + b;输出可能是:
js
var add = function add(a, b) {
return a + b;
};这是语法转换。
10.3 polyfill 示例
输入:
js
Promise.any([task1, task2]);如果目标环境没有 Promise.any,需要注入或引入对应运行时代码。
10.4 Babel 8 中的 polyfill 思路
Babel 8 迁移指南建议:如果使用 @babel/plugin-transform-runtime 的 corejs 或 @babel/preset-env 的 useBuiltIns,迁移到 babel-plugin-polyfill-corejs3 等 polyfill 插件。
应用项目示例:
json
{
"targets": "> 0.5%, not dead",
"presets": ["@babel/preset-env"],
"plugins": [
["babel-plugin-polyfill-corejs3", { "method": "usage-global" }]
]
}库项目要更谨慎,避免污染全局对象。通常更倾向使用 runtime helper 或明确声明 peer / dependency 策略。
method 决定 polyfill 怎么进入代码:
| method | 行为 | 适合 |
|---|---|---|
"usage-global" | 按使用情况分析,在全局作用域注入 import "core-js/modules/..."(修改全局对象) | 应用项目 |
"usage-pure" | 按使用情况,注入纯净版(不污染全局)的 polyfill 导入 | 库项目 |
"entry-global" | 在入口处全量引入(替代手写 import "core-js") | 需要完整兼容时 |
注意:
babel-plugin-polyfill-corejs3的version选项应填项目实际安装的core-js版本(如"3.42"),否则可能按过旧的 core-js 能力判断,注入多余或遗漏的 polyfill。usage-global依赖静态分析,动态访问的 API(如window[apiName])分析不到,特殊场景要手动补。
10.5 常见误区
误区一:以为 Babel 能自动解决所有兼容。
Babel 不会凭空让不存在的运行时 API 可用。
误区二:所有项目都需要 polyfill。
如果目标环境足够现代,过度 polyfill 会增加包体积。
误区三:库里随意污染全局。
库代码如果偷偷修改全局环境,可能影响宿主应用。
11. React、JSX 与 Babel
11.1 JSX 转换
输入:
jsx
const element = <h1 className="title">Hello</h1>;automatic runtime 输出类似:
js
import { jsx as _jsx } from "react/jsx-runtime";
const element = _jsx("h1", {
className: "title",
children: "Hello",
});11.2 配置
bash
pnpm add -D @babel/preset-reactjson
{
"presets": [
["@babel/preset-react", { "runtime": "automatic" }]
]
}11.3 Babel 8 默认行为
Babel 8 中 @babel/preset-react 默认使用 automatic runtime。旧项目如果依赖 classic runtime,应显式配置:
json
{
"presets": [
["@babel/preset-react", { "runtime": "classic" }]
]
}11.4 开发环境
生产构建要明确设置环境变量:
bash
NODE_ENV=production babel src -d libReact 开发转换可能会注入额外调试信息,不适合生产环境。
12. TypeScript 与 Babel
12.1 Babel 可以处理 TypeScript 什么
Babel 可以移除 TypeScript 类型语法:
ts
function add(a: number, b: number): number {
return a + b;
}输出:
js
function add(a, b) {
return a + b;
}12.2 Babel 不做类型检查
Babel 只转换语法,不执行 TypeScript 类型检查。
因此 TS 项目常见组合是:
bash
tsc --noEmit
babel src --out-dir lib或在 Vite / Webpack 中:
- Babel / esbuild / SWC 负责快速转译。
tsc --noEmit负责类型检查。- ESLint 负责代码规则。
12.3 配置
bash
pnpm add -D @babel/preset-typescriptjson
{
"presets": [
"@babel/preset-typescript"
]
}React + TS:
json
{
"presets": [
["@babel/preset-react", { "runtime": "automatic" }],
"@babel/preset-typescript"
]
}注意 preset 执行顺序是从后到前。
12.4 适用场景
Babel 转 TS 适合:
- 应用构建追求速度。
- 已有 Babel 插件链。
- 需要和 JSX、框架插件一起转换。
- 使用
tsc --noEmit单独类型检查。
不适合:
- 只想生成
.d.ts。 - 依赖 TypeScript 自身的某些 emit 行为。
- 需要项目引用的复杂类型构建。
库项目通常需要额外生成声明文件:
bash
tsc --emitDeclarationOnly12.5 Babel 8 中 @babel/preset-typescript 的变化
如果从 Babel 7 升级,TS 相关配置要注意这些官方改动:
| 变化 | 说明 | 迁移 |
|---|---|---|
移除 isTSX / allExtensions | 不再手动声明“按 TSX 解析” | 配合 @babel/preset-react 时直接删除,Babel 8 会根据扩展名自动处理 .tsx;要处理 .vue 等扩展名时用 ignoreExtensions: true |
移除 allowDeclareFields | 行为默认启用 | 直接删除该选项 |
onlyRemoveTypeImports 默认为 true | 只移除明确的 import type,普通 import 中未被使用的值导入会保留 | 使用 TypeScript 3.8 以下,或没开 verbatimModuleSyntax 时,显式设为 false |
| 保留未初始化的类字段 | class A { foo: string } 的 foo 会被初始化为 undefined,而不是删除 | 想要“仅类型”字段时,用 declare foo: string |
| 选项做类型校验 | 写了无效选项名会直接报错 | 清理配置里的无效选项 |
示例:未初始化类字段的差异:
ts
// 输入
class User {
name: string;
declare id: number; // 仅类型
}js
// Babel 8 输出(name 保留并初始化为 undefined,id 被移除)
class User {
name = undefined;
}这一点和 tsc 在 useDefineForClassFields 下的行为对齐,但对依赖“字段被完全移除”的旧代码是行为变化,升级时要检查依赖反射或 in 判断的代码。
13. Babel 与构建工具
13.1 Vite
Vite 开发阶段大量依赖浏览器原生 ESM 和 esbuild。大多数普通项目不需要手动加 Babel。
需要 Babel 的情况:
- React 插件内部使用 Babel 做 Fast Refresh 或 JSX 相关转换。
- 使用特殊 Babel 插件。
- 做兼容性很强的生产构建。
- 需要自定义 AST 变换。
13.2 Webpack
Webpack 中常见:
bash
pnpm add -D babel-loader @babel/core @babel/preset-envjs
export default {
module: {
rules: [
{
test: /\.[cm]?[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
cacheDirectory: true,
},
},
},
],
},
};cacheDirectory 对本地开发和重复构建很重要。
13.3 Rollup
库构建常用 Rollup:
bash
pnpm add -D @rollup/plugin-babel思路:
- Rollup 负责模块打包。
- Babel 负责语法转换。
- TypeScript 或 API Extractor 负责声明文件。
13.4 Jest / Vitest
测试环境需要理解:
- 测试文件是否需要 Babel 转换。
- ESM / CJS 模块边界。
- JSX / TS 是否由测试框架内置支持。
- sourcemap 是否正确。
不要盲目叠加 Babel、ts-jest、swc、esbuild。先明确谁负责转译,谁负责类型检查。
14. Babel 8 迁移重点
14.1 升级前准备
推荐在 Babel 7 项目中先做这些调整:
- 明确配置
targets。 - 如果用
@babel/preset-env,开启bugfixes: true。 - 把
loose/spec迁移到assumptions。 - React 项目显式配置 JSX runtime。
- TypeScript / Flow preset 检查旧选项。
- polyfill 方案迁移到 Babel 8 推荐插件。
- decorators 显式设置
version。
14.2 Node.js 要求
Babel 8 包要求 Node.js ^22.18.0 || >=24.11.0。
这只是 Babel 工具本身的运行要求,不代表输出代码不能运行在旧浏览器或旧 Node 环境。输出兼容性仍由 targets 决定。
14.3 ESM-only
Babel 8 以原生 ESM 发布。如果你的工具链中有直接 require("@babel/core") 的旧代码,需要检查兼容性。
14.4 不再默认 ES5
Babel 8 默认不再把代码编译到 ES5。需要兼容 ES5 时,必须显式配置:
json
{
"targets": {
"ie": "11"
},
"presets": ["@babel/preset-env"]
}14.5 assumptions
旧写法:
json
{
"presets": [
["@babel/preset-env", { "loose": true }]
]
}新思路:
json
{
"assumptions": {
"setPublicClassFields": true
},
"presets": ["@babel/preset-env"]
}assumptions 更明确地表达“项目允许 Babel 基于哪些假设生成更小或更快的代码”。
14.6 polyfill 迁移
旧项目中常见:
json
{
"presets": [
["@babel/preset-env", { "useBuiltIns": "usage", "corejs": 3 }]
]
}Babel 8 中应参考官方迁移指南,迁移到 polyfill 插件方案,并明确 core-js 版本策略。
14.7 升级策略
不要把 Babel 8 升级和业务重构混在一起。
推荐步骤:
- 锁定当前构建产物基线。
- 调整 Babel 7 配置接近 Babel 8。
- 升级 Node.js。
- 升级 Babel 及相关插件。
- 修复配置报错。
- 对比构建产物大小和目标语法。
- 跑完整测试、构建和关键页面验收。
15. 进阶:AST 基础
15.1 AST 是什么
AST 是抽象语法树。它把代码表示成结构化对象,便于工具理解和修改代码。
例如:
js
const answer = 42;可以抽象为:
- Program
- VariableDeclaration
- VariableDeclarator
- Identifier
- NumericLiteral
15.2 path 与 node
在 Babel 插件中:
node是 AST 节点本身。path是节点的上下文包装,提供查找父节点、替换、删除、插入等能力。
示例:
js
Identifier(path) {
console.log(path.node.name);
}15.3 visitor
visitor 是一组访问器:
js
export default function plugin() {
return {
visitor: {
FunctionDeclaration(path) {
// 访问函数声明
},
CallExpression(path) {
// 访问函数调用
},
},
};
}15.4 常见节点
| 节点 | 含义 |
|---|---|
Identifier | 标识符 |
StringLiteral | 字符串字面量 |
NumericLiteral | 数字字面量 |
CallExpression | 函数调用 |
MemberExpression | 成员访问 |
ImportDeclaration | import 声明 |
VariableDeclaration | 变量声明 |
FunctionDeclaration | 函数声明 |
ArrowFunctionExpression | 箭头函数表达式 |
学习 AST 最好的方式是打开 AST Explorer,对照源码观察节点结构。
16. 高级:自定义 Babel 插件
16.1 最小插件
js
export default function renameFooPlugin() {
return {
name: "rename-foo",
visitor: {
Identifier(path) {
if (path.node.name === "foo") {
path.node.name = "bar";
}
},
},
};
}配置:
json
{
"plugins": ["./plugins/rename-foo.js"]
}输入:
js
const foo = 1;
console.log(foo);输出:
js
const bar = 1;
console.log(bar);这个例子故意简单,但真实插件要注意作用域,否则可能误改对象属性、字符串、导入名等。
16.2 检查函数调用
禁止使用 console.log:
js
export default function noConsoleLogPlugin() {
return {
name: "no-console-log",
visitor: {
CallExpression(path) {
const callee = path.get("callee");
if (
callee.isMemberExpression() &&
callee.get("object").isIdentifier({ name: "console" }) &&
callee.get("property").isIdentifier({ name: "log" })
) {
throw path.buildCodeFrameError("Do not use console.log.");
}
},
},
};
}如果只是检查代码规范,优先考虑 ESLint 规则;如果需要转换输出代码,再考虑 Babel 插件。
16.3 插入 import
真实业务插件常做:
- 自动注入运行时 import。
- 按需引入组件。
- 删除调试代码。
- 替换环境变量。
- 编译框架 DSL。
- 对代码做埋点转换。
这类插件要处理:
- 重名变量。
- import 已存在。
- sourcemap。
- 生产与开发环境差异。
- 对 tree shaking 的影响。
16.4 插件测试
建议使用 fixture 测试:
text
fixtures/
basic/
input.js
output.js测试目标不是只看“不报错”,还要确认输出代码完全符合预期。
17. 性能优化与排错
17.1 减少 Babel 处理范围
不要盲目编译所有文件:
- 排除
node_modules。 - 排除构建产物。
- 排除测试快照。
- 对少数需要转换的依赖单独 include。
Webpack 示例:
js
{
test: /\.[cm]?[jt]sx?$/,
include: path.resolve("src"),
use: "babel-loader"
}17.2 使用缓存
Webpack:
js
{
loader: "babel-loader",
options: {
cacheDirectory: true
}
}Babel JS 配置中要正确使用 api.cache,否则缓存可能失效。
17.3 保留 ESM
面向打包工具时,通常不要把模块提前转成 CommonJS:
json
{
"presets": [
["@babel/preset-env", { "modules": false }]
]
}保留 ESM 更利于 tree shaking。
17.4 source map
开发与生产都要理解 sourcemap 策略:
- 开发环境需要便于调试。
- 生产环境要考虑错误定位和源码泄露。
- 上传到 Sentry 等平台时,可以不公开 sourcemap,但保留错误映射能力。
17.5 常见问题
语法无法解析
检查是否需要 parser plugin、preset 或 transform plugin。
插件顺序不对
重点检查 decorators、class properties、TypeScript、React JSX 的顺序。
构建产物过大
检查:
- targets 是否过旧。
- 是否强制 ES5。
- polyfill 是否全量注入。
- helper 是否重复。
- 是否把 ESM 转成 CommonJS 影响 tree shaking。
Babel 与 TypeScript 结果不一致
Babel 不做类型检查,也不完全等价于 tsc 的 emit。库项目要格外谨慎。
Babel 8 升级后配置报错
检查:
- Node.js 版本。
- CommonJS 直接 require Babel 包。
- 旧插件是否支持 Babel 8。
loose/spec选项。useBuiltIns/corejs策略。- React runtime 配置。
18. 学习路线与练习
18.1 基础阶段
目标:会用 Babel 编译代码。
练习:
- 创建
src/index.js。 - 使用 optional chaining 和 nullish coalescing。
- 配置
@babel/preset-env。 - 分别设置现代 targets 和 IE 11 targets。
- 对比输出代码差异。
18.2 进阶阶段
目标:理解配置、preset、plugin、polyfill。
练习:
- 配置
babel.config.json。 - 添加
@babel/preset-react。 - 添加
@babel/preset-typescript。 - 使用
tsc --noEmit做类型检查。 - 对比语法转换和 polyfill 的差异。
18.3 高级阶段
目标:能处理真实工程问题。
练习:
- 在 Webpack 中接入
babel-loader。 - 开启缓存。
- 调整 Browserslist,看构建产物变化。
- 分析某个 polyfill 为什么被引入。
- 用 sourcemap 定位生产错误。
18.4 架构师阶段
目标:把 Babel 当成编译平台理解。
练习:
- 写一个 Babel 插件,把指定 API 替换为新 API。
- 写 fixture 测试。
- 在 Monorepo 中配置根
babel.config.json。 - 对一个旧 Babel 7 项目设计 Babel 8 升级方案。
- 对比 Babel、SWC、esbuild 在项目中的职责边界。
19. 官方参考资料
- Babel 8 发布说明:https://babeljs.io/blog/2026/06/16/8.0.0/
- Babel 8 迁移指南:https://babeljs.io/docs/v8-migration/
- Babel 配置文件:https://babeljs.io/docs/config-files
- Babel 配置选项:https://babeljs.io/docs/options
- Babel plugins 文档:https://babeljs.io/docs/plugins
@babel/preset-env文档:https://babeljs.io/docs/babel-preset-env@babel/preset-react文档:https://babeljs.io/docs/babel-preset-react@babel/preset-typescript文档:https://babeljs.io/docs/babel-preset-typescript- Babel handbook:https://github.com/jamiebuilds/babel-handbook
- Browserslist:https://github.com/browserslist/browserslist