Babel 学习日记(0)

作者:商见曜
来源:恒生LIGHT云社区
作为现代前端项目必备的一种技术,Babel 是一个编译器,用于将前沿的 JS 语法转换为浏览器支持的语法。接下来就让我们一起深入学习一下吧。
Babel 的介绍
Babel 指的是“巴别塔”,出自圣经典故:
当时的人类只说一种语言,联合起来无所不能,要创建一座通向天堂的塔,上帝害怕了,为了分化他们,于是就让人类说不同的语言,人类因此失去了这种力量,分化了。而这座塔就是巴别塔。
Babel 学习日记(0)
文章图片

Babel 的用途
  • 转译 esnext,typescript,flow 等到目标环境支持的 js
    这是最常用的功能,用来吧代码中的 esnext 的语法,ts 和 flow 的语法转为基于目标环境支持的语法的实现。并且可以吧目标环境不支持的 api 进行 polyfill
  • 自由自在的编译器
    它暴露了很多的 api,想对源代码进行怎么转译都行
  • 代码的静态分析
    对代码进行 parse 之后,能够进行转换,是因为通过 AST 的结构能够理解代码,在转换之前生成目标代码之外,也可以用于分析代码的信息,进行检查。比如 linter,api 文档自动生成工具通过提取注释生成文档等。
Babel 的转译流程
Babel 是 source to source 的转换,整体转译过程分为三步:
Babel 学习日记(0)
文章图片

  1. parse: 通过 parser 将源代码转换为抽象语法树 (AST)
  2. transform:遍历 AST,调用各种 transform 插件对 AST 进行增删查改
  3. generate: 把转换后的 AST 打印成目标代码,并生成 sourcemap
为什么是三步而不是一步呢?因为源码太乱了,没有统一的格式,直接 source to source 不现实,于是就加一层 AST,将源代码中混乱的、无意义的部分去掉,这样对 AST 操作就能统一起来了。于是一步就变成了三步。(经典中间件思维了)
简单分析一下这三步
parse
前面已经说过 parse 是将源码转换为统一格式的 AST,这个过程是一个词法分析、语法分析的过程;
比如 let name = 'shangjianyao'; 这样一段代码,parse 先把它分成一个个不能细分的单词(术语叫 token),得到 let,name,=,'shangjianyao',这个过程叫词法分析,按照单词的构成规则拆分源码。
之后要把 token 进行递归的组装,生产 AST,这个过程是语法分析,按照不同的语法规则,把一组单词组合成对象。
也就是说先拆了,再按照新规则组装。
transform
transform 阶段对 parse 生成的 AST 进行处理:对 AST 进行深度优先遍历,遍历的过程中处理不同的 AST 节点会调用注册的相应的 vistor 函数(来源于插件),vistor 函数里可以对 AST 节点进行增删改,返回新的节点。这一步是逻辑处理的核心和多变的关键
generate
这个阶段会把 AST 打印成目标字符串,并且会生成 sourcemap。不同的 AST 对应不同结构的字符串。而 sourcemap 记录了从源码到目标代码的转换关系,通过它可以找到目标代码中每个节点对应的源码位置。
好好聊聊 AST
AST (Abstract Syntax Tree)抽象语法树是整个转译流程的核心,它是一个树状结构,有不同类型的节点,基本对应了 JS 语法里的数据类型和语法,我们分别了解一下:
Literal
Literal 字面量,比如 let name = 'shangjianyao' 中,shangjianyao 就是一个字符串字面量 StringLiteral,相应的还有数字字面量 NumericLiteral,布尔字面量 BooleanLiteral,null 字面量 NullLiteral,undefined 字面量 UndefinedLiteral,这些都是 Literal,规则就是这样的 --xxLiteral。
Identifier
也即是标识符的意思,变量名、属性名、参数名等各种声明和引用的名字,都是 Ientifer。(js 里的标识符规则是?Identifer 就是这个识别规则)
试试看,下面这段代码里有多少 identifier?
let name = 'shangjianyao'; function sayHello(name) { console.log('hello ' + name); }

图里标红色下划线的都是 identifier:
Babel 学习日记(0)
文章图片

Statement
statement 是语句,它是可以独立执行的单位,比如包含了一些保留字或者关键词比如 break、continue、return,或者流程控制 if、for、while 等,还有声明语句、表达式语句,这些都是 statement。一般来说,我们写的每一条可以独立执行的代码都是语句。
下面是一些常见的语句,每一行是一个 statement:
break; continue; return; debugger; throw Error(); {} try {} catch (e) {} finally {} for (; ; ) {} while (true) {} do {} while (true); switch (x) { case 1: break; default: ; } console.log(); with (x) {}

stament 的名字组合也是和 literal 的规则相似,都是 xxStatement,比如 break 对应 BreakStament。
Babel 学习日记(0)
文章图片

Declaration
声明语句,是一种特殊的语句,它执行的逻辑是在作用域里声明一个变量、函数、class,import、export 等。
比如:
const name = 'shangjianyao'; function sayHello(name) {} class Person {}import {name} from './name'; export {name}; export default name; export * from './name';

对应 DeclarationStatement,比如:
Babel 学习日记(0)
文章图片

Expression
expression 是表达式,特点是执行完以后有返回值,这是和语句(statement)的区别。
常见表达式有:
[1,2,3]; name = 'shangjianyao'; 1+1; -1; function (){} () => {} class {} name; this; super; a::b;

对应的 expression 是 xxExpression,比如:
Babel 学习日记(0)
文章图片

对于上面的表达式里面,怎么混入了 identifier?因为 identifier 会返回值,所以他也是 expression。有些表达式不能单独执行比如匿名函数表达式和匿名 class 表达式,需要和其他部分组成一个 statement。表达式在被 parse 时会包裹一层 ExpressionStatement,标识这个表达式是被当成语句执行的。
Babel 学习日记(0)
文章图片

Class
作为重要语法糖,class 也有专门的 AST 节点标识。
整个 class 的内容是 ClassBody,属性是 ClassProperty,方法是 MethodDefinition,通过 MethodDefinition 字段来区分构造函数和普通方法。
比如下面这段代码:
class Person { constructor(name) { this.name = name; } sayHello() { console.log('hello ' + this.name); } }

对应的 AST
Babel 学习日记(0)
文章图片

Modules
es module 是语法级别的模块规范,所以也有专门的 AST 节点。
// name import import {name} from './name'; // default import import name from './name'; // namespace import import * as name from './name';

对应的 AST
Babel 学习日记(0)
文章图片

有不同的语法就有不同的 importDeclaration 节点,通过 specifiers 字段来区分 import 的类型,分别对应为 importSpecifier、importDefaultSpecifier、importNamespaceSpecifier。
// name export export { name }; // default export export default name; // all export export * from './name';

对应的 AST
Babel 学习日记(0)
文章图片

分别对应 ExportNameDeclaration、ExportDefaultDeclaration、ExportAllDeclaration。只有 ExportNameDeclaration 才有 specifiers 字段。
Program & Directive
program 是代表整个程序的节点,他有一个 body 字段,是一个数组,里面存放了所有的 statement,执行语句的集合。directives 属性存放 Directive 节点,比如 use strict。
Program 是包裹具体执行语句的节点,而 Directive 则是代码中的指令。
File & Comment
Babel 的 AST 最外层节点是 File,它有 program、comments、tokens 等属性,分别存放 Program 程序体、注释、token 等,是最外层的节点。注释分为块注释(/**/) 和行注释(//)。
公共属性
每种节点都有自己的属性用来标识身份,自然地,也需要一些公共属性去构建和维持 AST,比如:
  • type: AST 节点的类型
  • start、end、loc:start 和 end 代表该节点对应的源码字符串的起始和结束下标,不区分行列。loc 是一个对象,有 line 和 column 属性分别记录开始和结束行列号。
  • leadingComments,innerComments,trailingComments:leadingComments、innerComments、trailingComments 分别存放前面注释,中间注释,后面注释。
  • extra: 存放一些额外的信息,比如 StringLiteral 修改 value 只是值的修改,而修改 extra.raw 则可以连同单双引号一起修改。
AST 可视化查看工具 我们不需要记住上面的这些太多,需要的时候到可视化的 AST 里查看即可。
Babel 学习日记(0)
文章图片

或者查阅 Babel parser 仓库的 AST。
或者查看 @Babel/types 的 typescript 类型定义
小结 本文简单介绍了一下 Babel 的用途:将超前语法转译为兼容语法。
介绍了 Babel 的转译过程:source code -> AST -> transformed AST -> code + sourcemap
介绍了 AST 是如何抽象源码的:利用各种节点来描述源码。比如标识符 Identifier,表达式 xxExpression,语句 xxStatement,声明语句 xxDeclaration, 字面量 xxLiteral,class,modules,file,Program,Directicve,Comment 等等。了解有哪些节点就知道怎么用 AST 标识源码了,当然也不需要记,用(astexpoler.net)进行可视化的查看更加理想。
下一节,我们将会学习 Babel 的 api,以及进行一次简单的实战,下期再见。??
想向技术大佬们多多取经?开发中遇到的问题何处探讨?如何获取金融科技海量资源?
恒生LIGHT云社区,由恒生电子搭建的金融科技专业社区平台,分享实用技术干货、资源数据、金融科技行业趋势,拥抱所有金融开发者。
扫描下方小程序二维码,加入我们!
【Babel 学习日记(0)】Babel 学习日记(0)
文章图片

    推荐阅读