文章:https://juejin.im/post/5a9315e46fb9a0633a711f25
https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md
- 你了解過Babel嗎?
了解過抽象語法樹,又稱AST,有學習過,也寫過一個基於AST的乞丐版模板引擎,先是詞法解析token,然后生產抽象語法樹,然后更改抽象語法樹,當然這是插件做的事情,最后根據新的AST生成代碼。
- 寫過Babel插件嗎
沒有,只是看過相關文檔
- 如果讓你寫一個插件,你能寫的出來嗎?
應該可以吧...
遂卒....
開玩笑的,既然提到了,又沒回答上來什么,哎喲我這暴脾氣,一想到今晚就睡不着,連夜把它擼了。
那么我們來從零寫個插件吧。
寫一個預計算簡單表達式的插件
預覽
Before:
const result = 1 + 2 + 3 + 4 + 5;
After:
const result = 15;
以上的例子可能大家不會經常遇到,因為傻x才會這么寫,但是有可能你會這么寫
setTimeout(function(){ // do something }, 1000 * 2) // 插件要做的事,就是把 1000 * 2 替換成 2000
前提條件
開工
再寫代碼之前,你需要明白Babel它的原理,簡單點說: Babel解析成AST,然后插件更改AST,最后由Babel輸出代碼
那么Babel的插件模塊需要你暴露一個function,function內返回visitor
module.export = function(babel){ return { visitor:{ } } }
visitor是對各類型的AST節點做處理的地方,那么我們怎么知道Babel生成了的AST有哪些節點呢?
很簡單,你可以把Babel轉換的結果打印出來,或者這里有傳送門: AST explorer

這里我們看到 const result = 1 + 2
中的1 + 1
是一個BinaryExpression
節點,那么在visitor中,我們就處理這個節點
var babel = require('babel-core'); var t = require('babel-types'); const visitor = { BinaryExpression(path) { const node = path.node; let result; // 判斷表達式兩邊,是否都是數字 if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) { // 根據不同的操作符作運算 switch (node.operator) { case "+": result = node.left.value + node.right.value; break case "-": result = node.left.value - node.right.value; break; case "*": result = node.left.value * node.right.value; break; case "/": result = node.left.value / node.right.value; break; case "**": let i = right; while (--i) { result = result || node.left.value; result = result - node.left.value; } break; default: } } // 如果上面的運算有結果的話 if (result !== undefined) { // 把表達式節點替換成number字面量 path.replaceWith(t.numericLiteral(result)); } } }; module.exports = function (babel) { return { visitor }; }
插件寫好了,我們運行下插件試試
const babel = require("babel-core"); const result = babel.transform("const result = 1 + 2;",{ plugins:[ require("./index") ] }); console.log(result.code); // const result = 3;
與預期一致,那么轉換 const result = 1 + 2 + 3 + 4 + 5;
呢?
結果是: const result = 3 + 3 + 4 + 5;
這就奇怪了,為什么只計算了1 + 2
之后,就沒有繼續往下運算了嗎?
我們看一下這個表達式的AST樹

你會發現Babel解析成表達式里面再嵌套表達式。
表達式( 表達式( 表達式( 表達式(1 + 2) + 3) + 4) + 5)
而我們的判斷條件並不符合所有的,只符合1 + 2
// 判斷表達式兩邊,是否都是數字 if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {}
那么我們得改一改
第一次計算1 + 2
之后,我們會得到這樣的表達式
表達式( 表達式( 表達式(3+ 3) + 4) + 5)
其中 3 + 3
又符合了我們的條件, 我們通過向上遞歸的方式遍歷父級節點
// 如果上面的運算有結果的話 if (result !== undefined) { // 把表達式節點替換成number字面量 path.replaceWith(t.numericLiteral(result)); let parentPath = path.parentPath; // 向上遍歷父級節點 parentPath && visitor.BinaryExpression.call(this, parentPath); }
到這里,我們就得出了結果 const result = 15;
那么其他運算呢:
const result = 100 + 10 - 50
>>> const result = 60;
const result = (100 / 2) + 50
>>> const result = 100;
const result = (((100 / 2) + 50 * 2) / 50) ** 2
>>> const result = 9;
完結
到這里,已經向你大概的講解了,如果編寫一個Babel插件,再也不怕面試官問我答不出什么了哈...
你以為這就完了嗎?
並沒有
如果轉換這樣呢: const result = 0.1 + 0.2;
預期肯定是0.3
, 但是實際上,Javascript有浮點計算誤差,得出的結果是0.30000000000000004
那是不是這個插件就沒卵用?
這就需要你去矯正浮點運算誤差了,可以使用Big.js;
比如: result = node.left.value + node.right.value;
改成 result = +new Big(node.left.value).plus(node.right.value);
你以為完了嗎? 這個插件還可以做很多
比如: Math.PI * 2
>>> 6.283185307179586
比如: Math.pow(2,2)
>>> 4
...
...
最后上項目地址: github.com/axetroy/bab…
作者:Axetroy
鏈接:https://juejin.im/post/5a9315e46fb9a0633a711f25
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。