這次帶給大家babel轉換es6方法實現,babel轉換es6的注意事項有哪些,下面就是實戰案例,一起來看一下。
我們在專案中都是透過配置插件和預設(多個插件的集合)來轉換特定程式碼,例如env、stage-0等。
實際上babel可以透過自訂外掛程式的方式實現任何程式碼的轉換,接下來我們透過一個「把es6的 class
轉換為es5」的範例來了解babel。
內容如下:
webpack環境設定
大家應該都設定過babel-core這個loader,它的作用是提供babel的核心Api,實際上我們的程式碼轉換都是透過插件來實現的。
接下來我們不用第三方的插件,自己實作一個es6類別轉換插件。先執行以下幾個步驟初始化一個專案:
npm install webpack webpack-cli babel-core -D
新一個webpack.config.js
設定webpack.config.js
如果我們的外掛程式名稱想叫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就可以了:
#創建其他語句也是一樣的道理,有了上面這兩個東西,我們可以做任何轉換了。
下面我們開始真正寫一個插件,分成以下幾步:
#在index.js中export一個函數
函數中傳回一個對象,物件有一個visitor參數(必須叫visitor)
#透過astexplorer.net查詢出class
對應的AST節點為ClassDeclaration
在vistor中設定一個擷取函數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()
方法,这需要我们做兼容处理。
接下来我们开始写代码,思路是:
拿到老的AST节点
创建一个数组用来盛放新的AST节点(虽然原class只是一个节点,但是替换后它会被若干个函数节点取代) 初始化默认的 constructor
节点(上文提到,class中有可能没有定义constructor)
循环老节点的AST对象(会循环出若干个函数节点)
判断函数的类型是不是 constructor
,如果是,通过取到数据创建一个普通函数节点,并更新默认 constructor
节点
处理其余不是 constructor
的节点,通过数据创建 prototype
类型的函数,并放到 es5Fns
中
循环结束,把 constructor
节点也放到 es5Fns
中
判断es5Fns的长度是否大于1,如果大于1使用 replaceWithMultiple
这个API更新AST
module.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 = <a href="http://www.php.cn/wiki/60.html" target="_blank">Object</a>.create(Parent)
,当然别忘了处理 super
关键字。
打包后代码
运行 npm start
打包后,我们看到打包后的文件里 class
语法已经成功转换为一个个的es5函数。
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
以上是babel轉換es6方法實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!