转换过程
核心模块
@babel/core 是编译器的核心模块,打开 package.json 可以看到其依赖包
"dependencies": {
"@babel/generator": "^7.3.4", // Babel 的代码生成器 读取AST并将其转换为代码和源码映射
"@babel/parser": "^7.3.4", // Babel 的解析器
"@babel/traverse": "^7.3.4", // 遍历AST 并且负责替换、移除和添加节点
"@babel/types": "^7.3.4", // 为 AST 节点提供的 lodash 类的实用程序库
...
}
解析:
Babel 使用 @babel/parser 解析代码,Babel 使用的解析器是 babylon。
转换:
Babel提供了@babel/traverse(遍历)方法维护这AST树的整体状态。
生成:
Babel使用 @babel/generator 将修改后的 AST 转换成代码。
理念
babel 本身不具有任何转化功能,它把转化的功能都分解到一个个 plugin 里面。因此当我们不配置任何插件时,经过 babel 的代码和输入是相同的。
Babel 把 Javascript 语法 分为 syntax 和 api。先说 api , api 指那些我们可以通过 函数重新覆盖的语法 ,类似 includes,map,includes,Promise,凡是我们能想到重写的都可以归属到 api。
什么是 syntax ,像 箭头函数,let,const,class, 依赖注入 Decorators,等等这些, Js 是无法重写的,想象下,在不支持的浏览器里不管怎么样,你都用不了 let 这个关键字。
插件和预设执行顺序
Plugin 会运行在 Preset 之前。
Plugin 会从前到后顺序执行。
Preset 的顺序则 刚好相反(从后向前)。
常见预设
preset 分为以下几种:
官方内容,目前包括 env, react, flow, minify 等。这里最重要的是 env,后面会详细介绍。
stage-x,这里面包含的都是当年最新规范的草案,每年更新。这里面还细分为
Stage 0 - 稻草人: 只是一个想法,经过 TC39 成员提出即可。
Stage 1 - 提案: 初步尝试。
Stage 2 - 初稿: 完成初步规范。
Stage 3 - 候选: 完成规范和浏览器初步实现。
Stage 4 - 完成: 将被添加到下一年度发布。
例如 syntax-dynamic-import 就是 stage-2 的内容,transform-object-rest-spread 就是 stage-3 的内容。此外,低一级的 stage 会包含所有高级 stage 的内容,例如 stage-1 会包含 stage-2, stage-3 的所有内容。stage-4 在下一年更新会直接放到 env 中,所以没有单独的 stage-4 可供使用。
es201x, latest 这些是已经纳入到标准规范的语法。例如 es2015 包含 arrow-functions,es2017 包含 syntax-trailing-function-commas。但因为 env 的出现,使得 es2016 和 es2017 都已经废弃。所以我们经常可以看到 es2015 被单独列出来,但极少看到其他两个。 latest 是 env 的雏形,它是一个每年更新的 preset,目的是包含所有 es201x。但也是因为更加灵活的 env 的出现,已经废弃。
env 的核心目的是通过配置得知目标环境的特点,然后只做必要的转换。 如果不写任何配置项,env 等价于 latest,也等价于 es2015 + es2016 + es2017 三个相加(不包含 stage-x 中的插件)
eg.
{
"presets": [
[
"env",
{
"modules": false,
"targets": {
"browsers": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
}
],
"stage-2"
],
"plugins": ["transform-decorators-legacy", "transform-runtime"],
"env": {
"test": {
"presets": ["env", "stage-2"],
"plugins": ["istanbul"]
}
}
注意"modules": false;以前你可能需要用babel来将ES6的模块语法转换为AMD、CommonJS、UMD之类的模块化标准语法,但是现在webpack已经把这个事情做了,所以就不需要babel来做了,但是babel配置项中的modules默认值是commonjs,所以你需要将modules设置为false才行,不然就冲突了。
polyfill和transform-runtime
Babel 只是转换 syntax 层语法,所有需要 @babel/polyfill 来处理API兼容,又因为 polyfill 体积太大,所以通过 preset的 useBuiltIns 来实现按需加载,再接着为了满足 npm 组件开发的需要 出现了 @babel/runtime 来做隔离。
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"debug": true,
"useBuiltIns": "usage",
"targets":{
"browsers":["> 1%", "last 2 versions", "not ie <= 8"]
}
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 2 // 参考官方文档
}
]
],
}
npm install --save @babel/runtime
npm install --save-dev @babel/plugin-transform-runtime
core-js
core-js是@babel/polyfill的核心依赖,它现在已经发布了3.0的版本,而且@babel/preset-env在7.4.0的版本已经支持这个最新的版本。
core-js@3的重要改变:
1.对于ECMAScript中已经稳定的功能,core-js已经几乎完全支持,并在core-js@3中引入了一些新的功能,删除了一些过时的特性
2.对于一些已经加入到ES2016-ES2019中的提案,现在已经被标记为稳定功能。
core-js@3 与 babel
@babel/prest-env
在升级到7.4.0以上的版本以后,既支持core-js@2,也支持core-js@3。所以增加了corejs的配置,来控制所需的版本,默认是core-js@2并且会有文字输出提示升级到3的版本。@babel/prest-env可以通过配置useBuiltIns来根据targets加载@babel/polyfill。
{
"presets": [
[
"@babel/preset-env",
{
"debug": true,
"useBuiltIns": "usage",
"corejs": "2.0.0",
"targets":{
"browsers":["> 1%", "last 2 versions", "not ie <= 8"]
}
}
]
]
}
@babel/runtime
当使用core-js@3的时候,@babel/transform-runtime会从core-js-pure这个包里去加载对应的polyfill代码,core-js-pure里面的代码不会污染全局变量,适合第三方库的开发。
在@babel/transform-runtime的最新版本中,已经支持core-js@3,需作如下操作。
yarn remove @babel/runtime-corejs2
yarn add @babel/runtime-corejs3
//babel.config.js
plugins: [
["@babel/transform-runtime", {
corejs: 3,
}]
]
Babel 7.x
preset 的变更:
淘汰 es201x,删除 stage-x,强推 env (重点)。stage-x 就没那么好运了,它们直接被删了。这是因为 babel 团队认为为这些 “不稳定的草案” 花费精力去更新 preset 相当浪费。stage-x 虽然删除了,但它包含的插件并没有删除。
淘汰 es201x 的目的是把选择环境的工作交给 env 自动进行,而不需要开发者投入精力。凡是使用 es201x 的开发者,都应当使用 env 进行替换。但这里的淘汰 (原文 deprecated) 并不是删除,只是不推荐使用了,不好说 babel 8 就真的删了。 与之相比,stage-x 就没那么好运了,它们直接被删了。这是因为 babel 团队认为为这些 “不稳定的草案” 花费精力去更新 preset 相当浪费。stage-x 虽然删除了,但它包含的插件并没有删除(只是被更名了,可以看下面一节),我们依然可以显式地声明这些插件来获得等价的效果。
npm package 名称的变化
这是 babel 7 的一个重大变化,把所有 babel-* 重命名为 @babel/*。顺带提一句,上面提过的 babel 解析语法的内核 babylon 现在重命名为 @babel/parser,看起来是被收编了。
only 和 ignore 匹配规则的变化
在 babel 6 时,ignore 选项如果包含 .foo.js,实际上的含义 (转化为 glob) 是 ./**/.foo.js,也就是当前目录 包括子目录 的所有 foo.js 结尾的文件。这可能和开发者常规的认识有悖。
于是在 babel 7,相同的表达式 .foo.js 只作用于当前目录,不作用于子目录。如果依然想作用于子目录的,就要按照 glob 的完整规范书写为 ./**/.foo.js 才可以。only 也是相同。
babel-upgrade
它的目的是帮助用户自动化地从 babel 6 升级到 7。