Babel 与转译
Babel 是一个 JavaScript 编译器,负责把"新语法"转译为"目标环境能运行的旧语法",并配合 polyfill 补齐缺失的 API。它是现代前端构建链路中的第一环,理解它就能理解"兼容性"问题的全貌。
一、Babel 的作用
输入(ES2022 新语法) 输出(ES5 旧语法)
const fn = (a) => a * 2; → var fn = function (a) { return a * 2; };| 类别 | 例子 | 处理方式 |
|---|---|---|
| 新语法(语法糖) | 箭头函数、解构、可选链 | Babel 转译为旧语法 |
| 新 API(内置对象) | Promise、Array.prototype.includes | polyfill 补齐实现 |
| 类型注解 | TypeScript 类型、Flow 类型 | Babel 剥离 |
转译(transpile)与 polyfill 是两件事:语法靠转译,API 靠 polyfill。这也是理解 Babel 配置的核心线索。
二、核心包
| 包名 | 作用 |
|---|---|
@babel/core | 编译器核心,负责解析、转换、生成代码 |
@babel/cli | 命令行工具,在终端里使用 Babel |
@babel/preset-env | 按目标环境自动选择需要转换的语法 |
@babel/preset-react | 转换 JSX |
@babel/preset-typescript | 剥离 TypeScript 类型 |
@babel/plugin-transform-xxx | 单个语法转换插件,如 plugin-transform-arrow-functions |
npm install -D @babel/core @babel/cli @babel/preset-env命令行直接使用:
npx babel src --out-dir dist # 转换 src 下所有文件到 dist
npx babel src/index.js --out-file dist/index.js三、preset 与 plugin
3.1 区别
| 维度 | plugin | preset |
|---|---|---|
| 粒度 | 单个转换(如箭头函数) | 一组转换的集合 |
| 本质 | 一个转换函数 | 若干 plugin 的组合 |
| 使用场景 | 精确控制某个语法 | 一键启用一整套(env/react/typescript) |
@babel/preset-env 内部就是由 plugin-transform-arrow-functions、plugin-transform-classes 等几十个插件组成的集合。
3.2 执行顺序
- plugin 先于 preset 执行;
- plugin 按数组顺序执行(前到后);
- preset 按逆序执行(后到前,后面的预设先执行)。
{
"presets": ["@babel/preset-react", "@babel/preset-env"],
"plugins": ["@babel/plugin-transform-runtime"]
}上例中插件先跑,preset 按 preset-env → preset-react 的顺序执行(数组逆序)。
四、@babel/preset-env
preset-env 的核心价值是按目标环境智能转换——只转目标环境不支持的语法,避免无谓的体积膨胀。
4.1 targets 与 browserslist
{
"presets": [
["@babel/preset-env", {
"targets": "> 0.25%, not dead",
"useBuiltIns": "usage",
"corejs": 3
}]
]
}targets 支持直接写 browserslist 查询字符串,也支持指定环境:
{
"targets": {
"chrome": "58",
"ie": "11"
}
}browserslist 是一套独立的查询语法,可在项目根的 .browserslistrc 或 package.json 的 browserslist 字段中声明,Babel、PostCSS(Autoprefixer)等工具共享同一份配置:
# .browserslistrc
last 2 versions
> 1%
not dead{
"browserslist": ["> 1%", "last 2 versions", "not dead"]
}| 写法 | 含义 |
|---|---|
last 2 versions | 各浏览器最近两个版本 |
> 1% | 全球使用率超过 1% 的浏览器 |
not dead | 排除官方停止维护的浏览器 |
ie >= 11 | 指定 IE 11 及以上 |
defaults | > 0.5%, last 2 versions, Firefox ESR, not dead |
can i use 网站(caniuse.com)的数据就是这些查询的底层依据。
4.2 useBuiltIns 三种模式
| 模式 | 行为 | 适用 |
|---|---|---|
false(默认) | 只转语法,不处理 API | 用 CDN 方式手动引入 polyfill |
"entry" | 在入口 import "core-js" 处,按 targets 全量注入需要的 polyfill | 简单粗暴,体积较大 |
"usage" | 扫描每个文件用到的 API,按需注入 | 最推荐,体积最小 |
// useBuiltIns: "usage" 时,代码里用到才注入
const arr = [1, 2, 3];
console.log(arr.includes(2)); // 自动注入 includes 的 polyfill
// 无需手动引入 core-js
// 若用 "entry",则需在入口手动写:
import "core-js/stable";
import "regenerator-runtime/runtime";五、polyfill 概念与演进
polyfill(垫片) 用旧语法重新实现新 API,让不支持的环境也能运行新特性。例如 Promise 没有原生实现时,用回调模拟一个。
| 阶段 | 方案 | 说明 |
|---|---|---|
| 早期 | @babel/polyfill | 合并 core-js 与 regenerator-runtime,已废弃 |
| 现在 | core-js + regenerator-runtime | 分别提供新 API 与 async/await/生成器的运行时 |
npm install core-js@3 regenerator-runtimeregenerator-runtime 负责 async/await 与生成器函数转译后的运行时支持——这类转译产物体积大,这也是为什么建议尽量让目标环境原生支持。
六、transform-runtime 与 polyfill 区别
@babel/plugin-transform-runtime 解决两个问题:避免 helper 重复注入、避免 polyfill 污染全局。
{
"plugins": [
["@babel/plugin-transform-runtime", { "corejs": 3 }]
]
}| 对比项 | preset-env + polyfill | transform-runtime |
|---|---|---|
| 注入方式 | 修改全局(Array.prototype.includes) | 以模块导入方式使用局部副本 |
| 全局污染 | 有 | 无 |
| 适用场景 | 业务应用代码 | 库/插件(不能污染使用者全局) |
| 运行时体积 | 按需较大 | 有运行时依赖,需打进包 |
转译后的 helper(如 _classCallCheck)默认在每个文件重复注入,transform-runtime 把它们提取为公共模块统一引用,显著减小产物:
// 不开启:每个文件里都有一份
function _classCallCheck(instance, Constructor) { ... }
// 开启后:统一从 @babel/runtime 引入
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";七、构建集成
Babel 本身不打包,它常作为一环嵌入构建链。Webpack 中通过 babel-loader 接入:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
},
],
},
};Vite 默认用 esbuild 转译(速度更快但兼容策略不同),需要时也可通过 @vitejs/plugin-legacy 接入 Babel 做低版本兼容。
八、Babel 与 TypeScript
| 方案 | 工作原理 | 类型检查 |
|---|---|---|
tsc 单独编译 | TypeScript 官方编译器,转译 + 类型检查一体 | 有 |
@babel/preset-typescript | 只剥离类型注解,不做类型检查 | 无(快) |
| 两者结合 | 构建用 Babel(快),tsc --noEmit 单独做类型检查 | 有 |
{
"presets": [
"@babel/preset-env",
"@babel/preset-typescript"
]
}tsc --noEmit # 只做类型检查,不输出文件注意:Babel 逐文件转译、不知道跨文件类型信息,所以部分依赖类型推断的语法(如 const enum、命名空间、装饰器)不受支持或需插件配合。
九、工作流总结
编写 ES2022+ / TS 代码
↓
babel-loader / esbuild(转译语法、剥离类型)
↓
preset-env 按 browserslist 决定转哪些
↓
core-js 按 useBuiltIns 注入 API polyfill
↓
Webpack/Rollup 打包Babel 的本质是"语法降级器 + API 补丁调度器"。配置 Babel 时只需想清楚三件事:目标环境是什么(targets)、要不要补 API(useBuiltIns)、是业务代码还是库(是否用 transform-runtime)。