林秀栋的技术博客

AST

文章来源

AST也就是抽象语法树。简单来说就是把程序用树状形式展现。

每种语言(HTML,CSS,JS等)都有自己的AST,而且还有多种AST解析器。

回归JS本身,常见的AST解析器有:

不同解析器解析出来的AST有些许差异,但本质上是一样的。

本文将基于@babel/parser来进行示例和讲解

import ajax from 'axios'

// 转换后的AST结构如下:
{
	"type": "ImportDeclaration",
	"start": 0,
	"end": 24,
	"loc": {
		"start": {
			"line": 1,
			"column": 0
		},
		"end": {
			"line": 1,
			"column": 24
		}
	},
	"specifiers": [
		{
			"type": "ImportDefaultSpecifier",
			"start": 7,
			"end": 11,
			"loc": {
				"start": {
					"line": 1,
					"column": 7
				},
				"end": {
					"line": 1,
					"column": 11
				}
			},
			"local": {
				"type": "Identifier",
				"start": 7,
				"end": 11,
				"loc": {
					"start": {
						"line": 1,
						"column": 7
					},
					"end": {
						"line": 1,
						"column": 11
					},
					"identifierName": "ajax"
				},
				"name": "ajax"
			}
		}
	],
	"importKind": "value",
	"source": {
		"type": "StringLiteral",
		"start": 17,
		"end": 24,
		"loc": {
			"start": {
				"line": 1,
				"column": 17
			},
			"end": {
				"line": 1,
				"column": 24
			}
		},
		"extra": {
			"rawValue": "axios",
			"raw": "'axios'"
		},
		"value": "axios"
	}
}

ImportDeclaration

语句的类型,表明是一个import的声明。

常见的有:

既然是一个引入表达式,自然分左右两部分,左边的是specifiers,右边的是source

specifiers

specifiers节点会有一个列表来保存specifier

如果左边只声明了一个变量,那么会给一个ImportDefaultSpecifier

如果左边是多个声明,就会是一个ImportSpecifier列表

什么叫左边有多个声明?看下面的示例

import {a,b,c} from 'x'

变量的声明要保持唯一性

而Identifier就是鼓捣这个事情的

source

source包含一个字符串节点StringLiteral,对应了引用资源所在位置。示例中就是axios

AST是如何转换出来的呢?

以babel为例子:

const parser = require('@babel/parser')
let codeString = `
	import ajax from 'axios'
`;

let file = parser.parse(codeString,{
	sourceType: "module"
})
console.dir(file.program.body)

在node里执行一下,就能打印出AST

应用场景以及实战

实际上,我们在项目中,AST技术随处可见

为了更好的理解AST,我们定义一个场景,然后实战一下。

场景:把import转换成require,类似于babel的转换

目标:通过AST转换,把语句

import ajax from ‘axios’

转为

var ajax = require(‘axios’)

要达到这个效果,首先我们要写一个babel-plugin。先上代码

// babelPlugin.js
const t = require('@babel/types');
module.exports = function babelPlugin(babel) {
  function RequireTranslator(path){
    var node = path.node
    var specifiers = node.specifiers
    //获取变量名称
    var varName = specifiers[0].local.name;
    //获取资源地址
    var source = t.StringLiteral(path.node.source.value)
    var local = t.identifier(varName)
    var callee = t.identifier('require')
    var varExpression = t.callExpression(callee,[source])
    var declarator = t.variableDeclarator(local, varExpression)
    //创建新节点
    var newNode = t.variableDeclaration("var", [declarator])
    //节点替换
    path.replaceWith(newNode)
  }

  return {
    visitor: {
      ImportDeclaration(path) {
        RequireTranslator.call(this,path)      
      }
    }
  };
};

// 测试代码:
const babel = require('@babel/core');
const babelPlugin = require('./babelPlugin')

let codeString = `
	import ajax from 'axios'
`;
const plugins = [babelPlugin]
const {code} = babel.transform(codeString,{plugins:plugins});
console.dir(code)

// 输出结果:
// 'var ajax = require("axios");'