babel使用詳解

漲月薪發表於2018-12-11

1. bebel該如何使用

第一步 安裝

npm install babel-cli --save-devcnpm install babel-cli --save-dev

如果想設定轉碼規則使用 cnpm install babel-preset-es2015 --save-dev

會在package.json中生成包如下(部分)

"babel-core": "^6.22.1",//babel轉譯器本身,提供了babel的轉譯API,如babel.transform等,用於對程式碼進行轉譯。像webpack的babel-loader就是呼叫這些API來完成轉譯過程的。
"babel-plugin-transform-runtime": "^6.22.0",//babel轉譯過程中使用到的外掛,其中babel-plugin-transform-xxx是transform步驟使用的
"babel-preset-env": "^1.3.2",//transform階段使用到的一系列的plugin
"babel-preset-stage-2": "^6.22.0",//transform階段使用到的一系列的plugin
"babel-register": "^6.22.0",//通過繫結node.js的require來自動轉譯require引用的js程式碼檔案
複製程式碼

(不建議安裝在全域性,因為不同專案可能會使用不同的bebel版本)

第二步 建立配置檔案 .babelrc (放在根目錄下,不建議在package.json裡在babel欄位新增設定)

{
  "presets": [//要載入和使用的preset列表,preset名前的babel-preset-可省略;presets列表的preset按從尾到頭的逆序執行(為了相容使用者使用習慣)
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
    //"es2015"
  ],
  "plugins": ["transform-runtime"],//該屬性是告訴babel要使用那些外掛,這些外掛可以控制如何轉換程式碼。要載入和使用的外掛列表,外掛名前的babel-plugin-可省略;plugin列表按從頭到尾的順序執行
  "env": {//指定在不同環境下使用的配置。比如production和development兩個環境使用不同的配置,就可以通過這個欄位來配置。env欄位的從process.env.BABEL_ENV獲取,如果BABEL_ENV不存在,則從process.env.NODE_ENV獲取,如果NODE_ENV還是不存在,則取預設值"development"
    "test": {
      "presets": ["env", "stage-2"],
      "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
    }
  }
}
//同時設定了presets和plugins,那麼plugins的先執行;
複製程式碼

babel會從當前轉譯的檔案所在目錄下查詢配置檔案,如果沒有找到,就順著文件目錄樹一層層往上查詢,一直到.babelrc檔案存在或者帶babel欄位的package.json檔案存在為止。

第三步 webpack.base.conf.js中配置babel

module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test')]
      }
    ]
  }
複製程式碼

第四步 在package.json中配置專案的瀏覽器相容情況

(具體引數參照[github.com/browserslis…])

 "browserslist": [
    "> 1%",
    "last 2 versions",
    "Android >= 3.2", 
    "Firefox >= 20", 
    "iOS 7"
  ]
複製程式碼

作用:

  1. 【.babelrc】檔案可以針對配置
{
 "presets": [
   ["env", {
     "targets": {
       "browsers": ["last 2 versions"],
       "node": "current"
     },
   }]
 ]
}
複製程式碼
  1. 根據瀏覽器可以獲得特性,比如最新的chrome瀏覽器支援原生的promise,而IE不支援,babel根據browserslist配置項就會動態的轉義。不用在一個個進行配置了。

第五步 進入根目錄,npm run build執行,獲得打包檔案

2. babel的工作原理

babel是一個轉譯器,它把同種語言的高版本規則翻譯成低版本規則。

babel的轉譯過程分為三個階段:parsing、transforming、generating,以ES6程式碼轉譯為ES5程式碼為例,babel轉譯的具體過程如下:

ES6程式碼輸入 -> babylon進行解析 -> 得到AST -> plugin用babel-traverse對AST樹進行遍歷轉譯 -> 得到新的AST樹
-> 用babel-generator通過AST樹生成ES5程式碼
複製程式碼

【!!!注意!!!】 Babel預設只轉換新的javascript語法,而不轉換新的API,比如 Iterator, Generator, Set, Maps, Proxy, Reflect,Symbol,Promise 等全域性物件。以及一些在全域性物件上的方法(比如 Object.assign)都不會轉碼。 比如說,ES6在Array物件上新增了Array.form方法,Babel就不會轉碼這個方法,如果想讓這個方法執行,必須使用 babel-polyfill來轉換等。

babel-polyfillbabel-runtime就是為了解決新的API與這種全域性物件或全域性物件方法不足的問題,因此可以使用這兩個外掛可以轉換的。

3.babel-polyfill 使用方法

  1. 先安裝包: npm install --save babel-polyfill
  2. 在入口處匯入polyfill,保證polyfill程式碼在所有其他程式碼前先被呼叫 import "babel-polyfill"
  3. webpack配置:module.exports = { entry: ["babel-polyfill", "./app/js"] };

如果只是需要引入部分新原生物件或API,那麼可以按需引入,而不必匯入全部的環境,如下:

【通過core-js實現按需引入polyfill或runtime】

  1. core-js是polyfill、runtime的核心,因為polyfill和runtime其實都只是對core-js和regenerator的再封裝,方便使用而已。
  2. polyfill和runtime都是整體引入的,不能做細粒度的調整,如果我們的程式碼只是用到了小部分ES6而導致需要使用polyfill和runtime的話,會造成程式碼體積不必要的增大(runtime的影響較小)。所以,按需引入的需求就自然而然產生了,這個時候就得依靠core-js來實現了。
  3. core-js有三種使用方式
  • 預設方式:require('core-js'):這種方式包括全部特性,標準的和非標準的,類似polyfill。

    require('core-js/fn/set');
    require('core-js/fn/array/from');
    require('core-js/fn/array/find-index');
    
    Array.from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3]
    [1, 2, NaN, 3, 4].findIndex(isNaN);   // => 2
    
    複製程式碼
  • 庫的形式: var core = require('core-js/library'):這種方式也包括全部特性,類似runtime,只是它不會汙染全域性名字空間,但是不能使用例項方法。

    var Set = require('core-js/library/fn/set');
    var from = require('core-js/library/fn/array/from');
    var findIndex = require('core-js/library/fn/array/find-index');
    
    from(new Set([1, 2, 3, 2, 1]));      // => [1, 2, 3]
    findIndex([1, 2, NaN, 3, 4], isNaN); // => 2
    複製程式碼

    想要使用prototype方法:通過::這個符號而不是.來呼叫例項方式,從而達到曲線救國的目的。這種方式的使用,路徑中都會帶有/virtual/

    import {fill, findIndex} from 'core-js/library/fn/array/virtual';
    
    Array(10)::fill(0).map((a, b) => b * b)::findIndex(it => it && !(it % 8)); // => 4
    
    // 對比下polyfill的實現 
    // Array(10).fill(0).map((a, b) => b * b).findIndex(it => it && !(it % 8));
    複製程式碼
  • shim: require('core-js/shim')var shim = require('core-js/library/shim'): 這種方式只包括標準特性(就是隻有polyfill功能,沒有擴充套件的特性)

    舉例說明

    • core-js/es6(core-js/library/es6)包含了全部的ES6特性,

    • core-js/es6/array(core-js/library/es6/array)只包含ES6的Array特性

    • core-js/fn/array/from(core-js/library/fn/array/from)只有Array.from這個實現

      (具體的每個特性和對應的路徑可以參照[github.com/zloirock/co…])

4.babel-polyfill和babel-runtime的區別

  • babel-polyfill 會做相容執行環境中並沒有實現的一些方法。
  • babel-runtime 是將es6編譯成es5去執行。使用es6的語法編寫,最終會通過babel-runtime編譯成es5。也就是說,不管瀏覽器是否支援ES6,只要是ES6的語法,它都會進行轉碼成ES5。所以就有很多冗餘的程式碼。
  • babel-polyfill 是通過向全域性物件和內建物件的prototype上新增方法來實現的。比如執行環境中不支援Array.prototype.find 方法,引入polyfill, 我們就可以使用es6方法來編寫了,但是缺點就是會造成全域性空間汙染。
  • babel-runtime不會汙染全域性物件和內建物件的原型,比如說我們需要Promise,我們只需要import Promise from 'babel-runtime/core-js/promise'即可,這樣不僅避免汙染全域性物件,而且可以減少不必要的程式碼。

【綜上】雖然 babel-runtime 可以解決 babel-polyfill中的避免汙染全域性物件,但是它自己也有缺點的,比如我現在有100個檔案甚至更多的話,就需要一個個檔案加import Promise from 'babel-runtime/core-js/promise',為解決這一問題,引入了babel-plugin-transform-runtime。避免手動引入多個檔案的import,並且它還做了公用方法的抽離。比如說有100個模組都使用promise,但是promise的polyfill僅僅存在1份。 這就是babel-plugin-transform-runtime外掛的作用。

5.transform-runtime和babel-runtime的區別

babel-plugin-transform-runtime外掛依賴babel-runtime,babel-runtime是真正提供runtime環境的包;也就是說transform-runtime外掛是把js程式碼中使用到的新原生物件和靜態方法轉換成對runtime實現包的引用,舉個例子如下:

// 輸入的ES6程式碼
var sym = Symbol();
// 通過transform-runtime轉換後的ES5+runtime程式碼 
var _symbol = require("babel-runtime/core-js/symbol");
var sym = (0, _symbol.default)();
複製程式碼

原本程式碼中使用的ES6新原生物件Symbol被transform-runtime外掛轉換成了babel-runtime的實現,既保持了Symbol的功能,同時又沒有像polyfill那樣汙染全域性環境(因為最終生成的程式碼中,並沒有對Symbol的引用)

而且babel-runtime其實也不是真正的實現程式碼所在,真正的程式碼實現是在core-js中。

6.transform-runtime外掛的功能

  1. 把程式碼中的使用到的ES6引入的新原生物件和靜態方法用babel-runtime/core-js匯出的物件和方法替代
  2. 當使用generators或async函式時,用babel-runtime/regenerator匯出的函式取代(類似polyfill分成regenerator和core-js兩個部分)
  3. 把Babel生成的輔助函式改為用babel-runtime/helpers匯出的函式來替代(babel預設會在每個檔案頂部放置所需要的輔助函式,如果檔案多的話,這些輔助函式就在每個檔案中都重複了,通過引用babel-runtime/helpers就可以統一起來,減少程式碼體積)
  4. babel-plugin-transform-runtime 的配置一些選項
  'plugins': [
    [
      'transform-runtime', 
      {
        'helpers': false,//預設值為true,表示是否開啟內聯的babel helpers(即babel或者環境本來存在的某些物件方法函式)如:extends,etc這樣的在呼叫模組名字時將被替換名字。
        'polyfill': false,//預設值為true,表示是否把內建的東西(Promise, Set, Map)等轉換成非全域性汙染的。
        'regenerator': true,//預設值為true,是否開啟generator函式轉換成使用regenerator runtime來避免汙染全域性域。
        'moduleName': 'flavortown/runtime'//預設值為 babel-runtime,引入規則:import extends from 'flavortown/runtime/helpers/extends';
      }
    ]
  ]
}
複製程式碼

【綜上】:babel-runtime就是一個提供了regenerator、core-js和helpers的執行時庫。transform-runtime在.babelrc裡配置的時候,還可以設定helpers、polyfill、regenerator這三個開關,以自行決定runtime是否要引入對應的功能。

【注意1】:建議不要直接使用babel-runtime,因為transform-runtime依賴babel-runtime,大部分情況下都可以用transform-runtime達成目的。

【注意2】:由於runtime不會汙染全域性空間,所以例項方法是無法工作的(因為這必須在原型鏈上新增這個方法,這是和polyfill最大的不同) ,比如:

var arr = ['a', 'b', 'c'];
arr.fill(7);  // 例項方法不行
Array.prototype.fill.apply(arr, 7);  // 用原型鏈來呼叫也是不行
複製程式碼

相關文章