深入Babel原理系列(一)Babel工作流程与项目结构简介
最近接触了一点关于Babel的知识,产生了一些兴趣,于是就打算看一看Babel的原理,然后总结学习下,这东西太复杂了,就分多个博客来写吧,这篇博客主要讲两件事,第一,简单描述下Babel的工作流程,第二,简单介绍下Babel的项目结构,也就是微内核模式。
工作流程
这里提前提一下,图中Traverser调用多个Transformer的这个结构就是微内核。
也就是说其实Babel的核心代码只包括左边那一列,Parser已经内置支持很多语法. 例如 JSX、Typescript、Flow、以及最新的ECMAScript规范。目前为了执行效率,parser是不支持扩展的。
其他的附加功能,都是一个个Transformer通过插件的形式实现的,只是Babel实现了一些内置的Transformer去实现一些常用的功能,如转换es2015+代码。
解析(Tokenizer + Parser)
对于源码,此时我们就把它看出一个字符串,对其分析的第一步,肯定是先把源码转换成AST,才好后续操作。
有一个在线AST转换器,我们在这上面可以做实验,写出的代码,它就帮我们翻译成AST:
我什么都不写,AST就有一个根结点了:
1 | // AST |
然后我在写一句const text = 'Hello World';
,就变成了
1 | { |
从这个结构我们可以简单看一下AST节点的结构。
每个节点都有type,start和end,type表明了节点的类型,就像根节点的类型就是Program,就是所有的代码,所以它的start是0,end是最后;
而不同类型的节点可能会有自己不同的定义,如VariableDeclaration
节点就有kind属性,表示是通过const还是var,let来声明变量的, declarations属性,表示具体的内容,它是个数组,也就是说一个VariableDeclaration
可以声明多个节点,每个节点的init属性表示该变量初始化的数据是什么。
总结AST树的特点:
- 节点是有类型的。我们学习树这种数据结构时,节点都是最简单的,这里复杂了,有类型。
- 节点与子节点的关系,是通过节点的属性链接的。我们学习的树结构,都是left、right左孩子右孩子的。但是AST树,不同类型的节点,属性不同,Program类型节点的子节点是它的body属性,VariableDeclaration类型的子节点,是它的declarations、kind属性。也就是节点的属性看作是节点的子节点,并且子节点也可能有类型,近而形成一个树。
- 父节点是所有子节点的组合,我们可以看到VariableDeclaration代表的const text = 'Hello World’被拆分成了下面两个子节点,子节点又继续拆分。
希望能从上面的分析中,让大家对AST有一个最直观的认识,就是节点有类型的树。
那么节点的类型系统就很必要了解了,这里是Babel的AST类型系统说明。大家可以看看,可以说类型系统是抽象了代码的各种成员,标识符、字面量、声明、表达式。所以拥有这些类型的节点的树结构,可以用来表达我们的代码。
Travese
第二步:转换。得到ast了,该操作它了,Babel中的babel-traverse用来干这个事。
1 | // 安装 |
babel-traverse库暴露了traverse方法,第一个参数是ast,第二个参数是一个对象,我们写了一个enter方法,方法的参数是个path,咋不是个node呢?我们看一下输出:
其实这个path中包含了node属性,同时也包含了很多用于其他分析的属性,如分析作用域的scope属性等。
生成器 babel-generator
第三步:生成。得到操作后的ast,该生成新代码了。Babel中的babel-generator用来干这个事。
1 | npm install --save babel-generator |
微内核与插件
上面我们讲过了Babel的工作流程,我们发现Babel的核心功能不大,很小其实是分四步走,把代码拆分为token,把token序列构建成AST,对AST进行一些操作,最后再把处理后的AST转化为新的代码。
这个核心功能不大,但是又为了能支持复杂的功能,所以在第三步对AST的处理提供了插件机制(这个插件机制是通过访问者模式实现的),而这种架构方式就叫做微内核。
具体的解释可以看这篇博客:https://bobi.ink/2019/10/01/babel/#访问者模式
参考链接: