AST抽象语法树和Babel插件

更新日期: 2020-11-08 阅读: 4k 标签: 插件
AST(Abstract Syntax Tree, AST)抽象语法树,可以把代码转译成语法树的表现形式

例如下面的代码:

var a = 3;
a + 5

AST抽象出来的树结构:


Program代表的是根节点

  • VariableDeclaration变量声明

    • Identifier 标识符 + Numeric Literal数字字面量
  • BinaryExpression(二项式)

    • Identifier 标识符,operator 二项式运算符,Numeric Literal数字字面量

可以到 astexplorer.net 查看AST的解析结果


编译器过程

大多数编译器的工作过程可以分为三部分:

  • Parse(解析)
  • Transform(转换)
  • Generate(代码生成)


安装 esprima 来理解编译的过程:

npm install esprima estraverse escodegen
const esprima = require('esprima')
const estraverse = require('estraverse')
const escodegen = require('escodegen')

let code = `var a = 3`

// Parse(解析)
let ast = esprima.parseScript(code);

//Transform(转换)
estraverse.traverse(ast, {
  enter(node) {
    console.log("enter",node.type);
  },
  leave(node) {
    console.log("leave",node.type);
  }
});

// Generate(代码生成)
const result = escodegen.generate(ast);
babel 对于 AST 的遍历是深度优先遍历,对于 AST 上的每一个分支 Babel 都会先向下遍历走到尽头,然后再向上遍历退出刚遍历过的节点,然后寻找下一个分支。

AST 对语法树的遍历是 深度优先遍历,所以会先向下遍历走到尽头,然后再向上遍历退出刚遍历过的节点,寻找下一个分支,所以遍历的过程中控制台会打印下面的信息:

enter Program
enter VariableDeclaration
enter VariableDeclarator
enter Identifier
leave Identifier
enter Literal
leave Literal
leave VariableDeclarator
leave VariableDeclaration
leave Program

通过type的判断我们可以修改变量的值:

estraverse.traverse(ast, {
  enter(node) {
    if(node.type === "Literal"){
      node.value = "change";
    }
  }
});

// var a = "change";


babel插件

来看下 babel是如何工作的, 首先通过npm安装 @babel/core和 babel-types:

npm install @babel/core

我们知道babel能编译es6代码,例如最基础的const和箭头函数

// es2015 的 const 和 arrow function
const add = (a, b) => a + b;

// Babel 转译后
var add = function add(a, b) {
  return a + b;
};

我们可以到 astexplorer 查看生成的语法树:


{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration", // 变量声明
      "declarations": [ // 具体声明
        {
          "type": "VariableDeclarator", // 变量声明
          "id": {
            "type": "Identifier", // 标识符(最基础的)
            "name": "add" // 函数名
          },
          "init": {
            "type": "ArrowFunctionExpression", // 箭头函数
            "id": null,
            "expression": true,
            "generator": false,
            "params": [ // 参数
              {
                "type": "Identifier",
                "name": "a"
              },
              {
                "type": "Identifier",
                "name": "b"
              }
            ],
            "body": { // 函数体
              "type": "BinaryExpression", // 二项式
              "left": { // 二项式左边
                "type": "Identifier",
                "name": "a"
              },
              "operator": "+", // 二项式运算符
              "right": { // 二项式右边
                "type": "Identifier",
                "name": "b"
              }
            }
          }
        }
      ],
      "kind": "const"
    }
  ],
  "sourceType": "module"
}

通过代码模拟一下:

const babel = require('babel-core');
const t = require('babel-types');

let code = `let add = (a, b)=>{return a+b}`;
let ArrowPlugins = {
visitor: {
    ArrowFunctionExpression(path) {
      let { node } = path;
      let body = node.body;
      let params = node.params;
      let r = t.functionExpression(null, params, body, false, false);
      path.replaceWith(r);
    }
  }
}
let result = babel.transform(code, {
  plugins: [
    ArrowPlugins
  ]
})
console.log(result.code);

我们可以在访问者visitor中捕获到匹配的type,在回调函数里面替换箭头函数。


class 转换

const babel = require("@babel/core");
const typs = require("@babel/types");

const code = `
class Animal {
    constructor(name){
        this.name = name
    }
    getName(){
        return this.name
    }
}
`

const classPlugins = {
    visitor:{
        ClassDeclaration(path){
            let node = path.node;
            let body = node.body.body;
            let id = node.id;
            let params = node.params;
            let methods = body.map(method=>{
                if(method.kind === "constructor"){
                    return typs.functionDeclaration(id, method.params, method.body)
                }else{
                    // Animal.prototype
                    let left = typs.memberExpression(id,typs.identifier("prototype"));
                    // Animal.prototype.getName
                    left = typs.memberExpression(left,method.key);
                    let right = typs.functionExpression(null,method.params,method.body);
                    return typs.assignmentExpression("=",left,right);
                }
            })
            path.replaceWithMultiple(methods);
        }
    }
}

const result = babel.transform(code, {
  plugins: [classPlugins]
})

console.log(result.code)


import 转换

const babel = require('@babel/core');
const types = require('@babel/types');

const code = `import antd,{Button} from "antd"`;

const importPlugin = {
  visitor: {
    ImportDeclaration(path) {
      let node = path.node
      let specifiers = node.specifiers
      if (
        !(
          specifiers.length == 1 &&
          types.isImportDefaultSpecifier(specifiers[0])
        )
      ) {
        specifiers = specifiers.map((specifier) => {
          let local = types.importDefaultSpecifier(specifier.local);
          if (types.isImportDefaultSpecifier(specifier)) {
            return types.importDeclaration([local],types.stringLiteral(node.source.value))
        } else {
            return types.importDeclaration([local],types.stringLiteral(node.source.value+"/lib/"+specifier.local.name))
          }
        });
        path.replaceWithMultiple(specifiers)
      }
    },
  },
}

const result = babel.transform(code, {
  plugins: [importPlugin],
});

console.log(result.code)


参考链接

来自:https://segmentfault.com/a/1190000027079474


本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/9828

相关推荐

基于Vue的验证码插件vue2-verify

在我们Web项目开发中,验证码是一种比较常见的区分用户是计算机还是人的手段。主要是为了保证项目的安全。现在Vue开发的项目很多,基本都是前后端分离的。给大家推荐一个基于Vue比较好用的验证码插件vue2-verify。但是大家要注意一点

video.js使用技巧

Video.js初始化有两种方式;一种是在<video>标签里面加上。注意,两者缺一不可。刚开始的时候我觉得后面的值为空对象{},不放也行,导致播放器加载不出来,后来加上来就可以了。

前端最常用的vscode插件集

在前端开发中,使用Visual Studio Code有哪些你常用的插件?推荐几个自己喜欢的,不带链接,自己搜索安装吧。这些都是比较实用、前端必备的插件集

vue开发常用第三方插件总结

这篇文章整理vue开发中常用插件及工具,主要包含: UI组件、开发框架、实用库、服务端SSR、辅助工具、应用实例、Demo示例等,分享给大家,希望对大家有所帮助

浏览器插件_常用谷歌浏览器插件推荐

常用谷谷歌浏览器确实没有其它国产软件的内置功能丰富。但是 Google 浏览器的的优点恰恰就体现在拥有超简约的界面,以及支持众多强大好用的扩展程序,用户能够按照自己的喜好去个性化定制浏览器。今天我就给大家介绍几款自己常用的插件。

对于前端开发,整理推荐好用的chrome插件或应用

向web前端开发者整理提供的chrome插件或应用:比如Postman、JSON Viewer、Page Ruler 、ChromeADB 等等

sublime安装插件

安装Sublime text 2插件很方便,可以直接下载安装包解压缩到Packages目录,也可以安装package control组件,然后直接在线安装

vue项目中使用新手引导功能_intro.js

如何在vue项目中使用用intro.js新手引导功能呢?这里需要使用到vue-introjs插件,vue-introjs是在Vue中绑定intro.js所使用的。在使用vue-introjs前,需要先安装intro.js

使用原生js开发插件的实现方法

作为前端开发,我们都习惯使用一些开源的插件例如jquery工具库,那么如何使用原生js来开发封装一个自己的插件呢?接下来就看一下怎么去开发一个自己的js插件,先上代码

vue项目中vscode格式化配置和eslint配置冲突

使用vscode开发vue项目的时候,从远端拉下一个新的项目后,安装完依赖后跑起项目时,发现直接报了一堆语法错误:包括换行、空格、单双引号、分号等各种格式问题

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!