想弄懂 Babel?你必须得先弄清楚这几个包
babel 最常用的配置项
以 babel.config.js 为例:
module.exports = {
// 最常用的两个
plugins: [],
presets: []
}@babel/preset-env
@babel/preset-env 可以分成两部分来理解,preset 和 env。preset 的作用是将 ES6+ 语法编译为 ES5 语法。env 的作用是根据用户设置的目标环境,提供目标环境缺失的功能特性。
env 作用默认是关闭的,需要配置参数 usage 来打开。
preset
下面举一个 @babel/preset-env 将 ES6+ 语法编译为 ES5 语法的例子。这个例子可以让你感受到使用预设的方便。
涉及到的依赖包只有三个。
{
"devDependencies": {
// babel 命令行工具
"@babel/cli": "^7.21.5",
// babel 核心包
"@babel/core": "^7.22.1",
// babel 预设(一系列插件的集合)
"@babel/preset-env": "^7.22.4"
}
}假设有这样一个文件,使用了很多 ES6+ 语法:
// 使用 const
const people = ['xiaowang', 'xiaojun', 'xiaoming']
const obj = {
// 属性简洁表示法
people,
like: 'eat1111',
// 箭头函数,参数默认值,模板字符串,
play: (sport = '篮球') => {
console.log(`喜欢${sport}`)
}
}
// 解构
const {like} = obj在使用 @babel/preset-env 时,如果没有指定 targets,那么默认会将代码兼容到最低版本,将其所有 ES6+ 语法转为 ES5。
preset.config.js
const presets = [
['@babel/preset-env', {
targets: '> 0.25%, not dead'
}]
];
module.exports = {presets};而如果通过 plugins 选项配置插件,需要配置很多个插件!
发现
只要下载了 @babel/preset-env 依赖,下面这些插件的依赖都不用安装。
原因是 @babel/preset-env 包的 dependencies 字段已经添加了这些依赖,在下载 preset-env,也会下载这些依赖。
plugins.config.js
module.exports = {
plugins: [
// 转换箭头函数
'@babel/plugin-transform-arrow-functions',
// 将 let、const 转为 var
'@babel/plugin-transform-block-scoping',
// 转换解构赋值
'@babel/plugin-transform-destructuring',
// 识别解构参数、默认参数、剩余参数
'@babel/plugin-transform-parameters',
// 识别属性缩写
'@babel/plugin-transform-shorthand-properties',
// 识别模板字符串
'@babel/plugin-transform-template-literals'
]
};编译过后的文件:
"use strict";
// 使用 const
var people = ['xiaowang', 'xiaojun', 'xiaoming'];
var obj = {
// 属性简洁表示法
people: people,
like: 'eat1111',
// 箭头函数,参数默认值,模板字符串,
play: function play() {
var sport = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '篮球';
console.log("\u559C\u6B22".concat(sport));
}
};
// 解构
var like = obj.like;env
后面会讲到。env 根据用户设置的环境,动态转化目标浏览器不兼容的语法和动态生成垫片 polyfill API。
提案
TC39 提案分为 0 1 2 3 4 共五个阶段。
- 阶段 0(stage-0)——草根(Strawman):只是一个想法,可能是 Babel 插件。
- 第一阶段(stage-1)——提案(Proposal):这是值得研究的。
- 第二阶段(stage-2)——草案(Draft):初步规范。
- 第三阶段(stage-3)——候选(Candidate):完整的规范和最初的浏览器实现。
- 第四阶段(stage-4)——完成(Finished):将被添加到下一年度的版本中。
再看看官网中这段话:
官网原话
Note: @babel/preset-env won't include any JavaScript syntax proposals less than Stage 3 because at that stage in the TC39 process, it wouldn't be implemented by any browsers anyway. Those would need to be included manually.
提示
我们平常说的 Babel 6、Babel 7 指的是 @babele/core 的版本。
大致意思是:
- 在 Babel 7 以后,@babel/preset-env 舍弃了 Stage presets(@babel/preset-stage-x)这种预设
- @babel/preset-env 只提供 TC39 大于 stage-3 的提案(即只包含最后一个阶段)。因此如果要用小于 stage 4 的提案语法,则必须先安装再手动引入对应插件
第一点比较好理解。这里说说第二点。如果我们想用一些小于 stage-4 阶段的语法的话,光安装 @babel/preset-env 这一个包是没有用的,因为这个包里只包含编译 stage-4 的预设,所以我们就得安装并配置相应的 plugin 去编译。
do-expressions
举个例子,在写这句话时,有一个新的语法:do-expressions,还处于 TC39 的 stage-1 阶段。

我们写一个 index.js 文件:
let x = do {
let tmp = 123
tmp * tmp + 1
}如果直接使用下面的配置文件编译的话,将会报错。
module.exports = {
presets: [
'@babel/preset-env'
]
}1 | let x = do { | ^ 2 | let tmp = 123 3 | tmp * tmp + 1 4 | }

解决方法,下载依赖 @babel/plugin-proposal-do-expressions,然后在 babel 配置文件中的 plugins 中声明。
pnpm i @babel/plugin-proposal-do-expressions -Dmodule.exports = {
presets: [
// '@babel/preset-env'
],
plugins: [
'@babel/plugin-proposal-do-expressions'
]
}再次执行打包脚本,发现编译成功!index.js 变为了 compiled.js。
let x = function () {
let tmp = 123;
return tmp * tmp + 1;
}();查看预设有哪些插件
在 node_modules 下 @babel/preset-env 包的 package.json 的 dependencies 字段中查看。
注意,我们在下载 @babel/preset-env 包的时候,也会下载 dependencies 字段下声明的包。
这也就是前文中提到的,光下载一个 @babel/preset-env 依赖,就可以在配置文件中的 plugins 字段中添加插件,但是却不用专门下载插件的依赖。
polyfill 垫片
功能
ES6+ 除了提供很多简洁的语法(let、class、() => {} 等)外,还为我们提供了很多便捷的 API(Promise、Symbol、Array.prototype.includes 等)。但旧版本浏览器是不支持这些 API 的,而 polyfill 存放了这些 API 的方法与实现,所以它可以使得这些不支持的浏览器支持这些 API。
理解
我们可以把所有这种存放了 ES6+ API 的方法与实现的集合叫做 polyfill,也就是我们经常说的垫片。(如果把我们的旧版本浏览器缺失的 API 当做一个个坑,polyfill 就是用来把这些坑填平)
polyfill 也分种类,有些 polyfill 包含所有浏览器缺失的 API,有些 polyfill 只包含浏览器缺失的 API 的一个,例如 promise-polyfill,proxy-polyfill。
@babel/polyfill
这个包已经不再维护。
为什么不再维护?(来自 GPT)
自从 Babel 7.4.0 版本起,官方推荐使用 "core-js/stable" 和 "regenerator-runtime/runtime" 来替代 "@babel/polyfill"。
以前,"@babel/polyfill" 用于在旧版浏览器中提供缺失的 JavaScript 功能和 API 的垫片(polyfills)。它会将这些垫片添加到全局对象中,以便在不支持这些功能的浏览器中运行新的 JavaScript 代码。然而,这种全局污染的方法在某些情况下可能会引发一些问题。
为了解决这个问题,Babel 推荐使用更为细粒度的 polyfill 引入方式。"core-js/stable" 用于引入 ES 功能的垫片,而 "regenerator-runtime/runtime" 用于引入 Generator 函数和 Async/Await 的垫片。这种方式允许开发者选择需要的功能,而不是一次性引入整个 polyfill 包。
因此,尽管 "@babel/polyfill" 没有被废弃,但官方推荐使用更细粒度的 polyfill 引入方式,以减少对全局对象的影响,并提高代码的可靠性和可维护性。
虽然废弃,但还有有必要了解。
因为很多项目还在用它来给老浏览器提供 ES6+ API。
官网解释
🚨 从 Babel 7.4.0 开始,这个包已经被弃用,转而直接包含 core-js/stable(用于 polyfill ECMAScript 功能)
使用:
import "core-js/stable";我们通过一个例子来认识 @babel/polyfill。创建一个项目,只下载 @babel/polyfill 这个包。
看看 @babel/polyfill 的依赖,发现只有两个:core-js 和 regenerator-runtime。并且 core-js 的版本为 2.6.5。 
core-js@2 作用
它里面存放了实现 ES6+ API 的实现方法。
regenerator-runtime
它的作用是编译我们代码里使用到的 async 函数和 generator 函数。
core-js
我们使用 @babel/polyfill,相当于引入了 core-js@2 和 regenerator-runtime。
babel@7.18.0 后,我们可以直接引入 core-js/stable。
import "core-js/stable";提醒
If you are compiling generators or async function to ES5, and you are using a version of @babel/core or @babel/plugin-transform-regenerator older than 7.18.0, you must also load the regenerator-runtime package
如果你正打算把 generator 函数或 async 函数编译为 ES5,并且你使用的 @babel/helpers 版本小于 7.18.0 或者你使用的 @babel/plugin-transform-regenerator 版本小于 7.18.0,你必须在入口文件中引入 regeneraor-runtime 包。
@babel/helpers 或 @babel/plugin-transform-regenerator 版本小于 7.18.0。
import "core-js/stable";
import "regenerator-runtime/runtime.js";我们实际举个例子!
两个包的版本都小于 7.18.0
不会内联 generator 辅助函数
创建一个项目,只下载下面这些包:
{
"name": "import-regenerator-runtime",
"scripts": {
"compile": "babel index.js -o compiled.js"
},
"devDependencies": {
"@babel/cli": "^7.20.7",
"@babel/core": "7.16.7", // 注意这里版本小于 7.18.0 //
"@babel/plugin-transform-regenerator": "7.16.7" // 注意这里版本小于 7.18.0
}
}
然后配置 babel.config.js
module.exports = {
plugins: [
'@babel/plugin-transform-regenerator'
]
}编译 index.js:
main()
async function main() {
await new Promise(() => {
setTimeout((resolve) => {
console.log('hello, babel regenerator runtime')
resolve()
}, 1000)
})
}
main();
function main() {
return regeneratorRuntime.async(function main$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return regeneratorRuntime.awrap(new Promise(() => {
setTimeout(resolve => {
console.log('hello, babel regenerator runtime');
resolve();
}, 1000);
}));
case 2:
case "end":
return _context.stop();
}
}, null, null, null, Promise);
}可以看到被编译后的文件出现了一个 regeneratorRuntime 变量,但是这个变量并没有被声明。所以如果执行这段代码肯定会报错。
@babel/core 版本小于 7.18.0,@babel/plugin-transform-regenerator 版本大于 7.18.0
会内联 generator 辅助函数


编译后:
function _regeneratorRuntime() { "use strict";
// 略 ...
}
main();
function main() {
return _regeneratorRuntime().async(function main$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return _regeneratorRuntime().awrap(new Promise(() => {
setTimeout(resolve => {
console.log('hello, babel regenerator runtime');
resolve();
}, 1000);
}));
case 2:
case "end":
return _context.stop();
}
}, null, null, null, Promise);
}编译后发现 regeneratorRuntime 替换为了一个 内联的 _regeneratorRuntime() 函数。
如果用 node 执行这个 js 文件,1 秒后会看到控制台输出 hello, babel regenerator runtime。
@babel/core 版本大于 7.18.0,@babel/plugin-transform-regenerator 版本小于 7.18.0
不会内联 generator 辅助函数
未引入 generator 内联辅助函数。
main();
function main() {
return regeneratorRuntime.async(function main$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return regeneratorRuntime.awrap(new Promise(resolve => {
setTimeout(() => {
console.log('hello, babel regenerator runtime');
resolve();
}, 1000);
}));
case 2:
case "end":
return _context.stop();
}
}, null, null, null, Promise);
}总结
会不会生产 generator 函数,取决于 @babel/plugin-transform-regenerator 插件的版本。而与 @babel/core 的版本无关。
transform-regenerator 版本低于 7.18.0 不会生产 generator 辅助函数,高于等于 7.18.0 会生成辅助内联函数。
我认为官方文档在这里写错了,我已经提交了 PR 修改这处错误,等待官方合并。
@babel/runtime
在 Babel 编译的时候,会生成一些辅助函数。
@babel/runtime 里面存放着这些辅助函数。
比如我写这样一个文件:
class People {
constructor() {
}
}
const person = new Person();经过 babel 编译后就会生成很多内联的辅助函数。
但其实这些内联辅助函数都可以在 @babel/runtime 这个包里面找到。
@babel/plugin-transform-runtime
@babel/runtime 里只是定义实现了这些辅助函数,但怎么让辅助函数从内联的形式变为从 @babel/runtime 里引入的形式呢?
可以使用插件 @babel/plugin-transform-runtime。
TIP
只使用插件 @babel/plugin-transform-runtime,会将辅助函数从 @babel/runtime 引入。这时执行编译后的代码,会报错。只需要再下载依赖包 @babel/runtime 即可。
@babel/plugin-transform-runtime 的 devDependencies 如下:
"devDependencies": {
"@babel/core": "workspace:^",
"@babel/helper-plugin-test-runner": "workspace:^",
"@babel/helpers": "workspace:^",
"@babel/preset-env": "workspace:^",
"@babel/runtime": "workspace:^",
"@babel/runtime-corejs3": "workspace:^",
"@babel/template": "workspace:^",
"@babel/types": "workspace:^",
"make-dir": "condition:BABEL_8_BREAKING ? : ^2.1.0"
},