AST抽象语法树和Babel插件

更新日期: 2020-11-08阅读: 3.1k标签: 插件
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://www.fly63.com/article/detial/9828

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

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

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

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

typeahead.js_jquery input 搜索自动补全jQuery插件

jquery.typeahead.js是一款高级的自动补全jQuery插件。该自动补全插件提供超过50个配置选项和回调方法,用于完成自动补全功能,能够完成绝大部分表单自动补全的需求。

js轮播插件_轮播图js代码插件总汇

这篇文章为大家分享图片轮播插件,最全最简单最通用的 幻灯片轮播插件,pc端和移动端都可完美使用,能满足绝大部分网站的轮播需求。js轮播插件包括Swiper、slick、owl carousel2、jssor/slider 、iSlider 等

ios风格的时间选择插件

在上个项目中,客户希望时间选择插件可以是ios风格的那种,但是找了很久,发现并没有用vue的ios风格时间插件,于是自己便自己造了一个轮子.插件依赖于better-scroll和vue

前端最常用的vscode插件集

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

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

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

sublime安装插件

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

BlockUI详细用法_Jquery中ajax加载提示插件blickUI

BlockUI 插件是用于进行AJAX操作时模拟同步传输时锁定浏览器操作。当它被激活时,它会阻止使用者与页面(或页面的一部分)进行交互,直至它被取消。BlockUI以在DOM中添加元素的方法来实现阻止用户与浏览器交互的外观和行为

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

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

点击更多...

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