2018-09-29

抽象语法树(Abstract Syntax Tree)


webpackLint等很多的工具和库的核心都是通过Abstract Syntax Tree抽象语法树这个概念来实现对代码的检查、分析等操作的。通过了解抽象语法树这个概念,你也可以随手编写类似的工具

2.抽象语法树用途 #

  • 代码语法的检查、代码风格的检查、代码的格式化、代码的高亮、代码错误提示、代码自动补全等等
    • 如JSLint、JSHint对代码错误或风格的检查,发现一些潜在的错误
    • IDE的错误提示、格式化、高亮、自动补全等等
  • 代码混淆压缩
    • UglifyJS2等
  • 优化变更代码,改变代码结构使达到想要的结构
    • 代码打包工具webpack、rollup等等
    • CommonJS、AMD、CMD、UMD等代码规范之间的转化
    • CoffeeScript、TypeScript、JSX等转化为原生Javascript

3.抽象语法树定义 #

这些工具的原理都是通过JavaScript Parser把代码转化为一颗抽象语法树(AST),这颗树定义了代码的结构,通过操纵这颗树,我们可以精准的定位到声明语句、赋值语句、运算语句等等,实现对代码的分析、优化、变更等操作

在计算机科学中,抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。

Javascript的语法是为了给开发者更好的编程而设计的,但是不适合程序的理解。所以需要转化为AST来更适合程序分析,浏览器编译器一般会把源码转化为AST来进行进一步的分析等其他操作。

ast

4.JavaScript Parser [#](#t34.JavaScript Parser)

  • JavaScript Parser,把js源码转化为抽象语法树的解析器。

  • 浏览器会把js源码通过解析器转为抽象语法树,再进一步转化为字节码或直接生成机器码。

  • 一般来说每个js引擎都会有自己的抽象语法树格式,Chrome的v8引擎,firefox的SpiderMonkey引擎等等,MDN提供了详细SpiderMonkey AST format的详细说明,算是业界的标准。

4.1 常用的JavaScript Parser有: [#](#t44.1 常用的JavaScript Parser有:)

  • esprima
  • traceur
  • acorn
  • shift

4.2 esprima [#](#t54.2 esprima)

  • 通过 esprima 把源码转化为AST

  • 通过 estraverse 遍历并更新AST

  • 通过 escodegen 将AST重新生成源码

  • astexplorer

    let esprima = require('esprima');
    var estraverse = require('estraverse');
    var escodegen = require("escodegen");
    let code = 'function ast(){}';
    let ast = esprima.parse(code);
    console.log(ast);
    estraverse.traverse(ast,{
       enter(node){
           node.name += '_ext';
       },
       leave(node){
        // console.log(node.type);
       }
    });
    let generated = escodegen.generate(ast);
    console.log(generated);
    

5. 转换箭头函数 [#](#t65. 转换箭头函数)

  • 访问者模式Visitor 对于某个对象或者一组对象,不同的访问者,产生的结果不同,执行操作也不同

  • babel-core

  • babel-types

  • Babel 插件手册

    let babel = require('babel-core');
    let types = require('babel-types');
    const code = `const sum = (a,b)=>a+b`;
    // babel-eslint
    
    const visitor = {
        ArrowFunctionExpression:{
            enter(path) {
                let node = path.node;
                let expression = node.body;
                let params = node.params;
                let returnStatement = types.returnStatement(expression);
                let block = types.blockStatement([
                    returnStatement
                ]);
                let func = types.functionExpression(null,params, block,false, false);
                path.replaceWith(func);
            }
        }
    }
    const result = babel.transform(code,{
        plugins:[
            {visitor}
        ]
    });
    console.log(result.code);
    

6. babel插件 [#](#t76. babel插件)

预计算简单表达式的插件

const result = 1 + 2;
const result = 3;
let babel = require('babel-core');
let types = require('babel-types');
let visitor = {
  BinaryExpression(path){
      let node = path.node;
      if(!isNaN(node.left.value) && !isNaN(node.right.value)){
          let result = eval(node.left.value + node.operator + node.right.value);
          path.replaceWith(types.numericLiteral(result));
          if(path.parentPath&& path.parentPath.node.type == 'BinaryExpression'){
              visitor.BinaryExpression.call(null,path.parentPath);
          }
      }
  }
}
module.exports = function(babel){
    return {
        visitor
    }
}
const babel = require('babel-core');
const result = babel.transform('const sum = 1+2+3',{
    plugins:[
        require('./plugin')
    ]
});
console.log(result.code);

7. webpack babel插件 [#](#t87. webpack babel插件)

7.1 实现按需加载 [#](#t97.1 实现按需加载)

import { flatten,concat } from "lodash"

转换为

import flatten from "lodash/flatten";
import concat from "lodash/flatten";

7.2 babel配置 [#](#t107.2 babel配置)

  • transform-runtime Babel 默认只转换新的 JavaScript 语法,而不转换新的 API。例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译,启用插件 babel-plugin-transform-runtime 后,Babel 就会使用 babel-runtime 下的工具函数

    {
            "presets":["react","stage-0","env"],
            "plugins":[
                ["extract", {"library":"lodash"}],
                ["transform-runtime", {}]
            ]
    }
    

编译顺序为首先plugins从左往右,然后presets从右往左

7.3 babel插件 [#](#t117.3 babel插件)

let babel = require('babel-core');
let types = require('babel-types');
const visitor = {
    ImportDeclaration:{
        enter(path,ref={opts:{}}){
            const specifiers = path.node.specifiers;
            const source = path.node.source;
            if(ref.opts.library == source.value && !types.isImportDefaultSpecifier(specifiers[0])){
                const declarations = specifiers.map((specifier,index)=>{
                    return types.ImportDeclaration(
                        [types.importDefaultSpecifier(specifier.local)],
                        types.stringLiteral(`${source.value}/${specifier.local.name}`)
                    )
                });
                path.replaceWithMultiple(declarations);
            }
        }
    }
}
module.exports = function(babel){
    return {
        visitor
    }
}

备注 #

Gitalking ...

Markdown is supported

Be the first guy leaving a comment!