完整專題:http://www.codefrom.com/p/Babel.jsjavascript
上一篇已經介紹了編寫babel.js插件所須要瞭解的基礎知識,這篇咱們就開始編寫babel.js插件了。java
第一篇傳送門: Babel.js插件開發之一 - Babel與ASTnode
新建一個新的ES6項目,建立目錄結構以下的項目:jquery
YourProject/ |- src/ | |- index.es6 |- js/ | |- app.js
進入到 YourProject
並安裝babel開發模塊 babel-core
git
$ cd path/to/YourProject/ $ npm install babel-core
以後目錄結構以下:es6
YourProject/ |- src/ |- js/ |- node_modules/ | |- babel-core/ | |- ... |- app.js
新建插件目錄github
cd node_modules/ mkdir babel-plugin-test
而且新建目錄下的nodejs模塊文件,以後目錄結構以下:express
fileYourProject/ |- src/ |- js/ |- node_modules/ | |- babel-core/ | |- babel-plugin-test/ | |- index.js | |- package.json |- app.js
接下來咱們就能夠在 index.js
中編寫插件了。npm
因爲AST中節點類型衆多,我在這裏就講如何經過如上文檔中的個別經常使用的類型進行轉換,其餘的都是相似的。json
PS: 最近Babel.js更新了5.6+的API,支持用ES6編寫,也換了新的轉換器接口= = 可素他們本身官方的栗子都跑不起來= =,放棄,以後弄明白再換上新藉口的版本,如今依然按照可用的例子進行講解。
首先 建立一個入口: 在新建立的 index.js
中添加:
javascript'use strict'; module.exports = function(babel) { var t = babel.types; // AST模塊 var imports = {}; // 若是你有本身的模塊組織方式,用這裏把模塊名和路徑記錄下來。 var moduleRoot = ''; // 你其餘的自定義變量 // module_name寫你的插件名稱,但並不在調用時使用 return new babel.Transformer('module_name', { // 這裏寫你的轉換邏輯 [邏輯區域] }); };
在AST中,咱們能夠把整個程序當作一個 Program
例如
var a = 42; var b = 5; var c = a + b;
其AST樹長這樣:(圖 1)
program
是它的根節點。
因而咱們能夠在上面的邏輯區域添加如下代碼:
Program: function(node) { },
除了 Program
經常使用的還有:
Class
(對類的處理方法)NewExpression
(對new新建對象表達式的處理)CallExpression
(對調用函數表達式的處理)MethodDefinition
(對類成員方法的處理)...
(等等)它們都有三個傳入參數: node
、 parent
、scope
:
注: 你能夠經過調試查看他們的具體構造。
如字面意思,他們分別表明了: 節點數據、 父節點、羣數據。其中,節點數據 node
是操做一條語句主要的參數~。
節點數據就是該節點的屬性以及內容,其具體的數據格式能夠看我在第一篇相關概念中最後提到的兩篇文檔:
ES5: https://github.com/estree/estree/blob/master/spec.md - 文檔 1
ES6: https://github.com/estree/estree/blob/master/es6.md - 文檔 2
例如,上面圖1中的那顆樹的 Program
的節點數據,文檔 1中相關描述以下:
Program
interface Program <: Node { type: "Program"; body: [ Statement ]; }
咱們能夠看到其 node
參數是一個 Node
類型的數據,包含了兩個屬性:公共屬性 type
表明節點類別,body
表明其內容,這裏是一個子節點的列表,列表中有三個VariableDeclaration
表明程序中的三條語句,其類型也是 Node
。
假設咱們定義了一種模塊化的方法(相似AMD的requirejs),咱們將整個程序包裹在一個 test.defineModule(function(){/* block */})
方法中。
那咱們能夠這樣構建Program:
'use strict'; module.exports = function(babel) { var t = babel.types; // AST模塊 return new babel.Transformer('module_name', { // 這裏寫你的轉換邏輯 [邏輯區域] Program: function(node) { var moduleFunction = t.functionExpression( // [1] t.identifier(''), // [2] [], // [3] t.blockStatement(node.body) // [4] ); var moduleDefine = t.callExpression( // [5] t.identifier('test.defineModule'), // [6] [moduleFunction] // [7] ); var moduleExpression = t.expressionStatement(moduleDefine); // [8] node.body = [moduleExpression]; // [9] return node; // [10] } }); };
這裏你只定義了Program的轉換機制,所以其餘的轉換仍是會按照默認的方式進行轉換。
按照這種機制,上面的AST樹的示例程序就被轉換成下面這樣了:
"use strict"; test.defineModule(function () { var a = 42; var b = 5; var c = a + b; });
下面咱們來逐行分析一下(以邏輯的順序):
[5] 新建一個函數調用
moduleDefine
[6] 這個被調用函數的名字叫作 'test.defineModule' 即:test.defineModule()
[8] 整個函數調用是一個完整的表達式moduleExpression
,即:test.defineModule();
接下來咱們須要向這個函數調用中填入參數列表
[7] 這個參數列表中有一個參數moduleFunction
1moduleFunction
是一個函數
[2] 這個函數的名稱是 '',即: 這是一個匿名函數
[3] 這個函數的參數列表爲空
[4] 這個函數的內容塊的內容是本來Program節點的內容
[9] 把原有的Program節點的內容替換成新的
[10] 返回這個改動,固然你是直接在本來的對象實例上改動的,不返回也能夠
一樣你也能夠從新定義 ImportDeclaration
和 ExportDeclaration
,其結構略微與普通節點有所不一樣,例如:
ImportDeclaration: function(node) { node.specifiers.forEach(function(specifier){ if(specifier.type == 'ImportDefaultSpecifier'){ imports[specifier.local.name] = node.source.value; } }); this.dangerouslyRemove(); }, ExportDeclaration: function(){ this.dangerouslyRemove(); }
其做用爲:將export 和import的相關轉換都刪掉,而且將import的值和路徑都記錄下來,能夠在其餘的轉換中用到,又或者直接在ImportDeclaration
中直接對import的變量進行操做,例如:
對
import $ from 'jquery'
咱們但願轉化成
var $ = test.requireModule('jquery');
並將其放入模塊內:
ImportDeclaration: function(node) { var self = this; node.specifiers.forEach(function(specifier){ if(specifier.type == 'ImportDefaultSpecifier'){ //imports[specifier.local.name] = node.source.value; var requireCall = t.callExpression( t.identifier('test.requireModule'), [t.literal(node.source.value)] ); var assignEx = t.assignmentExpression( '=', t.identifier(specifier.local.name), requireCall ); self.insertAfter(t.expressionStatement(assignEx)); } }); this.dangerouslyRemove(); },
將其假如以前的test.defineModule的轉換中,則咱們發現
import $ from 'jquery' var a = 42; var b = 5; var c = a + b;
被轉換爲了:
'use strict'; test.defineModule(function () { $ = test.requireModule('jquery'); var a = 42; var b = 5; var c = a + b; });
ImportDeclaration
在上述文檔2中的描述爲:
interface ImportDeclaration <: Node { type: "ImportDeclaration"; specifiers: [ ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier ]; source: Literal; }
其 specifiers
列表中的 specifiers 可能有三種類型,在文檔中都有很詳細的描述,這裏就很少說了。
按照這樣的理解,全部的方法均可以經過查看上面的文檔 1和文檔 2的說明進行改動。
看到這裏你已經能夠動手開始嘗試寫一個babel.js插件了。
上述文件目錄結構爲:
YourProject/ |- src/ |- js/ |- node_modules/ | |- babel-core/ | |- babel-plugin-test/ | |- index.js | |- package.json |- app.js
在 src
中編寫es6程序 test.es6
。
YourProject/ |- src/ | |- test.es6 |- js/ |- node_modules/ | |- babel-core/ | |- babel-plugin-test/ | |- index.js | |- package.json |- app.js
到YourProject目錄下。執行
$ babel src/ -d js/ --plugins babel-plugin-test
則在 js
文件夾中就是你轉化好的js文件啦~。
還有第三篇是有關英文文檔的翻譯。