本篇文章分享来自小伙伴「huanxing」的一次学习总结分享,希望跟社区的同学一起探讨。
Babel 是什么
babel 是一个 js 转译器。主要用于在当前和旧的浏览器或环境中,将 ECMAScript 2015+ 代码转换为 JavaScript 向后兼容版本的代码,最开始叫 6to5,顾名思义是 es6 转 es5,但是随着ECMAScript 标准规范发展越来越快,新语法层出不穷,有了 es7、es8 等, 6to5 的名字已经不合适了,所以改名为了 babel。
一般可以用于 es next、typescript等代码的转换,同时还暴露出了 api 让开发者可以进行特定用途的转换。除此以外,还可以做各种静态分析,如:语法检查,编译,代码高亮,代码转换,优化,压缩等等。
Babel 编译流程
Babel 的编译流程主要分为三步:解析 -> 转换 -> 生成
- parse:通过 parser 把源码转成抽象语法树(AST)。
- transform:遍历 AST,调用各种 transform 插件对 AST 进行增删改。
- generate:把转换后的 AST 打印成目标代码,并生成 sourcemap。
parse(解析)
这个阶段的主要任务就是将code转为AST,其中会经过两个阶段,分别是词法分析和语法分析。
比如 const CHANNEL_ID = 'zyzq'; 这样一段源码,我们要先把它分成一个个不能细分的单词(token),也就是 const, CHANNEL_ID, =, 'zyzq',这个过程是词法分析,按照单词的构成规则来拆分字符串成单词。
之后要把 token 进行递归的组装,生成 AST,这个过程是语法分析,按照不同的语法结构,来把一组单词组合成对象,比如声明语句、赋值表达式等都有对应的 AST 节点。
transform(转换)
transform 阶段是对 parse 生成的 AST 的处理,会进行 AST 的遍历,遍历的过程中处理到不同的 AST 节点会调用注册的相应的 visitor 函数,visitor 函数里可以对 AST 节点进行增删改,返回新的 AST(可以指定是否继续遍历新生成的 AST)。这样遍历完一遍 AST 之后就完成了对代码的修改
generate(生成)
该阶段的任务就是将 AST 转换回 code, 在此期间会对 AST 进行深度优先遍历,根据节点所包含的信息生成对应的代码,并且会生成对应的sourcemap。
sourcemap 记录了源码到目标代码的转换关系,通过它我们可以找到目标代码中每一个节点对应的源码位置,用于调试的时候把编译后的代码映射回源码,或者线上报错的时候把报错位置映射到源码
什么是 AST
从上面的介绍可以知道,babel 的整个编译流程都是围绕 AST 来的,那么什么是 AST 呢?
在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
简单来说就是按照某种约定好的规范,以树形的数据结构把我们的代码描述出来,让 js 引擎和转译器能够理解。
当然 AST 不是 JS 特有的,每个语言的代码都能转换成对应的 AST,并且 AST 结构的规范也有很多,Babel 使用一个基于 ESTree 修改过的 AST,我们可以通过 AST explorer 这个网站在线生成AST,用来学习一下结构。
如下图,可以看到 const CHANNEL_ID = 'zyzq' 的 AST 结构,我们找到了几个重要信息,最外层是一个VariableDeclaration 意思是变量声明,所使用的类型是 const,字段 declarations 内还有一个 VariableDeclarator[变量声明符] 对象,有一个命名为 CHANNEL_ID 的 Identifer[标识符] ,值为 zyzq 的 Literal[字面量]。
常用的 AST 节点
如果想要了解或者进行 babel 插件开发,了解一些常用的 AST 节点是必不可少的,如:
- Literal
Literal 是字面量的意思,比如 const CHANNEL_ID = 'zyzq'中,'zyzq'就是一个字符串字面量 StringLiteral,相应的还有数字字面量 NumericLiteral,布尔字面量 BooleanLiteral,模板字面量 TemplateLiteral,正则表达式字面量 RegExpLiteral 等。代码中的字面量很多,babel 就是通过 xxLiteral 来抽象这部分内容的。
- Identifer
Identifer 是标识符的意思,变量名、属性名、参数名等各种声明和引用的名字,都是Identifer。我们知道,JS 中的标识符只能包含字母或数字或下划线(“_”)或美元符号(“$”),且不能以数字开头。这是 Identifier 的词法特点。
- Statement
语句是代码执行的最小单位。我们常见的有 if 语句、while 语句、return 语句等等。下面代码中每一行都是一个 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){}
- Declaration
声明语句是一种特殊的语句,它执行的逻辑是在作用域内声明一个变量、函数、class、import、export 等
const a = 1; //VariableDeclaration
function b(){} //FunctionDeclaration
class C {} //ClassDeclaration
import d from 'e'; // ImportDeclaration
export default e = 1; //ExportDefaultDeclaration
export {e}; //ExportNamedDeclaration
export * from 'e'; //ExportAllDeclaration
- Expression
expression 表达式的特点是执行完成后会有返回值,这也是它和语句的区别。 有的表达式可以独立作为语句执行,所以在解析时会在最外层加一个 ExpressionStatement 节点。 常见的表达式有以下几种:
[1,2,3] //ArrayExpression 数组表达式
a = 1 //AssignmentExpression 赋值表达式
1 + 2; //二元表达式
-1; //一元表达式
function(){}; //函数表达式
() => {}; //箭头函数表达式
class{}; //class表达式
a; //标识符
this; //this表达式
super; //super
a::b; //绑定表达式
- Class
class 的语法比较特殊,有专门的 AST 节点来表示。整个 class 的内容是 ClassBody,属性是 ClassProperty,方法是ClassMethod(通过 kind 属性来区分是 constructor 还是 method)。如:
- Import
import 有 3 种语法
named import:
import {c, d} from'c';
default import:
import a from 'a';
namespaced import:
import * as b from 'b';
这 3 种语法都对应 ImportDeclaration 节点,但是 specifiers 属性不同,分别对应 ImportSpicifier、ImportDefaultSpecifier、ImportNamespaceSpcifier。
- export
export 也有3种语法
named export:
export { b, d};
default export:
export default a;
all export:
export * from 'c';
分别对应 ExportNamedDeclaration、ExportDefaultDeclaration、ExportAllDeclaration。
- AST 的公共属性
- type: AST 节点的类型
- start、end、loc:start 和 end 代表该节点在源码中的开始和结束下标。而 loc 属性是一个对象,有 line 和 column 属性分别记录开始和结束的行列号。
- leadingComments、innerComments、trailingComments: 表示开始的注释、中间的注释、结尾的注释,每个 AST 节点中都可能存在注释,而且可能在开始、中间、结束这三种位置,想拿到某个 AST 的注释就通过这三个属性。
- extra::记录一些额外的信息,用于处理一些特殊情况。比如 StringLiteral 的 value 只是值的修改,而修改 extra.raw 则可以连同单双引号一起修改。
以上就是 AST 的常用节点介绍,了解了 AST 就可以把对代码的操作转为对 AST 的操作了,这是编译、静态分析的第一步。