原文: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/