摘要:接著上一篇文章深入了解一的處理步驟的三個主要處理步驟分別是解析,轉換,生成。模塊是的代碼生成器,它讀取并將其轉換為代碼和源碼映射抽象語法樹抽象語法樹在以上三個神器中都出現過,所以對于編譯器來說至關重要。
接著上一篇文章《深入了解babel(一)》
Babel 的處理步驟Babel 的三個主要處理步驟分別是: 解析(parse),轉換(transform),生成(generate)。對應著babel-core源碼中分別用到的babylon、babel-traverse、babel-generator。
(1)BabylonBabylon 是 Babel 的解析器。最初是 從Acorn項目fork出來的。Acorn非常快,易于使用。
import * as babylon from "babylon"; const code = `function square(n) { return n * n; }`; babylon.parse(code); // Node { // type: "File", // start: 0, // end: 38, // loc: SourceLocation {...}, // program: Node {...}, // comments: [], // tokens: [...] // }(2)babel-traverse
Babel Traverse(遍歷)模塊維護了整棵樹的狀態,并且負責替換、移除和添加節點。我們可以和 Babylon 一起使用來遍歷和更新節點。
import * as babylon from "babylon"; import traverse from "babel-traverse"; const code = `function square(n) { return n * n; }`; const ast = babylon.parse(code); traverse(ast, { enter(path) { if ( path.node.type === "Identifier" && path.node.name === "n" ) { path.node.name = "x"; } } });(3)babel-generator
Babel Generator模塊是 Babel 的代碼生成器,它讀取AST并將其轉換為代碼和源碼映射
import * as babylon from "babylon"; import generate from "babel-generator"; const code = `function square(n) { return n * n; }`; const ast = babylon.parse(code); generate(ast, {}, code); // { // code: "...", // map: "..." // }抽象語法樹(AST)
ast抽象語法樹在以上三個神器中都出現過,所以ast對于編譯器來說至關重要。以下列舉了一些ast的應用:
瀏覽器會把js源碼通過解析器轉為抽象語法樹,再進一步轉化為字節碼或直接生成機器碼
JSLint、JSHint對代碼錯誤或風格的檢查,發現一些潛在的錯誤
IDE的錯誤提示、格式化、高亮、自動補全等等
UglifyJS
代碼打包工具webpack、rollup
CoffeeScript、TypeScript、JSX等轉化為原生Javascript
自己動手寫插件presets預設就是關于一系列插件的集合,presets的存在減少了babelrc配置文件的體積,不用看到一大堆的插件數組,并且保證了每個用戶配置的插件清單一模一樣,所以插件對于babel來說至關重要,前端開發者如何開發一個自定義插件決定了今后對代碼編譯的掌控程度,babel插件就像一把手術刀對js源碼進行精準、可靠的改裝。
本人在寫練習寫插件的過程中主要用到了以下兩個方法:
ast explorer
基于babel-core在IDE中編寫代碼
引用babel-core模塊進行編碼方式如下:
const {transform,generate}=require("babel-core"); const myPlugin=require("./myPlugin"); const code = `d = a + b + c`; var es5Code = transform(code, { plugins: [myPlugin] }) console.log(es5Code.code);ast explorer
本人比較青睞的babel插件在線編寫方式,可以實時看到編譯后的結果以及對應的AST部分,結合babel-types可以很快的寫出手術刀式的插件,下面這張圖是ast explorer解析出來的json:
export default function (babel) { const {types:t}=babel; return { name: "可有可無的插件名字", visitor: { VariableDeclaration(path,state){ console.log(path); } }, }; }
每一個插件都要返回帶有visitor字段的對象,而visitor對象中存放你的遍歷方法,本人總結為等價于上面ast explorer截圖中的type屬性(例如:VariableDeclaration),遍歷方法是指插件根據遍歷方法讓ast中的節點走進你寫的遍歷方法函數中。遍歷方法就像js中的addeventlistener,可以重復寫多個監聽函數,所以當多個插件疊合在一起就會出現一些不可預料的事情,這是考驗你插件編寫是否安全、可靠的事情,也是最難的部分。
舉一個最簡單的例子,如何刪除代碼中的所有console?
let a=33; console.log(12121212); var b; console.warn(12121212); aaaa,cccc console.error(12121212); dd=0; let c;
export default function ({types:t}) { return { name: "刪除所有的console", visitor: { CallExpression(path,state){ if(path.get("callee").isMemberExpression()){ if(path.get("callee").get("object").isIdentifier()){ if(path.get("callee").get("object").get("name").node=="console")path.remove() } } } }, }; }
CallExpression遍歷方法也就是console.log(...)對應的AST type屬性,當走進CallExpression函數后,我們可以獲取path和state兩個參數,path包含了當前節點的相關信息,按照前端的思維可以理解為dom節點,可以往上或者往下查找節點,當前節點path包含了很多信息,方便我們編寫插件,而state中包含了插件的options和數據,options就是babelrc中plugins引入插件時,添加的options,在state中可以接收到它。
剛開始寫插件的時候,完全當成dom節點直接獲取節點中的信息是非常危險的(我也是看了babel多個插件后知道的),每往下取一個信息時都要去判斷這個類型是否跟我們的ast樹一樣,這樣就可以去除掉其他的情況,例如其他的CallExpression也走到這個函數中了,但是它可能并沒有callee或者object,代碼執行到這邊就會出錯或者誤傷,嚴謹的控制節點獲取流程將會幫助我們省去很多不必要的麻煩。
代碼中獲取callee節點可以有兩種方式,一種是path.node.callee,還有一種是path.get("callee"),個人比較喜歡后者,因為可以直接調用方法(例如isMemberExpression),否則你就要像這樣去判斷t.isMemberExpression(path.node.callee),不夠優雅。
當我們條件判斷到當前node是console,直接用remove方法就可以刪除ast節點了,編譯后的代碼:
let a=33; var b; aaaa,cccc dd=0; let c;
babel官方已經發布了一個刪除console的插件,可以對比下發現,思路和步驟基本一致,babel官方開發的更加全面,考慮了其他兩個情況。
插件編寫第二站 -- 作用域的影響function a(n){ n*n } let n=1
考慮下如何改寫函數中n變成_n?
export default function ({ types: t }) { let paramsName=""; return { name: "給function中的參數加上下劃線", visitor: { FunctionDeclaration(path) { if(!path.get("params").length||!path.get("params")[0])return; paramsName=path.get("params")[0].get("name").node; path.traverse({ Identifier(path){ if(path.get("name").node==paramsName)path.replaceWith(t.Identifier("_"+paramsName)); } }); }, } }; }
按照第一個例子的思路,我們很容易就可以把n給改成_n,但是這時候fucntion外面的let n=1,也會被改寫,所以我們在FunctionDeclaration方法中調用了path.traverse,把需要遍歷的方法Identifier包裹在其中,這樣就保護了外面代碼的安全,這種方式保證了插件編寫的安全性
插件編寫第三站 -- bindingsconst aaaa=1; const bb=4; function b(){ let aaaa=2; aaaa=3; } aaaa=34;
讓我們來接著做另外一個例子,如何將const改成var,并且對const聲明的值給予只讀保護?
export default function (babel, options) { return { name: "const polyfill", visitor: { VariableDeclaration(path) { if(path.get("kind").node!="const")return; path.node.kind="var"; }, ExpressionStatement(path){ if(!path.get("expression").isAssignmentExpression())return; let nodeleft=path.get("expression").get("left"); if(!nodeleft.isIdentifier())return; if(path.scope.bindings[nodeleft.get("name").node].kind=="const")console.error("Assignment to constant variable"); } }, }; }
VariableDeclaration方法中將const改成了let,ExpressionStatement方法中用來觀察const的變量是否被修改,由于function有自己的作用域,所以aaaa可以被重新聲明和修改,這里用到了bindings屬性,可以查看該節點的變量申明類型,當發現kind為const時才發出error警告,這個例子是對bindings的一次應用。
插件編寫第四站 -- 創建節點當我們替換一個節點或者插入一個節點到容器中,我們需要按照節點的構建規則來創建,下面的例子是將n*n修改成n+100
function square(n) { return n * n; }
先給出答案,代碼如下:
export default function ({types:t}) { return { name: "將n*n修改成n+100", visitor: { BinaryExpression(path){ path.replaceWith(t.binaryExpression("+", path.node.left, t.Identifier("100"))); path.stop(); } }, }; }
現在我們要把BinaryExpression這個type的節點給替換掉,就要按照BinaryExpression節點的規則來創建,可以參考babel-types網站的說明文檔:
我們需要分別構建operator、left、right這三種類型的節點,再查看ast中對這三個節點的描述
OK,left和right都是Identifier類型,而operator是字符串,字符串直接寫入“+”就可以替換掉了,而Identifier類型節點的創建還要查看babel-types給出的文檔:
我們只要給出string類型的name就可以了,所以我們可以成功創建自己的節點了。
總結ast explorer真的是一個很好的網站,并且可以在插件中寫console,可以在控制臺中實時看到console的結果,對我們理解ast節點用很大的幫助,另外以上介紹插件的例子還是太少,插件編寫要注意的遠不止這些方面,但是本人沒時間想出那么多的例子來很好的介紹,所以大家可以直接閱讀這篇文檔來深入了解。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/51435.html
摘要:接著上一篇文章深入了解一的處理步驟的三個主要處理步驟分別是解析,轉換,生成。模塊是的代碼生成器,它讀取并將其轉換為代碼和源碼映射抽象語法樹抽象語法樹在以上三個神器中都出現過,所以對于編譯器來說至關重要。 接著上一篇文章《深入了解babel(一)》 Babel 的處理步驟 Babel 的三個主要處理步驟分別是: 解析(parse),轉換(transform),生成(generate)。對...
摘要:目前羅列的只是的情況。例如,包含了。的執行過程是,首先讀取配置中的條件,根據這些條件從模塊可得出該條件下的所有瀏覽器最低版本號列表,而又為的轉譯插件提供了瀏覽器的最低版本號列表,兩個瀏覽器版本號列表的查詢可得出一個轉譯插件的集合。 babel的定義 Babel 是 JavaScript 編譯器,更確切地說是源碼到源碼的編譯器,通常也叫做轉換編譯器(transpiler)。 babel-...
摘要:目前羅列的只是的情況。例如,包含了。的執行過程是,首先讀取配置中的條件,根據這些條件從模塊可得出該條件下的所有瀏覽器最低版本號列表,而又為的轉譯插件提供了瀏覽器的最低版本號列表,兩個瀏覽器版本號列表的查詢可得出一個轉譯插件的集合。 babel的定義 Babel 是 JavaScript 編譯器,更確切地說是源碼到源碼的編譯器,通常也叫做轉換編譯器(transpiler)。 babel-...
閱讀 3275·2021-09-30 09:47
閱讀 2290·2021-09-10 10:51
閱讀 1889·2021-09-08 09:36
閱讀 2926·2019-08-30 12:56
閱讀 3027·2019-08-30 11:16
閱讀 2622·2019-08-29 16:40
閱讀 2994·2019-08-29 15:25
閱讀 1632·2019-08-29 11:02