babel了解

104 阅读5分钟

Babel的包构成

核心包

babel-core:babel转译器本身,提供了babel的转译API,如babel.transform等,用于对代码进行转译。像webpack的babel-loader就是调用这些API来完成转译过程的。

babylon:js的词法解析器,AST生成

babel-traverse:用于对AST(抽象语法树,想了解的请自行查询编译原理)的遍历,主要给plugin用

babel-generator:根据AST生成代码

babel-types:用于检验、构建和改变AST树的节点

babel-template:辅助函数,用于从字符串形式的代码来构建AST树节点

babel-helpers:一系列预制的babel-template函数,用于提供给一些plugins使用

babel-code-frames:用于生成错误信息,打印出错误点源代码帧以及指出出错位置

babel-plugin-xxx:babel转译过程中使用到的插件,其中babel-plugin-transform-xxx是transform步骤使用的

babel-preset-xxx:transform阶段使用到的一系列的plugin(官方写好的插件)

babel-polyfill:JS标准新增的原生对象和API的shim,实现上仅仅是core-js和regenerator-runtime两个包的封装

babel-runtime:功能类似babel-polyfill,一般用于library或plugin中,因为它不会污染全局作用域

babel-cli:babel的命令行工具,通过命令行对js代码进行转译

babel-register:通过绑定node.js的require来自动转译require引用的js代码文件

Parser(解析):此过程接受转换之前的源码,输出 AST(抽象语法树)。在 Babel 中负责此过程的包为 babel/parser;

Transform(转换):此过程接受 Parser 输出的 AST(抽象语法树),输出转换后的 AST(抽象语法树)。在 Babel 中负责此过程的包为 @babel/traverse;

Generator(生成):此过程接受 Transform 输出的新 AST,输出转换后的源码。在 Babel 中负责此过程的包为 @babel/generator。

(1)code --> AST

第一步就是把我们写的 ES6 代码字符串转换成 ES6 AST

那转换的工具为 babel 的 parser

怎么转换的你就理解为正常的转 AST,简单的例子会放到结尾

(2)Transform

这一步做的事情,就是操作 AST。 将 ES6 的 AST 操作 JS 转换成 ES5 的 AST

Transform 会遍历AST,在此过程中会对 AST 结构进行添加、移除、更新等操作,当然这些操作依赖开发者提供的插件。Babel 对每一个 AST 节点都提供了「进入节点 enter」 与 「退出节点 exit」 两个时机,第三方开发者可以利用这两个时机对旧 AST 做操作。值得一提的是,Transform 步骤是 Babel 最复杂的部分,也是第三方插件能大显身手的地方。

这一步是最重要的地方,类似webpack,插件plugins就是在这里生效,也可以自己手写插件加入其中。

params: [ // 参数 { "type": "Identifier", "name": "a" }, { "type": "Identifier", "name": "b" } ]
var visitor = { Identifier: { enter() { console.log('Identifier enter'); }, exit() { console.log('Identifier exit'); } } };
  • 进入 Identifier(params[0])
  • 走到尽头
  • 退出 Identifier(params[0])
  • 进入 Identifier(params[1])
  • 走到尽头
  • 退出 Identifier(params[1])

(3) Generate(代码生成)

上一步是将 ES6 的 AST 操作 JS 转换成 ES5 的 AST

这一步就是将 ES5 的AST 转换成 ES5 代码字符串

经过上面两个阶段,需要转译的代码已经经过转换,生成新的 AST 了,最后一个阶段理所应当就是根据这个 AST 来输出代码。

Babel是通过深度遍历的

此外,还要注意很重要的一点就是,babel只是转译新标准引入的语法,比如ES6的箭头函数转译成ES5的函数;而新标准引入的新的原生对象,部分原生对象新增的原型方法,新增的API等(如Proxy、Set等),这些babel是不会转译的。需要用户自行引入polyfill来解决

plugins

插件应用于babel的转译过程,尤其是第二个阶段transforming,如果这个阶段不使用任何插件,那么babel会原样输出代码。

我们主要关注transforming阶段使用的插件,因为transform插件会自动使用对应的词法插件,所以parsing阶段的插件不需要配置。

presets

如果要自行配置转译过程中使用的各类插件,那太痛苦了,所以babel官方帮我们做了一些预设的插件集,称之为preset,这样我们只需要使用对应的preset就可以了。以JS标准为例,babel提供了如下的一些preset:

• es2015

• es2016

• es2017

• env

es20xx的preset只转译该年份批准的标准,而env则代指最新的标准,包括了latest和es20xx各年份 另外,还有 stage-0到stage-4的标准成形之前的各个阶段,这些都是实验版的preset,建议不要使用。

polyfill

polyfill是一个针对ES2015+环境的shim,实现上来说babel-polyfill包只是简单的把core-js和regenerator runtime包装了下,这两个包才是真正的实现代码所在(后文会详细介绍core-js)。

使用babel-polyfill会把ES2015+环境整体引入到你的代码环境中,让你的代码可以直接使用新标准所引入的新原生对象,新API等,一般来说单独的应用和页面都可以这样使用。

runtime

polyfill和runtime的区别(必看)

直接使用babel-polyfill对于应用或页面等环境在你控制之中的情况来说,并没有什么问题。但是对于在library中使用polyfill,就变得不可行了。因为library是供外部使用的,但外部的环境并不在library的可控范围,而polyfill是会污染原来的全局环境的(因为新的原生对象、API这些都直接由polyfill引入到全局环境)。这样就很容易会发生冲突,所以这个时候,babel-runtime就可以派上用场了。

zhuanlan.zhihu.com/p/58624930