在前端培训学习中每个编程语言都有本人的 AST,理解 AST 并能进行一些开发,会给咱们的我的项目开发提供很大的便当。上面就带大家一探到底:
通过本文能理解到什么
- JS AST 构造和属性
- babel 插件开发
JS AST 简介
AST 也就是形象语法树。简略来说就是把程序用树状模式展示。
每种语言(HTML,CSS,JS 等)都有本人的 AST,而且还有多种 AST 解析器。
回归 JS 自身,常见的 AST 解析器有:
• acorn
• @babel/parser
• Typescript
• Uglify-js
• 等等
不同解析器解析进去的 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 的申明。
常见的有:
- VariableDeclaration:var x = ‘init’
- FunctionDeclaration:function func(){}
- ExportNamedDeclaration:export function exp(){}
- IfStatement:if(1>0){}
- WhileStatement:while(true){}
- ForStatement:for(;;){}
- 不一一列举
既然是一个引入表达式,天然分左右两局部,右边的是 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 技术随处可见
• Babel 对 es6 语法的转换
• Webpack 对依赖的收集
• Uglify-js 对代码的压缩
• 组件库的按需加载 babel-plugin
• 等等
为了更好的了解 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,)
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”);
babel-plugin
在 babel 的官网有开发文档,这里只是简略的形容一下留神要点:
• 插件要求返回一个 visitor 对象。
• 能够拦挡所有的节点,函数名称就是节点类型,入参是 path,能够通过 path.node 来获取以后节点
• @babel/types 提供了大量节点操作的 API,同样能够在官网看的具体的阐明
transform
这里的代码大家是不是看着很相熟。没错,就是.babelrc 里的配置。咱们开发的插件,配置到.babelrc 的 plugins 里,就能够全局运行了。
作者:cd2001cjm