babel的AST实践

428 阅读2分钟

说起 AST 语法,很多前端小伙伴都是不陌生,但是真正使用的却少之又少,本文简单总结一下自己学习和使用 ast 的心路历程。

首先什么是 AST 呢?

JavaScript 是一种解释类型语言,在执行前要经历词法分析、语法分析(AST)、代码生成 三个阶段

  • 词法分析:JavaScript将代码串进行分析为词法单元

    例如代码块 var answer = 6; 会被分解成为 var、answer、=、6、;
    
    [  {      "type": "Keyword",      "value": "var"  },  {      "type": "Identifier",      "value": "answer"  },  {      "type": "Punctuator",      "value": "="  },  {      "type": "Numeric",      "value": "6"  },  {      "type": "Punctuator",      "value": ";"  }]
    

    具体的 type 值可以参考 @babel/types 官方介绍 @babel/types

  • 语法分析:

    将词法单元转为由元素嵌套组成的语法结构树,就是我们所说的抽象语法树(abstract syntax code,AST)

    {
      "type": "File",
      "start": 0,
      "end": 16,
      "loc": {
        "start": {
          "line": 1,
          "column": 0
        },
        "end": {
          "line": 2,
          "column": 0
        }
      },
      "errors": [],
      "program": {
        "type": "Program",
        "start": 0,
        "end": 16,
        "loc": {
          "start": {
            "line": 1,
            "column": 0
          },
          "end": {
            "line": 2,
            "column": 0
          }
        },
        "sourceType": "module",
        "interpreter": null,
        "body": [
          {
            "type": "VariableDeclaration",
            "start": 0,
            "end": 15,
            "loc": {
              "start": {
                "line": 1,
                "column": 0
              },
              "end": {
                "line": 1,
                "column": 15
              }
            },
            "declarations": [
              {
                "type": "VariableDeclarator",
                "start": 4,
                "end": 14,
                "loc": {
                  "start": {
                    "line": 1,
                    "column": 4
                  },
                  "end": {
                    "line": 1,
                    "column": 14
                  }
                },
                "id": {
                  "type": "Identifier",
                  "start": 4,
                  "end": 10,
                  "loc": {
                    "start": {
                      "line": 1,
                      "column": 4
                    },
                    "end": {
                      "line": 1,
                      "column": 10
                    },
                    "identifierName": "answer"
                  },
                  "name": "answer"
                },
                "init": {
                  "type": "NumericLiteral",
                  "start": 13,
                  "end": 14,
                  "loc": {
                    "start": {
                      "line": 1,
                      "column": 13
                    },
                    "end": {
                      "line": 1,
                      "column": 14
                    }
                  },
                  "extra": {
                    "rawValue": 6,
                    "raw": "6"
                  },
                  "value": 6
                }
              }
            ],
            "kind": "var"
          }
        ],
        "directives": []
      },
      "comments": []
    }
    

    其实我们单看 body 就可以,它里面包含了我们的语法树结构,其实里面的一些字段 startendloc 这三个值对我们来说基本上是没有大作用的

    我们可以在这一步中对源代码进行添加、更新和删除节点,

    1. 使用 @babel/parser 将字符串转为 AST 语法树,
    2. 使用 @babel/traverse 转换代码
    const parser = require('@babel/parser');
    const traverse = require('@babel/traverse');
    
    var answer = 6;
    
    const ast = parser.parse(code);
    
    traverse.default(ast, {
      enter(path) {
        if(path.isIdentifier({name: "answer"})) {
          path.node.name = 'question';
        }
        if(path.isLiteral({value: 6})) {
          path.node.value = 666;
        }
      },
    });
    
    const newCode = generator.default(ast, {}, code).code;
    
    console.log('newCode: ', newCode) // newCode:  var question = 666;
    
    

    我们也可以在语法树中使用 node type 进行代码的修改

    traverse.default(ast, {
      VariableDeclarator(path) {
        if(path.node.id.name ==='answer'){
          path.node.init.value ='666'
        }
      }
    });
    
  • 代码生成:

  const generator = require('@babel/generator');
  const newCode = generator.default(ast, {}, code).code;

  console.log('newCode: ', newCode) // newCode:  var question = 666;

在前端的开发过程中,不免的需要去开发一些代码工具,我们通过工具可以去修改我们的源代码,从而提升开发效率。在这个情况下,我们就需要分析源代码、修改源代码、生产新的代码。这就会用到 AST 的语法分析