原文:https://www.sitepoint.com/understanding-asts-building-babel-plugin/javascript
本文只选择了重要部分进行翻译java
咱们设计了一个插件来将正常的object和array转换为持久的数据结构Morinode
咱们想写的code是这样:git
var foo = { a: 1 };
var baz = foo.a = 2;
foo.a === 1;
baz.a === 2;
复制代码
想要转换获得的是:github
var foo = mori.hashMap('a', 1);
var baz = mori.assoc(foo, 'a', 2);
mori.get(foo, 'a') === 1;
mori.get(baz, 'a') === 2;
复制代码
Babel 主要的处理过程包括三部分: express
Babylon 解析和理解Javascript代码npm
babel-traverse分析和修改AST编程
babel-generator将AST树转换回正常的代码json
理解AST是咱们接下去内容的基础。 Javascript语言是由一串字符串生成的,每个都带有着一些可视的语义信息。这对咱们来讲都颇有用,由于它容许咱们使用匹配字符 ([], {}, ()), 成对的字符("", ''),以及缩进,让咱们更好的理解程序。 而后这对计算机来讲是无心义的。对他们来讲,每个字符在内存中只是一个数值,他们不能使用它们来问高水平的问题像“有多少变量在这个声明?相反,咱们须要妥协,找到一个方法来把代码变成可编程的和计算机能够理解的东西。数组
形以下面的代码
var a =3;
a + 5
复制代码
解析获得的AST树
全部的AST起始于一个Program的根节点,该节点包含了程序最顶级的表达。在这个例子中,咱们只有两个:
尽管它们由简单的块组成,ast的大小意味着它们至关复杂,特别是对于重要的项目。相比于直接本身去理解AST,咱们可使用astexplorer.net, 网站容许咱们在左边输入Javascript代码,右边会输出AST。咱们将使用这个工具来理解和实验代码。
为了Babel的稳定性,请选择使用"babylon6"做为一个解释器。
确保你是使用node和npm安装。建立一个工程文件,建立一个package.json文件而且安装以下依赖
mkdir moriscript && cd moriscript
npm init -y
npm install --save-dev babel-core
复制代码
咱们建立一个文件插件而且导出一个默认函数
// moriscript.js
module.exports = function(babel) {
var t = babel.types;
return {
visitor: {
}
};
};
复制代码
babel 提供了一个visitor模式,能够用于编写各类插件,插入删除等操做来产生一个新的AST树
// run.js
var fs = require('fs');
var babel = require('babel-core');
var moriscript = require('./moriscript');
// read the filename from the command line arguments
var fileName = process.argv[2];
// read the code from this file
fs.readFile(fileName, function(err, data) {
if(err) throw err;
// convert from a buffer to a string
var src = data.toString();
// use our plugin to transform the source
var out = babel.transform(src, {
plugins: [moriscript]
});
// print the generated code to screen
console.log(out.code);
});
复制代码
MoriScript首要的任务是转换Object和Array为它们对应的Mori部分:HashMapsh和Vector。咱们首先要转换的是Array。
var bar = [1, 2, 3];
// should becom
var bar = mori.vector(1, 2, 3);
复制代码
将上述代码复制到astexplorer,而且高亮数组[1,2,3]来看对应的AST节点。
为了可读性咱们只选择了数据区域的AST节点:
// [1, 2, 3]
{
"type": "ArrayExpression",
"elements":[
{
"type": "NumericLiteral",
"value": 1
},
{
"type": "NumericLiteral",
"value": 2
},{
"type": "NumericLiteral",
"value": 3
}
]
}
复制代码
而mori.vector(1,2,3)的AST以下:
{
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "mori"
},
"property":{
"type": "Identifier",
"name": "vector"
}
},
"arguments":[
{
"type": "NumericLiteral",
"value": 1
},
{
"type": "NumericLiteral",
"value": 2
},
{
"type": "NumericLiteral",
"value": 3
}
]
}
复制代码
上述节点的可视化效果,能够清楚地看到两棵树之间的区别
如今咱们能够很清楚地看到,咱们须要更换顶级表达式,但咱们能共享在两棵树之间的数字表达。
让咱们开始添加第一个ArrayExpression到咱们的visitor中:
module.exports = function(babel) {
var t = babel.types;
return {
visitor: {
ArrayExpression: function(path) {
}
}
};
};
复制代码
咱们能够从babel-types文档中找到对应的表达式类型,在这个例子咱们要去替换ArrayExpression为一个CallExpression,咱们能够生成t.callExpression(callee, arguments)。而后须要的是利用t.memberExpression(object, property)来调用MemberExpression。
ArrayExpression: function(path){
path.replaceWith(
t.callExpression(
t.memberExpression(t.identifier('mori'), t.identifier('vector')),
path.node.elements
)
)
}
复制代码
接下来看看Object
var foo = { bar: 1};
var foo =mori.hashMap('bar',1);
复制代码
object语法跟ArrayExpression有类似的结构
高亮mori.hashMap('bar', 1)获得:
{
"type": "ObjectExpression",
"properties": [
{
"type": "ObjectProperty",
"key": {
"type": "Identifier",
"name": "bar"
},
"value": {
"type": "NumericLiteral",
"value": 1
}
}
]
}
复制代码
可视化获得的AST树:
ObjectExpression: function(path){
var props = [];
path.node.properties.forEach(function(prop){
props.push(
t.stringLiteral(prop.key.name),
prop.value
);
});
path.replaceWith(
t.callExpression(
t.memberExpression(t.identifier('mori'), t.identifier('hasMap')),
props
)
)
}
复制代码
相似的咱们有一个CallExpression包围着一个MemberExpression。跟Array的代码很相似,不一样的是咱们须要作点更复杂的来获得属性和值。
foo.bar = 3;
mori.assoc(foo, 'bar', 3);
复制代码
AssignmentExpression: function(path){
var lhs = path.node.left;
var rhs = path.node.right;
if(t.isMemberExpression(lhs)){
if(t.isIdentifier(lhs.property)){
lhs.property = t.stringLiteral(lhs.property.name);
}
path.replaceWith(
t.callExpression(
t.memberExpression(),
[lhs.object, lhs.property, rhs]
)
);
}
}
复制代码
foo.bar;
mori.get(foo, 'bar');
复制代码
MemberExpression: function(path){
if(t.isAssignmentExpression(path.parent)) return;
if(t.isIdentifier(path.node.property)){
path.node.property = t.stringLiteral(path.node.property.name)
}
path.replaceWith(
t.callExpression(
t.memberExpression(),
[path.node.object, path.node.property]
)
)
}
复制代码
存在的一个问题是得过的mori.get又会是一个MemberExpression,致使循环递归
// set a flag to recognize express has been tranverse
MemberExpression: function(path){
if(path.node.isClean) return ;
...
}
复制代码
Babel 只能转换Javascript 也就是Babel parser理解的
AST 在线 parse: https://astexplorer.net/