babel 官方在 2018-08-27 发布了文章,babel 7 正式发布,距离 babel 6 相隔3年。
包含内容(官网cp):
- upgrade tool
- JavaScript 配置文件
babel.config.js
- 选择性的配置
overrides
- TC39 提案 支持 @babel/proposals
- jsx、typescript、flow 的支持
- Babel 辅助函数的变化
- 自动 Polyfill (试验)
useBuiltins
详细内容可见:Babel 7 发布
此篇文章主要介绍 babel 7 相关 presets
、plugins
、@babel 下其他所有的 packages
、babel 其他工具、以及 babel 插件相关,即 babel 全套(包含部分流程上的源码链接)
至于 AST 抽象语法树相关可见另一文 抽象语法树-AST-与-编译器-Compiler 内容。
transform or polyfill
ES 相关功能可分为:
- 新的写法、运算符、async/await、const、let、class 等老浏览器无法识别的一类
- Object.assign 等可通过 现有 JavaScript 代码封装实现(例如 core-js / polyfill)的语法糖
要注意的是,babel 作为转换工具,@babel/core, 大部分(也可以理解为全部) babel plugins 只会作用于第1类。
像 第2类 的处理,需要 @babel/polyfill (已废弃,替代品见下文),或者 @babel/preset-env 设定 useBuiltIns
进行,参见:babel-preset-env index.js L190
具体需要用到 polyfill 的功能,可见
- Babel - 学习 ES2015(当前页面搜索 polyfill)
- Polyfills: everything you ever wanted to know, or maybe a bit less
babel 概览
babel 仓库 packages
详见: babel monorepo
babel 7 升级后,相应 babel 包全部统一至 @babel namespace 下。
可以理解为,babel 自身的能力,全部在 @babel namespace 下,而像 babel-loader、gulp-babel 此类其他工具的插件,则仍然作为单独的 package
plugins
从上方的仓库包内,或者 babel 官网 plugins 上,可以看到非常多的 plugins
这里我们先关注 transform
/ proposal
的插件。其对应的作用,就是将抽象语法树 A(es 2015 及以上等等) 转换为 抽象语法树 B(es 5),在上一文 抽象语法树-AST-与-编译器-Compiler 中,称为 visitor
presets
babel 6 支持 preset-es2015
这类年份已经成规范的内容,以及 preset-stage-0
这类未确定的草案的内容。
而 babel 7 统一:
- 将
preset-es2015
这类年份已经成规范的内容统一为@babel/preset-env
,因为规范已落地,可以放心提前使用,提前了解 JS 新规范特性,也减少大家的配置负担(不需要关心到底是 es2015、es2016 还是 es2017) - 将
preset-stage-0
这类未确定的草案,移出 preset。因为草案未落地,使用了这些最新的特性,可能对大家未来没有太大帮助。减少未确定的语法的干扰
当下 presets
官方支持的有:
- @babel/preset-env
- @babel/preset-flow
- @babel/preset-react
- @babel/preset-typescript
而如果我们还是想要使用到一些草案性质的语法、方法,在 babel monorepo 列表中,可以看到很多 plugin-proposal-
前缀的,可以使用对应的 plugin 进行引入
另一方面,我们如果查看 @babel/preset-env
的 package.json 文件
// etc...
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
"@babel/plugin-proposal-optional-catch-binding": "^7.2.0",
"@babel/plugin-proposal-unicode-property-regex": "^7.4.4",
// etc...
也就更清楚,presets
的含义,就是 “预设” 的 plugins
,进行的相应组装
注意:Plugin 会运行在 Preset 之前,Plugin 会从前到后顺序执行,Preset 的顺序则是从后向前。
babel 转换流程
以此段代码为例
const a = 1
console.log([1].includes(a))
"scripts": {
"build": "babel src --out-dir dist"
}
@babel/cli
基于 babel 配置 / 命令行配置,使用 @babel/core
进行编译、输出
@babel/core
- transformFile 读取文件,运行 runAsync
- transformation/index.js runSync
- normalizeFile
- transformFile
- generateCode
@babel/parser
- 上方的 normalizeFile 对应的调用 @babel/parser
@babel/traverse
- 上方的 transformFile 对应的合并 plugin vistors,调用 @babel/traverse
@babel/generator
- 上方的 generateCode 对应的调用 @babel/generator
概括
即 @babel/core
transform / transformFile ...
方法,包含了 @babel/parser,@babel/traverse,@babel/generator 连串调用。
@babel/preset-env 配置项
useBuiltIns && corejs
useBuiltIns: 'entry'
// 必须在开头引入
import '@babel/polyfill'
const a = 1
console.log([1].includes(a))
corejs: 2
// 编译后,@babel/polyfill 被拆分成 N 个包
require("core-js/modules/es6.array.copy-within");
// *** 省略 200+行 ***
require("regenerator-runtime/runtime");
var a = 1;
console.log([1].includes(a))
corejs: 3
// 没变化
require("@babel/polyfill");
var a = 1;
console.log([1].includes(a));
因为 @babel/polyfill 只是 core-js 2.x + regenerator-runtime 的组合,因此其无法被处理出 core-js 3
corejs: 3 + useBuiltIns: 'entry'
的话,就会报警告: @babel/polyfill is deprecated. Please, use required parts of core-js and regenerator-runtime/runtime separately
如果将原代码更改为:
import 'core-js'
import 'regenerator-runtime/runtime'
const a = 1
console.log([1].includes(a))
// 编译后,core-js 被拆分成 N 个包
require("core-js/modules/es.symbol");
// *** 省略 500+行 ***
require("regenerator-runtime/runtime");
var a = 1;
console.log([1].includes(a))
useBuiltIns: 'usage'
// 可以省略
// import '@babel/polyfill'
const a = 1
console.log([1].includes(a))
corejs: 2
// 多了这两行
require("core-js/modules/es7.array.includes");
require("core-js/modules/es6.string.includes");
// import '@babel/polyfill'
var a = 1;
console.log([1].includes(a));
corejs: 3
require("core-js/modules/es.array.includes");
// import '@babel/polyfill'
var a = 1;
console.log([1].includes(a));
概括
@babel/polyfill
已被弃用,建议使用core-js
和regenerator-runtime/runtime
代替。因@babel/polyfill
就是它俩组成,用来模拟完整的 ES2015+ 环境。确实没必要再包一层- corejs 3 比 2 更完善
- 根据具体需要使用
useBuiltIns: 'usage'
或useBuiltIns: 'entry'
( usage 风险:npm 的 dependencies 包进行业务开发,babel 默认是不会检测 依赖包的代码的。 也就是说,如果某个需要 polyfill 的特性,依赖包使用了但是业务代码没有使用,会引起未可知的 BUG)
参考
其他配置项
@babel namespace 下其他工具
弃用
@babel/polyfill
已废弃,使用 import 'core-js'; import 'regenerator-runtime/runtime'
代替
babel 其他运行方式
@babel/node
- 以 child_process 形式
- @babel/core transform,与上方 transformFile 流程类似
- node repl 调用、node vm 运行
- 才疏学浅,也不好解释更多。最关键:不要在生产环境运行。 开发环境大概可以减少一点测试时间,类似 ts-node,属于偷懒用法 / 做法,用处不大
- 详见:@babel/node docs
@babel/register
- 通过 pirates 工具,给 node
require
加了 hook - 在文件顶部使用
require('@babel/register')
后,后续require("./my-plugin.xxx")
都会经过babel.transfrom
后,得到编译后的代码再运行 - 文档:@babel/register
- 类似 @babel/node,偷懒用法 / 做法,最关键:不要在生产环境运行
@babel/standalone —— 浏览器 / 特定环境
- 一个完整的 babel.js / babel.min.js 文件,用来进行 原代码 transform 等功能
- 文档:@babel/standalone
- 一般情况下,用处不大,也用不上。除非是需要特有环境下运行的,例如:JSFiddle, JS Bin, the REPL on the Babel site
parser 阶段语法检查
@babel/highlight
- 使用 js-tokens(有趣的是,其内部用的是 另一个老牌解析器 esprima) 分词
- 使用 命令行高亮
- 具体用法见:@babel/highlight
@babel/code-frame
- 使用 @babel/highlight,大致是优化了错误代码的显示,加了代码下方的箭头 / 错误信息。具体用法见:@babel/code-frame docs
编译结果优化
@babel/plugin-transform-runtime
例如:
class A {}
如果不使用此插件,编译后内容
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var A = function A() {
_classCallCheck(this, A);
};
如果是多个文件,那么每个文件都会有 _classCallCheck
方法,如果再把这些文件合并在一起,_classCallCheck
就会有 N 次定义,以此类推,还有其他各种函数。
如果使用了插件
plugins: [
['@babel/transform-runtime', {
// corejs: false, use @babel/runtime
// corejs: 2, use @babel/runtime-corejs2
corejs: 3, // use @babel/runtime-corejs3
}],
]
对应的编译后内容
// corejs false
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var A = function A() {
(0, _classCallCheck2["default"])(this, A);
};
// corejs 2
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
var A = function A() {
(0, _classCallCheck2["default"])(this, A);
};
// corejs 3
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
var A = function A() {
(0, _classCallCheck2["default"])(this, A);
};
就仍然会是 require 形式引用。最终 webpack、browserify 等工具进行打包时, 就不会有 N 个重复的
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
- 文档见:@babel/plugin-transform-runtime docs
- 看起来上方,corejs 的配置最终编译后内容没什么差距,但实际上 Symbol 等使用,编译后结果会有一些差异。因此还是推荐根据 @babel/env 配置 的 corejs 版本,相应的也 进行 @babel/transform-runtime 插件的 corejs 配置。
@babel/runtime, @babel/runtime-corejs2, @babel/runtime-corejs3
针对 @babel/env
的 corejs
配置
['@babel/env', {
useBuiltIns: 'usage',
corejs: 3,
}]
对应的 runtime
版本,仅用于 @babel/transform-runtime
插件内部
@babel/plugin-external-helpers
功能 与 @babel/plugin-transform-runtime
类似 或者 说有冲突,推荐使用 @babel/plugin-transform-runtime
,此工具也就没必要了。
其需要配合 @babel/cli
的 babel-external-helpers
命令行工具使用,参考阅读:babel-external-helpers
babel 关于移除 @babel/plugin-external-helpers
的讨论 - Remove babel-plugin-external-helpers in favor of modular helpers
@babel/helpers
像前文提到的,会有 _classCallCheck
产生:
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
其是通过 @babel/helpers helpers.js,经过 @babel/template
转换成 AST 后,插入的。
而像 @babel/plugin-transform-runtime
,实际上有一个 build-dist.js 将 @babel/helpers helpers.js 里面的这些 helper
,一个个的编译成单独的文件放在 @babel/runtime
helpers
目录下,相应处理后,此部分原来被添加的代码片段,就变成了
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
其他 helpers
同理
其他 - 解析 / 转换 / 插件开发相关
- @babel/types
- @babel/template
- @babel/helper-xxxx-xxxx
- @babel/plugin-proposal-xxxx-xxxx
- @babel/plugin-syntax-xxxx-xxxx
- @babel/plugin-transform-xxxx-xxxx
此部分内容,为避免此文太长(不看),单独放到 babel 插件开发相关 一文内。
其他 babel 工具
还好用
- babel-loader
- rollup-plugin-babel
- gulp-babel ——
through.obj
包了一下,babel.transformAsync
转了一下 - babel-jest
- 已经移动到 jest 仓库,主要为 jest 的测试代码 提供 babel 转换,让其可以使用 es6 的方式编写
- 使用 ts-jest 貌似更好一些
- babel-eslint,一个 AST parser,原代码 -> babel AST -> estree
- eslint 仅仅支持 es 最新的语法,不支持一些 proposals 的内容,因此需要 babel-eslint 将这些内容,通过 babel 转换出新的 AST,给到 eslint
- 补充:前文抽象语法树-AST-与-编译器-Compiler 提到 eslint 使用 espree,而 babel 根据自身情况做了一些ESTree 的调整,因此又做了一步 babylon-to-espree
.eslintrc.js
->module.exports = { parser: "babel-eslint" };
- 配合使用 对应的规则调整 eslint-plugin-babel
- 使用 typescript-eslint 也行。不过都可以直接用 ts-lint 了。玩法(轮子)真多 (> w <)
- babel-upgrade —— babel 6 -> babel 7 的升级工具
用处不大 / 再见了您
- ember-cli-babel
- broccoli-babel-transpiler
- babelify
- babel-brunch
- grunt-babel
- generator-babel-plugin