首頁  >  文章  >  web前端  >  babel轉換es6步奏詳解

babel轉換es6步奏詳解

php中世界最好的语言
php中世界最好的语言原創
2018-04-20 11:23:121569瀏覽

這次帶給大家babel轉換es6步奏詳解,babel轉換es6的注意事項有哪些,下面就是實戰案例,一起來看一下。

babel是一個轉碼器,目前開發react、vue專案都要使用到它。它可以把es6 的語法轉換成es5,也可以轉換JSX等語法。

我們在專案中都是透過配置插件和預設(多個插件的集合)來轉換特定程式碼,例如env、stage-0等。

實際上babel可以透過自訂外掛程式的方式實現任何程式碼的轉換,接下來我們透過一個「把es6的 class 轉換為es5」的範例來了解babel。

內容如下:

webpack環境設定

大家應該都設定過babel-core這個loader,它的作用是提供babel的核心Api,實際上我們的程式碼轉換都是透過插件來實現的。

接下來我們不用第三方的插件,自己實作一個es6類別轉換插件。先執行以下幾個步驟初始化一個專案:

  1. npm install webpack webpack-cli babel-core -D

  2. 新一個webpack.config.js

  3. 設定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就可以了:

#創建其他語句也是一樣的道理,有了上面這兩個東西,我們可以做任何轉換了。

下面我們開始真正寫一個插件,分成以下幾步:

  1. #在index.js中export一個函數

  2. 函數中傳回一個對象,物件有一個visitor參數(必須叫visitor)

  3. #透過astexplorer.net查詢class 對應的AST節點為ClassDeclaration

  4. 在vistor中設定一個擷取函數ClassDeclaration ,意思是我要擷取js程式碼中所有 ClassDeclaration 節點

  5. 寫邏輯程式碼,完成轉換

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 () 方法,這需要我們做相容處理。

接下來我們開始寫程式碼,想法是:

  1. 拿到舊的AST節點

  2. 建立一個陣列用來盛放新的AST節點(雖然原class只是一個節點,但是替換後它會被若干個函數節點取代) 初始化預設的constructor 節點(上文提到,class中有可能沒有定義constructor)

  3. 循環老節點的AST物件(會循環出若干個函數節點)

  4. 判斷函數的型別是不是 constructor ,如果是,透過取到資料建立一個普通函數節點,並更新預設constructor 節點

  5. ##處理其餘不是

    constructor 的節點,透過資料建立prototype 類型的函數,並放到es5Fns

  6. 。循環結束,把

    constructor 節點也放到es5Fns

  7. 判斷es5Fns的長度是否大於1,如果大於1使用

    replaceWithMultiple 這個API更新AST

  8. 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 == &#39;constructor&#39;) {
          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(&#39;prototype&#39;), 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函數。

相信看了本文案例你已經掌握了方法,更多精彩請關注php中文網其它相關文章!

推薦閱讀:

vue cli如何升級webapck4

AngularJS應用模組化的詳細步奏

#vue-cli 3.0 新手入門必知
#

以上是babel轉換es6步奏詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn