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 |
| 123 | NumberLiteral |
| /^[a-z]+/ | RegExpLiteral |
| true | BooleanLiteral |
| 1.234n | BigintLiteral |
| null | NullLiteral |
代码中的字面量很多,babel就是通过xxxLiteral来抽象这部分内容。
Identifier(标识符)
Identifier 是标识符的意思,变量名、属性名、参数名等各种声明和引用的名字,都是Identifier。
下面分析下下面的代码中有多少Identifier(标识符)
const name = 'chuze';
function say(name) {
console.log(name);
}
const obj = {
name: 'aaa',
};
Statement(语句)
statement是语句,可以是独立执行的单位,比如break、continue、debugger、return、if语句、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){}
对应的语句类型如下:
BreakStatementContinueStatementReturnStatementDebuggerStatementThrowStatementBlockStatementTryStatementForInStatementForStatementWhileStatementDoWhileStatementSwitchStatmentLabeledStatementWithStatement
语句是代码执行的最小单位,代码是由语句(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'
对应的节点有如下几种:
VariableDeclarationFunctionDeclarationClassDeclarationImportDeclarationExportDeclarationExportNamedDeclarationExportAllDeclaration
声明语句用于定义变量,也是代码中的一个基础组成部分。
Expression (表达式)
表达式特点是执行完之后有返回值,这是和语句(statement)的区别。
下面是一些常见的表达式:
[1,2,3]
a = 1
1 + 2;
-1;
function(){};
() => {};
class{};
a;
this;
super;
a::b;
对应的AST如下:
为什么这里Identifier和super也是表达式?
因为Indentifier、super有返回值,符合表达式的特点,所以也是expression。
我们判断AST节点是不是某种类型要看它是不是符合该种类型的特点,比如语句的特点是能够单独执行,表达式的特点是有返回值。
有的表达式可以单独执行,符合语句的特点,所以也是语句,比如赋值表达式、数组表达式等:
a = 1;
[1,2,3];
但是有些表达式不能单独执行,需要和其他类型的节点组合在一起构成语句。
比如匿名函数表达式和匿名class表达式单独执行会报错:
function() {};
class{}
这种需要和其他部分一起构成一条语句,比如组成赋值语句:
a = function() {};
b = class
这条赋值语句对应的AST是这样的:
赋值语句的AST节点
AssignmentExpression包裹了一层ExpressionStatemen节点,代表这个表达式是被当成语句执行的。
Class
class的语法也有专门的AST节点来表示。
整个class的内容是ClassBody,属性是ClassProperty, 方法是ClassMethod(通过kind属性来区分是constructor还是method)。
比如下面的代码:
class Man extends Person {
name = "dahua";
constructor() {}
eat(){}
}
对应的AST是这样的:
class是es 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,ImportNamespaceSpcifier。
图中黄框标出的就是
specifier部分,可以直观的看出整体结构相同,只是specifier部分不同,所以import语法的AST的结构是ImportDeclaration包含各种importspecifier。
Export
export也有三种语法:
named export:
export {b, d};
default export:
export default a;
all export;
export * from 'c';
分别对应ExportNamedDeclaration、ExportDefaultDeclaration、ExportAllDeclaration的AST。
比如这三种export:
export { b, d};
export default a;
export * from 'c';
对应的AST节点为:
Program & Directive
program是代表整个程序的节点,它有body属性代表程序体,存放statement数组,就是具体执行的语句的集合。还有directives属性,存放Directive节点,比如use strict这种指令会使用Directive节点表示。
Program是包裹具体执行语句的节点,而Directive则是代码中的指令部分。
File & Comment
babel的AST最外层节点是File,它有program、comments、tokens等属性,分别存放Program程序体、注释、token等,是最外层节点。
注释分为块注释和行内注释,对应CommentBlock和CommentLine节点。
上面几种就是常见的一些AST节点类型,babel就是通过这些节点来抽象源码中不同的部分。