什么是Babel

Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。下面列出的是 Babel 能为你做的事情:

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过引入第三方 polyfill 模块,例如 core-js
  • 源码转换(codemods)

https://juejin.cn/post/6844903746804137991

抽象语法数(AST)

要了解Babel的工作原理,那首先需要了解抽象语法树,因为Babel插件就是作用于抽象语法树。首先我们编写的代码在编译阶段解析成抽象语法树(AST),然后经过一系列的遍历和转换,然后再将转换后的抽象语法树生成为常规的js代码。
Babel的工作流程:
TB1nP2ONpXXXXb_XpXXXXXXXXXX-1958-812.png
代码解析成 AST的目的就是为了方便计算机更好地理解我们的代码,看一下代码(在线工具)
image.png
像右边的结构叫做节点(Node),一个AST是由多个或单个这样的节点组成,节点内部可以有多个这样的子节点,构成一颗语法树,这样就可以描述用于静态分析的程序语法。

Babel的工作流程

Babel 的三个主要处理步骤分别是:解析(parse),转换(transform),生成(generate)。

  • 解析

    将代码解析成抽象语法树(AST),每个js引擎(比如Chrome浏览器中的V8引擎)都有自己的AST解析器,而Babel是通过Babylon实现的。在解析过程中有两个阶段:词法分析和语法分析,词法分析阶段把字符串形式的代码转换为令牌(tokens)流,令牌类似于AST中节点;而语法分析阶段则会把一个令牌流转换成 AST的形式,同时这个阶段会把令牌中的信息转换成AST的表述结构。

  • 转换

    在这个阶段,Babel接受得到AST并通过babel-traverse对其进行深度优先遍历,在此过程中对节点进行添加、更新及移除操作。这部分也是Babel插件介入工作的部分。

  • 生成

    将经过转换的AST通过babel-generator再转换成js代码,过程就是深度优先遍历整个AST,然后构建可以表示转换后代码的字符串。

这部分更详细的可以查看Babel手册。而值得注意的是,babel的插件有两种,一种是语法插件,这类插件是在解析阶段辅助解析器(Babylon)工作;另一类插件是转译插件,这类插件是在转换阶段参与进行代码的转译工作,这也是我们使用babel最常见也最本质的需求。这篇文章主要关注的也是babel的转译插件。
为了了解Babel在遍历时处理AST的具体过程,我们还需要了解下面几个重要知识点。

Visitor

当Babel处理一个节点时,是以访问者的形式获取节点信息,并进行相关操作,这种方式是通过一个visitor对象来完成的,在visitor对象中定义了对于各种节点的访问函数,这样就可以针对不同的节点做出不同的处理。我们编写的Babel插件其实也是通过定义一个实例化visitor对象处理一系列的AST节点来完成我们对代码的修改操作。

Path

从上面的visitor对象中,可以看到每次访问节点方法时,都会传入一个path参数,这个path参数中包含了节点的信息以及节点和所在的位置,以供对特定节点进行操作。具体来说Path 是表示两个节点之间连接的对象。这个对象不仅包含了当前节点的信息,也有当前节点的父节点的信息,同时也包含了添加、更新、移动和删除节点有关的其他很多方法。
Path对象包含的属性和方法主要如下:

  1. ── 属性
  2. - node 当前节点
  3. - parent 父节点
  4. - parentPath path
  5. - scope 作用域
  6. - context 上下文
  7. - ...
  8. ── 方法
  9. - get 当前节点
  10. - findParent 向父节点搜寻节点
  11. - getSibling 获取兄弟节点
  12. - replaceWith AST节点替换该节点
  13. - replaceWithMultiple 用多个AST节点替换该节点
  14. - insertBefore 在节点前插入节点
  15. - insertAfter 在节点后插入节点
  16. - remove 删除节点
  17. - ...

State

State是visitor对象中每次访问节点方法时传入的第二个参数。如果看Babel手册里的解释,可能还是有点困惑,简单来说,state就是一系列状态的集合,包含诸如当前plugin的信息、plugin传入的配置参数信息,甚至当前节点的path信息也能获取到,当然也可以把babel插件处理过程中的自定义状态存储到state对象中。

Scopes(作用域)

这里的作用域其实跟js说的作用域是一个道理,也就是说babel在处理AST时也需要考虑作用域的问题,比如函数内外的同名变量需要区分开来,这里直接拿Babel手册里的一个例子解释一下。

Babel的工具集

Babylon

“Babylon 是 Babel的解析器。最初是从Acorn项目fork出来的。Acorn非常快,易于使用,并且针对非标准特性(以及那些未来的标准特性) 设计了一个基于插件的架构。”。这里直接引用了手册里的说明,可以说Babylon定义了把代码解析成AST的一套规范。引用一个例子:

babel-traverse

babel-traverse用于维护操作AST的状态,定义了更新、添加和移除节点的操作方法。之前也说到,path参数里面的属性和方法都是在babel-traverse里面定义的。这里还是引用一个例子,将babel-traverse和Babylon一起使用来遍历和更新节点:

babel-types

babel-types是一个强大的用于处理AST节点的工具库,“它包含了构造、验证以及变换AST节点的方法。该工具库包含考虑周到的工具方法,对编写处理AST逻辑非常有用。”