Babel 的 AST

81 阅读5分钟

babel编译的第一步是把源码parse成抽象语法树AST(Abstract Syntax Tree),后续对这个AST进行转换,且后续的整个编译流程都是围绕AST来进行的,我们先来熟悉下AST

常见的 AST 节点

AST是对源码的抽象,字面量标识符表达式语句模块语法class语法都有各自的AST

Literal(字面量)

literal是字面量的意思,比如let name = 'chuze'中,'chuze'就是一个字符串字面量StringLiteral,相应的还有数字字面量NumberLiteral,布尔字面量BooleanLiteral,正则表达式字面量RegExpLiteral等。

下面是一些字面量对应的Literal节点:

字面量literal节点
"chuze"StringLiteral
'chuze'TemplateLiteral
123NumberLiteral
/^[a-z]+/RegExpLiteral
trueBooleanLiteral
1.234nBigintLiteral
nullNullLiteral

代码中的字面量很多,babel就是通过xxxLiteral来抽象这部分内容。

Identifier(标识符)

Identifier 是标识符的意思,变量名属性名参数名等各种声明和引用的名字,都是Identifier
下面分析下下面的代码中有多少Identifier(标识符)

const name = 'chuze';
function say(name) {
  console.log(name);
}
const obj = {
  name: 'aaa',
};

image.png

Statement(语句)

statement是语句,可以是独立执行的单位,比如breakcontinuedebuggerreturnif语句while语句for语句声明语句表达式语句等。我们写的每一条可以独立执行的代码都是语句。

语句末尾一般都会加一个分号分割,或者用换行分割。

下面这些我们经常写的代码,每一行都是一个Statement:

break;
continue;
return;
debugger;
throw Error();
{}
try {} catch(e) {} finally{}
for (let key in obj) {}
for (let i = 0;i < 10;i ++) {}
while (true) {}
do {} while (true)
switch (v){case 1: break;default:;}
label: console.log();
with (a){}

对应的语句类型如下:

  • BreakStatement
  • ContinueStatement
  • ReturnStatement
  • DebuggerStatement
  • ThrowStatement
  • BlockStatement
  • TryStatement
  • ForInStatement
  • ForStatement
  • WhileStatement
  • DoWhileStatement
  • SwitchStatment
  • LabeledStatement
  • WithStatement
    语句是代码执行的最小单位,代码是由语句Statement)构成的。

Declaration(声明语句)

声明语句时一种特殊的语句,他执行的逻辑是在作用域内声明一个变量、函数、class、import、export等。
比如下面的这些语句都是声明语句:

const a = 1;
function b() {}
class C{}
import a fromm 'a';
export default e = 1;
export {e}
export * from 'e'

对应的节点有如下几种:

  • VariableDeclaration
  • FunctionDeclaration
  • ClassDeclaration
  • ImportDeclaration
  • ExportDeclaration
  • ExportNamedDeclaration
  • ExportAllDeclaration

声明语句用于定义变量,也是代码中的一个基础组成部分。

Expression (表达式)

表达式特点是执行完之后有返回值,这是和语句(statement)的区别。
下面是一些常见的表达式:

[1,2,3]
a = 1
1 + 2;
-1;
function(){};
() => {};
class{};
a;
this;
super;
a::b;

对应的AST如下: image.png 为什么这里Identifier和super也是表达式?
因为Indentifier、super有返回值,符合表达式的特点,所以也是expression。

我们判断AST节点是不是某种类型要看它是不是符合该种类型的特点,比如语句的特点是能够单独执行,表达式的特点是有返回值。

有的表达式可以单独执行,符合语句的特点,所以也是语句,比如赋值表达式、数组表达式等:

a = 1;
[1,2,3];

但是有些表达式不能单独执行,需要和其他类型的节点组合在一起构成语句。

比如匿名函数表达式和匿名class表达式单独执行会报错:

function() {};
class{}

这种需要和其他部分一起构成一条语句,比如组成赋值语句:

a = function() {};
b = class

这条赋值语句对应的AST是这样的: image.png 赋值语句的AST节点AssignmentExpression包裹了一层ExpressionStatemen节点,代表这个表达式是被当成语句执行的。

Class

class的语法也有专门的AST节点来表示。

整个class的内容是ClassBody,属性是ClassProperty, 方法是ClassMethod(通过kind属性来区分是constructor还是method)。

比如下面的代码:

class Man extends Person {
    name = "dahua";
    constructor() {}
    eat(){}
}

对应的AST是这样的: image.png classes next的语法,babel中有专门的AST来表示它的内容。

Modules

es module是语法级别的模块规范,所以也有专门的AST节点。

Import

import 有三种语法:
named import

import {c, d} from 'c';

default import:

import a from 'a';

namespaced import:

import * as b from 'b';

这三种语法都对应ImportDeclaration节点,但是specifiers属性不同,分别对应ImportSpicifier,ImportDefaultSpecifier,ImportNamespaceSpcifierimage.png 图中黄框标出的就是specifier部分,可以直观的看出整体结构相同,只是specifier部分不同,所以import语法的AST的结构是ImportDeclaration包含各种importspecifier

Export

export也有三种语法:
named export:

export {b, d};

default export:

export default a;

all export;

export * from 'c';

分别对应ExportNamedDeclarationExportDefaultDeclarationExportAllDeclarationAST
比如这三种export

export { b, d};
export default a;
export * from 'c';

对应的AST节点为: image.png

Program & Directive

program是代表整个程序的节点,它有body属性代表程序体,存放statement数组,就是具体执行的语句的集合。还有directives属性,存放Directive节点,比如use strict这种指令会使用Directive节点表示。 image.png Program是包裹具体执行语句的节点,而Directive则是代码中的指令部分。

File & Comment

babelAST最外层节点是File,它有programcommentstokens等属性,分别存放Program程序体、注释、token等,是最外层节点。

注释分为块注释和行内注释,对应CommentBlockCommentLine节点。 image.png

上面几种就是常见的一些AST节点类型,babel就是通过这些节点来抽象源码中不同的部分。