AST

175 阅读3分钟

什么是AST

AST 技术是现代化前端基建和工程化建设的基石,在开发框架,尤其是构建工具中离不开AST的相关技术

AST 的概念

在计算机科学中,抽象语法树(Abstract Syntax Tree),或简称语法树(Syntax Tree),是源代码语法结构的一种抽象表示,它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似if-condition-then 这样的条件跳转语句,可以使用带有三个分支的节点表示

AST 涉及常用的工具

  • 构建工具
    • webpack
      • webpack 也是可以拿到整个的AST ,使用plugin或者loader进行注入
    • Babel
    • Postcss
    • eslint
  • 开发框架
    • Vue
    • React
      • react的jsx的解析部分就是Babel团队的

AST的整个流程

什么是编译

编译,就是从一种高级语言转换成另一种低级语言

  • 高级语言:JavascriptJavaCgoLang 等有很多用于描述逻辑的语言特性,比如 分支、循环、函数、判断等接近人的思维,可以让开发人员快速掌握。
  • 低级语言:汇编语言机器语言 这些和硬件执行细节有关的,会操作寄存、内存。需要开发者理解熟悉计算机的工作原理。

前端的编译

前端的编译很多是落脚在 ES6 - ES5

AST生成过程

Parser

source code -> tokenizer -> 分词结果 -> 语法分析 -> AST

transform
generator

常见的AST节点

AST 也是有标准的,JS parser的AST大多是estree标准,Babel 的编译也是遵循着这个标准来的

1.Literal

字面量

    const a ='aa'; //StringLiteral
    const b = 'aa'; // templateLiteral
    const c = 123; //NumberLiteral
    const reg = /^[a-z]+/;//RegExpLiteral
    const d = true; //BooleanLiteral
    const e = null; //NullLiteral

2. Identifier

标识符:变量名、属性名、参数名等各种声明和引用的名字

3. Statement

语句,它是可以独立执行的单位

break,continue,debugger,return,if,while,for每一个可以独立运行的代码都是语句 一般末尾有分号

  • BreakStatement
  • ContinueStatement
  • DebuggerStatement
  • ReturnStatement
  • ThrowStatement
    {} //BlockState
    
    try()catch(e){} finally() //try
    
  • ForinStatement
  • ForStatement
  • WhileStatement
  • DowhileStatement

4. Declaration

声明语句

    const a = 1; //VaribleDeclaration
    function (){} //FunctionDeclaration
    class C {}  //ClassDeclaration
    import x form 'xxx' //ImportDeclaration
    export 
    //ExportDefaultDeclaration
    //ExportDeclaration
    //ExportAllDeclaration

5. Expression

表达式特点是执行完了有返回值

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

AST 的公共属性

start,end ,loc(line,column)

Babel

Babel的用途

转译esnext,typeScript,flow等特定目标环境支持的js

一些特定用途的代码转换

taro转译小程序就是基于babel的API实现的

代码的静态分析

linter 工具,也是基于AST对代码检查 jest ESM不行,需要babel

Babel 的编译流程

  • parse:把源码转成AST
    • '@babel/parser'
    • let name = 'xiaoming';
  • tranform:遍历AST,对AST进行改造
    • '@babel/traverser'
      • 遍历AST,最后调用visitor修改AST
    • '@babel/types'
      • 创建、修改、删除,需要用到的types
    • '@babel/template'
      • 如果需要批量处理就需要
  • generate:把改造后的代码生成目标代码以及sourcemap
    • '@babel/generator'

以上这些都是在@babel/core中提供,并基于以上的流程,完成babel的整体编译,并应用plugin 和preset。

简单的打印

    const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const types = require('@babel/types');

const sourceCode = `
  console.info(1)

  function foo() {
    console.log(2)
  }
  class Bar{
    baz(){
      console.error(3);
    };
    render(){
      console.debug(4)
    }
  }
`;
const consoleMembers = ['log', 'error', 'info', 'debug']
// 解析AST
const ast = parser.parse(sourceCode, {
  sourceType: 'unambiguous',
});

traverse(ast, {
  //visitor
  CallExpression(path, state) {
    console.log('-----', path.node, state);
    if (types.isMemberExpression(path.node.callee) 
      && path.node.callee.object.name === 'console' 
      && path.node.callee.object.type === 'Identifier' 
      && consoleMembers.includes(path.node.callee.property.name)
      ) {
        const { line, column} = path.node.loc.start;
        path.node.arguments.unshift(types.stringLiteral(`line:${line},column: ${column}`))
    }
  }
})

const { code, map } = generate(ast);
console.log(code)