Skip to content

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 在前端编译、兼容和构建链路中的位置。


目录 ​

  1. Babel 是什么
  2. 为什么还需要 Babel
  3. Babel 的工作流程
  4. 版本与生态现状
  5. 快速开始
  6. 配置文件体系
  7. plugins 与 presets
  8. @babel/preset-env 详解
  9. targets 与 Browserslist
  10. polyfill 与语法转换
  11. React、JSX 与 Babel
  12. TypeScript 与 Babel
  13. Babel 与构建工具
  14. Babel 8 迁移重点
  15. 进阶:AST 基础
  16. 高级:自定义 Babel 插件
  17. 性能优化与排错
  18. 学习路线与练习
  19. 官方参考资料

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 主要经过三个阶段:

  1. Parse:把源码解析成 AST。
  2. Transform:遍历 AST,并通过插件修改 AST。
  3. 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)值得逐条对照:

  1. 没有 .browserslistrc 时,显式配置顶层 targets(Babel 7 默认 >= 0%,Babel 8 默认 defaults)。
  2. @babel/preset-env 先在 Babel 7 里开启 bugfixes: true。
  3. loose / spec 迁移到顶层 assumptions。
  4. React 项目显式设置 runtime(Babel 7 默认 classic,Babel 8 默认 automatic);继续用 classic 时设置 useSpread: true。
  5. TypeScript / Flow preset 把 isTSX、allExtensions 换成 ignoreExtensions。
  6. 处理 TS/Flow 时把 allowDeclareFields 设为 true(Babel 7 迁移步骤;Babel 8 中该选项已移除并默认启用此行为,届时直接删掉该选项)。
  7. transform-runtime 的 corejs 或 useBuiltIns 迁移到 babel-plugin-polyfill-corejs3。
  8. @babel/plugin-proposal-decorators 显式设置 version: "legacy" 或 "2023-11"。
  9. 移除已废弃的 @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-env

5.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 lib

5.5 配置 npm scripts ​

json
{
  "scripts": {
    "build:babel": "babel src --out-dir lib --source-maps"
  }
}

运行:

bash
pnpm build:babel

6. 配置文件体系 ​

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"]
}

执行顺序是:

  1. @babel/preset-react
  2. @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"]
useBuiltInspolyfill 注入方式(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 dead

9.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 20202020 年以来发布的版本
Chrome >= 90指定浏览器版本下限
node 22Node.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-react
json
{
  "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 lib

React 开发转换可能会注入额外调试信息,不适合生产环境。


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-typescript
json
{
  "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 --emitDeclarationOnly

12.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-env
js
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 项目中先做这些调整:

  1. 明确配置 targets。
  2. 如果用 @babel/preset-env,开启 bugfixes: true。
  3. 把 loose / spec 迁移到 assumptions。
  4. React 项目显式配置 JSX runtime。
  5. TypeScript / Flow preset 检查旧选项。
  6. polyfill 方案迁移到 Babel 8 推荐插件。
  7. 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 升级和业务重构混在一起。

推荐步骤:

  1. 锁定当前构建产物基线。
  2. 调整 Babel 7 配置接近 Babel 8。
  3. 升级 Node.js。
  4. 升级 Babel 及相关插件。
  5. 修复配置报错。
  6. 对比构建产物大小和目标语法。
  7. 跑完整测试、构建和关键页面验收。

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成员访问
ImportDeclarationimport 声明
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 编译代码。

练习:

  1. 创建 src/index.js。
  2. 使用 optional chaining 和 nullish coalescing。
  3. 配置 @babel/preset-env。
  4. 分别设置现代 targets 和 IE 11 targets。
  5. 对比输出代码差异。

18.2 进阶阶段 ​

目标:理解配置、preset、plugin、polyfill。

练习:

  1. 配置 babel.config.json。
  2. 添加 @babel/preset-react。
  3. 添加 @babel/preset-typescript。
  4. 使用 tsc --noEmit 做类型检查。
  5. 对比语法转换和 polyfill 的差异。

18.3 高级阶段 ​

目标:能处理真实工程问题。

练习:

  1. 在 Webpack 中接入 babel-loader。
  2. 开启缓存。
  3. 调整 Browserslist,看构建产物变化。
  4. 分析某个 polyfill 为什么被引入。
  5. 用 sourcemap 定位生产错误。

18.4 架构师阶段 ​

目标:把 Babel 当成编译平台理解。

练习:

  1. 写一个 Babel 插件,把指定 API 替换为新 API。
  2. 写 fixture 测试。
  3. 在 Monorepo 中配置根 babel.config.json。
  4. 对一个旧 Babel 7 项目设计 Babel 8 升级方案。
  5. 对比 Babel、SWC、esbuild 在项目中的职责边界。

19. 官方参考资料 ​

基于 VitePress 构建的个人学习文档知识库