您好,登錄后才能下訂單哦!
babel是一個轉碼器,目前開發react、vue項目都要使用到它。它可以把es6+的語法轉換為es5,也可以轉換JSX等語法。
我們在項目中都是通過配置插件和預設(多個插件的集合)來轉換特定代碼,例如env、stage-0等。
實際上babel可以通過自定義插件的方式實現任何代碼的轉換,接下來我們通過一個“把es6的 class
轉換為es5”的例子來了解一下babel。
內容如下:
webpack環境配置
大家應該都配置過babel-core這個loader,它的作用是提供babel的核心Api,實際上我們的代碼轉換都是通過插件來實現的。
接下來我們不用第三方的插件,自己實現一個es6類轉換插件。先執行以下幾步初始化一個項目:
如果我們的插件名字想叫transform-class,需要在webpack配置中做如下配置:
接下來我們在node_modules中新建一個babel-plugin-transform-class的文件夾來寫插件的邏輯(如果是真實項目,你需要編寫這個插件并發布到npm倉庫),如下圖:
紅色區域是我新建的文件夾,它上面的是一個標準的插件的項目結構,為了方便我只寫了核心的index.js文件。
如何編寫bable插件
babel插件其實是通過AST(抽象語法樹)實現的。
babel幫助我們把js代碼轉換為AST,然后允許我們修改,最后再把它轉換成js代碼。
那么就涉及到兩個問題:js代碼和AST之間的映射關系是什么?如何替換或者新增AST?
好,先介紹一個工具:astexplorer.net:
這個工具可以把一段代碼轉換為AST:
如圖,我們寫了一個es6的類,然后網頁的右邊幫我們生成了一個AST,其實就是把每一行代碼變成了一個對象,這樣我們就實現了一個映射。
再介紹一個文檔: babel-types :
這是創建AST節點的api文檔。
比如,我們想創建一個類,先到astexplorer.net中轉換,發現類對應的AST類型是 ClassDeclaration
。好,我們去文檔中搜索,發現調用下面的api就可以了:
創建其他語句也是一樣的道理,有了上面這兩個東西,我們可以做任何轉換了。
下面我們開始真正編寫一個插件,分為以下幾步:
class
對應的AST節點為 ClassDeclaration
ClassDeclaration
,意思是我要捕獲js代碼中所有 ClassDeclaration
節點module.exports = function ({ types: t }) { return { visitor: { ClassDeclaration(path) { //在這里完成轉換 } } }; }
代碼中有兩個參數,第一個 {types:t}
東西是從參數中解構出變量t,它其實就是babel-types文檔中的t(下圖紅框),它是用來創建節點的:
第二個參數 path
,它是捕獲到的節點對應的信息,我們可以通過 path.node
獲得這個節點的AST,在這個基礎上進行修改就能完成了我們的目標。
如何把es6的class轉換為es5的類
上面都是預備工作,真正的邏輯從現在才開始,我們先考慮兩個問題:
我們要做如下轉換,首先把es6的類,轉換為es5的類寫法(也就是普通函數),我們觀察到,很多代碼是可以復用的,包括函數名字、函數內部的代碼塊等。
如果不定義class中的 constructor
方法,JavaScript引擎會自動為它添加一個空的 constructor()
方法,這需要我們做兼容處理。
接下來我們開始寫代碼,思路是:
constructor
節點(上文提到,class中有可能沒有定義constructor)constructor
,如果是,通過取到數據創建一個普通函數節點,并更新默認 constructor
節點constructor
的節點,通過數據創建 prototype
類型的函數,并放到 es5Fns
中constructor
節點也放到 es5Fns
中replaceWithMultiple
這個API更新ASTmodule.exports = function ({ types: t }) { return { visitor: { ClassDeclaration(path) { //拿到老的AST節點 let node = path.node let className = node.id.name let classInner = node.body.body //創建一個數組用來成盛放新生成AST let es5Fns = [] //初始化默認的constructor節點 let newConstructorId = t.identifier(className) let constructorFn = t.functionDeclaration(newConstructorId, [t.identifier('')], t.blockStatement([]), false, false) //循環老節點的AST對象 for (let i = 0; i < classInner.length; i++) { let item = classInner[i] //判斷函數的類型是不是constructor if (item.kind == 'constructor') { let constructorParams = item.params.length ? item.params[0].name : [] let newConstructorParams = t.identifier(constructorParams) let constructorBody = classInner[i].body constructorFn = t.functionDeclaration(newConstructorId, [newConstructorParams], constructorBody, false, false) } //處理其余不是constructor的節點 else { let protoTypeObj = t.memberExpression(t.identifier(className), t.identifier('prototype'), false) let left = t.memberExpression(protoTypeObj, t.identifier(item.key.name), false) //定義等號右邊 let prototypeParams = classInner[i].params.length ? classInner[i].params[i].name : [] let newPrototypeParams = t.identifier(prototypeParams) let prototypeBody = classInner[i].body let right = t.functionExpression(null, [newPrototypeParams], prototypeBody, false, false) let protoTypeExpression = t.assignmentExpression("=", left, right) es5Fns.push(protoTypeExpression) } } //循環結束,把constructor節點也放到es5Fns中 es5Fns.push(constructorFn) //判斷es5Fns的長度是否大于1 if (es5Fns.length > 1) { path.replaceWithMultiple(es5Fns) } else { path.replaceWith(constructorFn) } } } }; }
優化繼承
其實,類還涉及到繼承,思路也不復雜,就是判斷AST中沒有 superClass
屬性,如果有的話,我們需要多添加一行代碼 Bird.prototype = Object.create(Parent)
,當然別忘了處理 super
關鍵字。
打包后代碼
運行 npm start
打包后,我們看到打包后的文件里 class
語法已經成功轉換為一個個的es5函數。
結尾
現在一個類轉換器就寫完了,希望能對大家了解babel有一點幫助。也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。