从Babel开始认识AST抽象语法树
前言
AST抽象语法树想必大家都有听过这个概念,但是不是只停留在听过这个层面呢。其实它对于编程来讲是一个非常重要的概念,当然也包括前端,在很多地方都能看见AST抽象语法树的影子,其中不乏有vue、react、babel、webpack、typeScript、eslint等。简单来说但凡需要编译的地方你基本都能发现AST的存在。
babel是用来将javascript高级语法编译成浏览器能够执行的语法,我们可以从babel出发来了解AST抽象语法树。
如果这篇文章有帮助到你,️关注+点赞️鼓励一下作者,文章公众号首发,关注 前端南玖 第一时间获取最新文章~
babel编译流程
了解AST抽象语法树之前我们先来简单了解一下babel的编译流程,以及AST在babel编译过程中起到了什么作用?
我这里画了张图方便理解babel编译的整个流程
- parse: 用于将源代码编译成AST抽象语法树
- transform: 用于对AST抽象语法树进行改造
- generator: 用于将改造后的AST抽象语法树转换成目标代码
很明显AST抽象语法树在这里充当了一个中间人的身份,作用就是可以通过对AST的操作还达到源代码到目标代码的转换过程,这将会比暴力使用正则匹配要优雅的多。
AST抽象语法树
在计算机科学中,抽象语法树(Abstract Syntax Tree,AST) 是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
虽然在日常业务中我们可能很少会涉及到AST层面,但如果你想在babel、webpack等前端工程化上有所深度,AST将是你深入的基础。
预览AST
说了这么多,那么AST到底长什么样呢?
接下来我们可以通过工具AST Explorer来直观的感受一下!
比如我们如下代码:
let fn = () => {
console.log('前端南玖')
}
它最终生成的AST是这样的:
- AST抽象语法树是源代码语法结构的一种抽象表示
- 每个包含type属性的数据结构,都是一个AST节点
- 它以树状的形式表现编程语言的语法结构,每个节点都表示源代码中的一种结构
AST结构
为了统一
ECMAScript标准的语法表达。社区中衍生出了ESTree Spec,是目前前端所遵循的一种语法表达标准。
节点类型
| 类型 | 说明 |
|---|---|
| File | 文件 (顶层节点包含 Program) |
| Program | 整个程序节点 (包含 body 属性代表程序体) |
| Directive | 指令 (例如 "use strict") |
| Comment | 代码注释 |
| Statement | 语句 (可独立执行的语句) |
| Literal | 字面量 (基本数据类型、复杂数据类型等值类型) |
| Identifier | 标识符 (变量名、属性名、函数名、参数名等) |
| Declaration | 声明 (变量声明、函数声明、Import、Export 声明等) |
| Specifier | 关键字 (ImportSpecifier、ImportDefaultSpecifier、ImportNamespaceSpecifier、ExportSpecifier) |
| Expression | 表达式 |
公共属性
| 类型 | 说明 |
|---|---|
| type | AST 节点的类型 |
| start | 记录该节点代码字符串起始下标 |
| end | 记录该节点代码字符串结束下标 |
| loc | 内含 line、column 属性,分别记录开始结束的行列号 |
| leadingComments | 开始的注释 |
| innerComments | 中间的注释 |
| trailingComments | 结尾的注释 |
| extra | 额外信息 |
AST是如何生成的
一般来讲生成AST抽象语法树都需要javaScript解析器来完成
JavaScript解析器通常可以包含四个组成部分:
- 词法分析器(Lexical Analyser)
- 语法解析器(Syntax Parser)
- 字节码生成器(Bytecode generator)
- 字节码解释器(Bytecode interpreter)
词法分析
这里主要是对代码字符串进行扫描,然后与定义好的 JavaScript 关键字符做比较,生成对应的Token。Token 是一个不可分割的最小单元。
词法分析器里,每个关键字是一个 Token ,每个标识符是一个 Token,每个操作符是一个 Token,每个标点符号也都是一个 Token,词法分析过程中不会关心单词与单词之间的关系.
除此之外,还会过滤掉源程序中的注释和空白字符、换行符、空格、制表符等。最终,整个代码将被分割进一个tokens列表
javaScript中常见的token主要有:
关键字:var、let、const等
标识符:没有被引号括起来的连续字符,可能是一个变量,也可能是 if、else 这些关键字,又或者是 true、false 这些内置常量
运算符: +、-、 *、/ 等
数字:像十六进制,十进制,八进制以及科学表达式等
字符串:变量的值等
空格:连续的空格,换行,缩进等
注释:行注释或块注释都是一个不可拆分的最小语法单元
标点:大括号、小括号、分号、冒号等
比如我们还是这段代码:
let fn = () => {
console.log('前端南玖')
}
它在经过词法分析后生成的token是这样的:
工具:esprima
[
{
"type": "Keyword",
"value": "let"
},
{
"type": "Identifier",
"value": "fn"
},
{
"type": "Punctuator",
"value": "="
},
{
"type": "Punctuator",
"value": "("
},
{
"type": "Punctuator",
"value": ")"
},
{
"type": "Punctuator",
"value": "=>"
},
{
"type": "Punctuator",
"value": "{"
},
{
"type": "Identifier",
"value": "console"
},
{
"type": "Punctuator",
"value": "."
},
{
"type": "Identifier",
"value": "log"
},
{
"type": "Punctuator",
"value": "("
},
{
"type": "String",
"value": "'前端南玖'"
},
{
"type": "Punctuator",
"value": ")"
},
{
"type": "Punctuator",
"value": "}"
}
]
拆分出来的每个字符都是一个token
语法分析
这个过程也称为解析,是将词法分析产生的token按照某种给定的形式文法转换成AST的过程。也就是把单词组合成句子的过程。在转换过程中会验证语法,语法如果有错的话,会抛出语法错误。
还是上面那段代码,在经过语法分析后生成的AST是这样的:
工具:AST Explorer
{
"type": "VariableDeclaration", // 节点类型: 变量声明
"declarations": [ // 声明
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier", // 标识符
"name": "fn" // 变量名
},
"init": {
"type": "ArrowFunctionExpression", // 箭头函数表达式
"id": null,
"generator": false,
"async": false,
"params": [], // 函数参数
"body": { // 函数体
"type": "BlockStatement", // 语句块
"body": [
{
"type": "ExpressionStatement", // 表达式语句
"expression": {
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"identifierName": "console"
},
"name": "console"
},
"computed": false,
"property": {
"type": "Identifier",
"name": "log"
}
},
"arguments": [ // 函数参数
{
"type": "StringLiteral", // 字符串
"extra": {
"rawValue": "前端南玖",
"raw": "'前端南玖'"
},
"value": "前端南玖"
}
]
}
],
"directives": []
}
}
}
],
"kind": "let" // 变量声明类型
}
在得到AST抽象语法树之后,我们就可以通过改造AST语法树来转换成自己想要生成的目标代码。
常见的解析器
第一个用JavaScript编写的符合EsTree规范的JavaScript的解析器,后续多个编译器都是受它的影响
一个小巧、快速的 JavaScript 解析器,完全用 JavaScript 编写
babel官方的解析器,最初fork于acorn,后来完全走向了自己的道路,从babylon改名之后,其构建的插件体系非常强大
UglifyJS 是一个 JavaScript 解析器、缩小器、压缩器和美化器工具包。
esbuild是用go编写的下一代web打包工具,它拥有目前最快的打包记录和压缩记录,snowpack和vite的也是使用它来做打包工具,为了追求卓越的性能,目前没有将AST进行暴露,也无法修改AST,无法用作解析对应的JavaScript。
AST应用
了解完AST,你会发现我们可以用它做许多复杂的事情,我们先来利用@babel/core简单实现一个移除console的插件来感受一下吧。
这个其实就是找规律,你只要知道console语句在AST上是怎样表现的就能够通过这一特点精确找到所有的console语句并将其移出就好了。
- 先来看下console语句的AST长什么样
很明显它是一个表达式节点,所以我们只需要找到name为console的表达式节点删除即可。
- 编写plugin
const babel = require("@babel/core")
let originCode = `
let fn = () => {
const a = 1
console.log('前端南玖')
if(a) {
console.log(a)
}else {
return false
}
}
`
let removeConsolePlugin = function() {
return {
// 访问器
visitor: {
CallExpression(path, state) {
const { node } = path
if(node?.callee?.object?.name === 'console') {
console.log('找到了console语句')
path.parentPath.remove()
}
}
}
}
}
const options = {
plugins: [removeConsolePlugin()]
}
let res = babel.transformSync(originCode, options)
console.dir(res.code)
从执行结果来看,它找到了两个console语句,并且都将它们移除了
这就是对AST的简单应用,学会AST能做的远不止这些像前端大部分比较高级的内容都能看到它的存在。后面会继续更新Babel以及插件的用法。
我是南玖,我们下期见!!!
从Babel开始认识AST抽象语法树的更多相关文章
- AST抽象语法树
抽象语法树简介 (一)简介 抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并 ...
- AST抽象语法树 Javascript版
在javascript世界中,你可以认为抽象语法树(AST)是最底层. 再往下,就是关于转换和编译的"黑魔法"领域了. 现在,我们拆解一个简单的add函数 function add ...
- 用python演示一个简单的AST(抽象语法树)
如果对'a + 3 * b'进行解释,当中a=2,b=5 代码非常easy,就不再进行具体的解释了. Num = lambda env, n: n Var = lambda env, x: env[x ...
- 【深入】 - AST抽象语法树
参考: https://segmentfault.com/a/1190000016231512
- 从零写一个编译器(九):语义分析之构造抽象语法树(AST)
项目的完整代码在 C2j-Compiler 前言 在上一篇完成了符号表的构建,下一步就是输出抽象语法树(Abstract Syntax Tree,AST) 抽象语法树(abstract syntax ...
- 如何查看SparkSQL 生成的抽象语法树?
前言 在<Spark SQL内核剖析>书中4.3章节,谈到Catalyst体系中生成的抽象语法树的节点都是以Context来结尾,在ANLTR4以及生成的SqlBaseParser解析SQ ...
- 理解Babel是如何编译JS代码的及理解抽象语法树(AST)
Babel是如何编译JS代码的及理解抽象语法树(AST) 1. Babel的作用是? 很多浏览器目前还不支持ES6的代码,但是我们可以通过Babel将ES6的代码转译成ES5代码,让所有的浏览器都 ...
- Babel(抽象语法树,又称AST)
文章:https://juejin.im/post/5a9315e46fb9a0633a711f25 https://github.com/jamiebuilds/babel-handbook/blo ...
- 五分钟了解抽象语法树(AST)babel是如何转换的?
抽象语法树 什么是抽象语法树? It is a hierarchical program representation that presents source code structure acco ...
- 抽象语法树(AST)
AST描述 在计算机科学中,抽象语法树(AST)或语法树是用编程语言编写的源代码的抽象语法结构的树表示.树的每个节点表示在源代码中出现的构造.语法是“抽象的”,因为它不代表真实语法中出现的每个细节,而 ...
随机推荐
- Double数据运算过程中精度调整
Double数据进行运算时,容易出现多位小数的精度问题 ①问题现象 ②解决方案 使用BigDecimal类型来进行Double类型数据运算 创建BigDecimal类型对象时将Double类型的数据转 ...
- 如何在IDEA中自定义模板、快速生成完整的代码?
文章目录 1.修改现有的模板 2.自定义模板 3.在代码中测试自定义模板 1.修改现有的模板 打开设置面板- settings 2.自定义模板 选择定义模板组 选择创建模板 define 代表应用的范 ...
- 3.pygame快速入门-游戏循环及动画实现
游戏循环的开始,意味着游戏的正式开始,游戏循环的作用如下 1.保证游戏不会直接退出 2.变化图像的位置--动画效果 3.检测用户交互--按键.鼠标等 游戏时钟 pyagame提供了一个pyga ...
- 第二阶段:高级核心基础知识·第1章Linux三剑客·1
1.用vmware添加一块10G的硬盘,且永久挂载到/data01中,写出详细的步骤 2.用自己语言描述raid0和raid1的区别 RAID 0: 将两个或以上相同信号,容量的硬盘组合,磁盘阵列的总 ...
- <五>掌握左值引用和初识右值引用
1:C++的引用,引用和指针的区别? 1:从汇编指令角度上看,引用和指针没有区别,引用也是通过地址指针的方式访问指向的内存 int &b=a ; 是需要将a的内存地址取出并存下来, b=20; ...
- 二十三、Pod的service介绍
Pod 的 Service 介绍 一.Service 介绍 Kubernetes Service 定义了这样一种抽象: 一个 Pod 的逻辑分组,一种可以访问它们的策略,通常称为微服务. 这一组 Po ...
- iptables介绍和基本使用
iptables 防火墙是什么 防火墙好比一堵真的墙,能够隔绝些什么,保护些什么. 防火墙的本义是指古代构筑和使用木制结构房屋的时候,为防止火灾的发生和蔓延,人们将坚固的石块堆砌在房屋周围作为屏障,这 ...
- 支持JDK19虚拟线程的web框架,之二:完整开发一个支持虚拟线程的quarkus应用
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本篇是<支持JDK19虚拟线程的web ...
- 云原生之旅 - 8)云原生时代的网关 Ingress Nginx
前言 当我们在Kubernetes部署的服务需要暴露给外部用户使用时,有三种选择:LoadBalancer,NodePort, Ingress. LoadBalancer类型得结合各个Cloud Pr ...
- JavaWeb2
1. web服务器软件:Tomcat 1.1 概述 服务器:安装了服务器软件的计算机 服务器软件:接受用户的请求,处理请求,做出响应 web服务器软件:接受用户的请求,处理请求,做出响应 在web服务 ...