[譯]理解AST構建Babel插件

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

Babel 主要的處理過程包括三部分: express

Babel 轉換

Parse

Babylon 解析和理解Javascript代碼npm

Transform

babel-traverse分析和修改AST編程

Generate

babel-generator將AST樹轉換回正常的代碼json

AST抽象語法樹

理解AST是咱們接下去內容的基礎。 Javascript語言是由一串字符串生成的,每個都帶有着一些可視的語義信息。這對咱們來講都頗有用,由於它容許咱們使用匹配字符 ([], {}, ()), 成對的字符("", ''),以及縮進,讓咱們更好的理解程序。 而後這對計算機來講是無心義的。對他們來講,每個字符在內存中只是一個數值,他們不能使用它們來問高水平的問題像「有多少變量在這個聲明?相反,咱們須要妥協,找到一個方法來把代碼變成可編程的和計算機能夠理解的東西。數組

形以下面的代碼

var a =3;
a + 5
複製代碼

解析獲得的AST樹

全部的AST起始於一個Program的根節點,該節點包含了程序最頂級的表達。在這個例子中,咱們只有兩個:

  1. 一個VariableDeclaration用來賦值給Identifier的a標識符一個NumericLiteral數值3
  2. ExpressionStatement由一個BinaryExpression組成,由Identifier的「a」標識符和操做符「+」,以及數值5

儘管它們由簡單的塊組成,ast的大小意味着它們至關複雜,特別是對於重要的項目。相比於直接本身去理解AST,咱們可使用astexplorer.net, 網站容許咱們在左邊輸入Javascript代碼,右邊會輸出AST。咱們將使用這個工具來理解和實驗代碼。

爲了Babel的穩定性,請選擇使用"babylon6"做爲一個解釋器。

Setup

確保你是使用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);
});

複製代碼

Arrays數組

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

接下來看看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的代碼很相似,不一樣的是咱們須要作點更復雜的來獲得屬性和值。

Assignment

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]
            )
        );
    }
}
複製代碼

Membership

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/

相關文章
相關標籤/搜索