Babel快速上手使用指南

在剛開始使用babel的時候,相信不少同窗應該和我同樣,對於babel的使用配置只知其一;不知其二,babel相關的包@babel/core,@babel/cli,babel-loader,@babel/polyfill,@babel/plugin-transform-runtime,@babel/runtime什麼時候引入又有什麼做用和區別會感到疑惑。這篇文章的目的就是幫助還沒使用或剛剛使用babel的同窗快速瞭解這些內容,遊刃有餘的使用babel這個強大的工具。
對於想要更深刻了解babel的推薦官方的文檔:中文英文javascript

說明:本文使用babel版本爲7,webpack版本爲4,不一樣版本安裝和配置存在差別。前端

什麼是babel

babel是一個Javascript編譯器,是目前前端開發最經常使用的工具之一,主要用於將 ECMAScript 2015+ 版本的代碼轉換爲向後兼容的 JavaScript 語法,以便可以運行在當前和舊版本的瀏覽器或其餘環境。好比在代碼中使用了ES6的箭頭函數,這種寫法在IE裏面是會報錯的,爲了讓代碼能在IE中運行,就須要將代碼編譯成IE支持的寫法,這就是babel的工做。vue

const fn = arg => {
    console.log(arg);
};

//babel轉換後
 "use strict";

var fn = function fn(arg) {
  console.log(arg);
};

複製代碼

使用方式

  1. 命令行工具中使用
// 在項目中執行
npm i @babel/core @babel-cli -D
複製代碼

安裝以後,就能夠在package.json的scripts中執行babel的腳本命令了。固然也能夠全局安裝,這樣能夠在命令行工具中直接使用babel命令,可是並不推薦。java

{
  "scripts": {
    "build": "babel src -d dist"
  }, 
}
複製代碼

接着命令行執行npm run build,babel就會將src文件夾中的文件編譯好,並輸出到lib文件夾。node

  1. webpack中使用

比起直接用命令行命令,如今咱們的項目一般都使用了打包工具,若是能夠將babel和打包工具結合,在打包時自動調用babel編譯代碼那就更加方便。這裏以webpack爲例,在webpack中若是想使用babel的編譯功能,須要安裝babel-loader。react

npm i @babel/core babel-loader -D
複製代碼

而後在webpack的配置文件中,配置用babel-loader來加載處理js文件。webpack

// webpack.config.js
const path = require('path');
module.exports = {
    entry: './src/app.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }
        ]
    }
};
複製代碼
  1. 更多使用方式

babel還提供了不少其餘使用方式,好比直接配合編輯器使用,或者別的打包工具好比Gulp,Grunt之類的,更多方法能夠去官網查到。git

說明:@babel/core是babel7版本的基礎包,是必須引入的。es6

配置

使用方法很簡單,但只是這樣還不夠,如今執行babel編譯,會發現編譯是成功的,可是編譯後的內容和編譯前沒有任何區別,那是由於咱們沒有告訴babel要怎麼去編譯,編譯哪些內容,咱們須要一個配置文件來告訴babel如何工做。
配置文件的方式有如下幾種:github

  1. 在package.json中設置babel字段。
  2. .babelrc文件或.babelrc.js
  3. babel.config.js文件

第1種方式不用建立文件,package.json加入babel的配置信息就行。

//package.json
{
   "name":"babel-test",
   "version":"1.0.0",
   "devDependencies": {
       "@babel/core":"^7.4.5",
       "@babel/cli":"^7.4.4",
       "@babel/preset-env":"^7.4.5"
   }
   "babel": {
       "presets": ["@babel/preset-env"]
   }
}

複製代碼

第二種.babelrc和.babelrc.js是同一種配置方式,只是文件格式不一樣,一個是json文件,一個是js文件。

.babelrc

{
    "presets": ["@babel/preset-env"]
}
複製代碼

.babelrc.js

//webpack的配置文件也是這種寫法
module.exports = {
    presets: ['@babel/preset-env']
};
複製代碼

這兩個配置文件是針對文件夾的,即該配置文件所在的文件夾包括子文件夾都會應用此配置文件的設置,並且下層配置文件會覆蓋上層配置文件,經過此種方式能夠給不一樣的目錄設置不一樣的規則。
而第3種babel.config.js雖然寫法和.babelrc.js同樣,可是babel.config.js是針對整個項目,一個項目只有一個放在項目根目錄。

注意1:.babelrc文件放置在項目根目錄和babel.config.js效果一致,若是兩種類型的配置文件都存在,.babelrc會覆蓋babel.config.js的配置。

注意2:在package.json裏面寫配置仍是建立配置文件都沒有什麼區別,看我的習慣。react官方腳手架create-react-app建立的react項目babel配置是寫在package.json裏面的,而vue官方腳手架@vue/cli建立的vue項目,則是經過babel.config.js設置。

Plugins和Presets

有了配置文件,接下來就是要經過配置文件告訴babel編譯哪些內容,而後還要引入對應的編譯插件(Plugins),好比上面講到的箭頭函數的轉換須要的是@babel/plugin-transform-arrow-functions這個插件,咱們經過npm安裝這個包以後在配置裏面進行設置。

npm i @babel/plugin-transform-arrow-functions -D
複製代碼
// babel.config.js
module.exports = {
    plugins: ['@babel/plugin-transform-arrow-functions']
};
複製代碼

如今咱們代碼中的箭頭函數就會被編譯成普通函數,可是有個問題,咱們總不能一個個的引入這些插件,來對應轉化咱們用到的每一個新特性,這是很是麻煩的,因而有了一個東西叫作預設(Presets)。

預設其實就是一個預先設定的插件列表,使用一個預設就是將這個預設規定的所有插件安裝並使用,好比預設@babel/preset-es2015,這個預設就包含了@babel/plugin-transform-arrow-functions,以及其餘es2015新特性的轉換插件,像for-of,class,模板字符串等。咱們只用經過npm安裝這個預設包,並像下面這樣設置,就能夠在咱們的代碼中隨意使用這些es2015的新特性,編譯時babel會將這些代碼轉換成低版本瀏覽器也能識別兼容的代碼。

npm i @babel/preset-es2015 -D
複製代碼
// babel.config.js
module.exports = {
    presets: ['@babel/preset-es2015']
};
複製代碼

注意:babel不光支持新語法特性的轉換,react,vue的語法也是經過babel轉換的,好比react項目可使用preset-react。

preset-env

preset雖然已經大大方便了咱們的使用,可是若是咱們還想使用更新一些的語法,好比es2016的**(至關於pow()),es2017的async/await等等,咱們就要引入@babel/preset-es2016,@babel/preset-es2017之類的,並且隨着js語法的更新,這些preset會愈來愈多。因而babel推出了babel-env預設,這是一個智能預設,只要安裝這一個preset,就會根據你設置的目標瀏覽器,自動將代碼中的新特性轉換成目標瀏覽器支持的代碼。

還以轉化箭頭函數舉例,npm安裝@babel/preset-env並配置。

npm i @babel/preset-env -D
複製代碼
// babel.config.js
module.exports = {
    presets: [
        [
            '@babel/preset-env',
            {
                targets: {
                    chrome: '58'
                }
            }
        ]
    ]
};
複製代碼

編譯後咱們會發現箭頭函數並未被轉換成普通函數,那是由於咱們設置目標瀏覽器支持到chrome58,chrome58是原生支持箭頭函數的,因此箭頭函數就並未被轉換,若是咱們將目標瀏覽器設置爲支持ie9,因爲ie9並不支持箭頭,編譯後就會發現箭頭函數被轉換成了普通函數。

目標瀏覽器版本設置方式詳情可參考browserslist, 瀏覽器特性支持可查詢caniuse

注意1:即便不設置targes,也會有一個默認值,規則爲 > 0.5%, last 2 versions, Firefox ESR, not dead。
注意2:官方推薦使用preset-env。

plugin-transform-runtime和runtime

當我在用babel編譯時,有些功能須要一些工具函數來輔助實現,好比class的編譯。

class People{
}

// babel編譯後
'use strict';

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError('Cannot call a class as a function');
    }
}

var People = function People() {
    _classCallCheck(this, People);
};

複製代碼

編譯後的代碼中,_classCallCheck就是一個輔助功能實現的工具函數。若是多個文件中都用到了class,每個文件編譯後都生成一個工具函數,最後就會產生大量重複代碼,平白增長文件體積。而plugin-transform-runtime就是爲了解決這個問題,這個插件會將這些工具函數轉換成引入的形式。

npm i @babel/plugin-transform-runtime -D
複製代碼
module.exports = {
    presets: ['@babel/preset-env'],
    plugins: ['@babel/plugin-transform-runtime']
};
複製代碼

安裝設置完成以後再用babel編譯結以下:

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var People = function People() {
  (0, _classCallCheck2["default"])(this, People);
};
複製代碼

_classCallCheck2這個工具函數已經變成從一個npm包中引入,不會再產生單獨的工具函數代碼。可是能夠看到工具函數是從@babel/runtime這個包中引入,因此要安裝@babel/runtime這個依賴包,在項目打包的時候纔不會報錯。

npm i @babel/runtime
複製代碼

注意:babel/runtime並非開發依賴,而是項目生產依賴。編譯時使用了plugin-transform-runtime,你的項目就要依賴於babel/runtime,因此這兩個東西是一塊兒使用的。

babel-polyfill

babel能夠轉化一些新的特性,可是對於新的內置函數(Promise,Set,Map),靜態方法(Array.from,Object.assign),實例方法(Array.prototype.includes)這些就須要babel-polyfill來解決,babel-polyfill會完整模擬一個 ES2015+環境。

好比你的代碼中用到了Array.from,可是目標瀏覽器不支持Array.from,引入babel-polyfill就會給Array添加一個from方法,代碼執行的時候使用的Array.from實際上是babel-polyfill模擬出來功能,這樣雖然污染了Array的靜態方法,可是確實實現了兼容。 以前的使用方式是npm安裝@babel/polyfill,並在項目入口處引入@babel/polyfill包。

npm i @babel/polyfill
複製代碼
// entry.js
import "@babel/polyfill";
複製代碼

可是這種方式已經被廢棄不推薦使用,由於@babel/polyfill體積比較大,總體引入既增長項目體積,又污染了過多的變量,因此更推薦使用preset-env來按需引入polyfill。

// babel.config.js
module.exports = {
    presets: [
        [
            '@babel/preset-env',
            {
                useBuiltIns: 'usage', // usage-按需引入 entry-入口引入(總體引入) false-不引入polyfill
                corejs: 2  // 2-corejs@2 3-corejs@3
            }
        ]
    ]
};
複製代碼

corejs 是一個給低版本的瀏覽器提供接口的庫,也是polyfill功能實現的核心,此處指定的是引入corejs的版本,須要經過npm安裝指定版本的corejs庫做爲生產依賴。

npm i core-js@2
複製代碼

以後執行babel編譯能夠看到以下效果:

const a = Array.from([1])

//babel編譯後
"use strict";

require("core-js/modules/es6.string.iterator");

require("core-js/modules/es6.array.from");

var a = Array.from([1]); 

複製代碼

能夠看到在使用Array.from以前,提早從core-js引入了相應的polyfill,根據文件名,咱們大概猜到它們的功能是什麼。

plugin-transform-runtime和babel-polyfill的討論

上面說了plugin-transform-runtime主要是負責將工具函數轉換成引入的方式,減小重複代碼,而babel-polyfill則是引入相關文件模擬兼容環境。babel-polyfill有一個問題就是引入文件會污染變量,其實plugin-transform-runtime也提供了一種runtime的polyfill。
咱們將配置文件修改一下。

module.exports = {
    plugins: [['@babel/plugin-transform-runtime', { corejs: 2 }]]
};
複製代碼

這裏的corejs和presets裏設置的corejs是不一樣的,這個地方的corejs是指定了一個叫runtime-corejs庫的版本,使用時也須要用npm安裝對應的包。

npm i @babel/runtime-corejs2
複製代碼

而後執行一下babel編譯看一下區別。

const a = Array.from([1])

//babel編譯後
"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");

var _from = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/array/from"));

var a = (0, _from["default"])([1]);

複製代碼

能夠看到,此方法和使用babel-polyfill的區別是,並無改變Array.from,而是建立了一個_from來模擬Array.from的功能,調用Array.from會被編譯成調用_from,這樣作的好處很明顯就是不會污染Array上的靜態方法from,plugin-transform-runtime提供的runtime形式的polyfill都是這種形式。

通過個人測試,除了實例上的方法如Array.prototype.includes這種的,其它以前提到的內置函數(Promise,Set,Map),靜態方法(Array.from,Object.assign)均可以採用plugin-transform-runtime的這種形式。

而後我就想,既然這種形式不會污染變量,那固然能用就用這種了,可是羣裏問了一下後,大佬們給出了一個見解(感謝大佬justjavac和小時哥的說明)。

runtime 不污染全局變量,可是會致使多個文件出現重複代碼。
寫類庫的時候用runtime,系統項目仍是用polyfill。
寫庫使用 runtime 最安全,若是咱們使用了 includes,可是咱們的依賴庫 B 也定義了這個函數,這時咱們全局引入 polyfill 就會出問題:覆蓋掉了依賴庫 B 的 includes。若是用 runtime 就安全了,會默認建立一個沙盒,這種狀況 Promise 尤爲明顯,不少庫會依賴於 bluebird 或者其餘的 Promise 實現,通常寫庫的時候不該該提供任何的 polyfill 方案,而是在使用手冊中說明用到了哪些新特性,讓使用者本身去 polyfill。

話說的已經很明白了,該用哪一種形式是看項目類型了,不過一般對於通常業務項目來講,仍是plugin-transform-runtime處理工具函數,babel-polyfill處理兼容。

最後總結

包名 功能 說明
@babel/core babel編譯核心包 必裝開發依賴
@babel/cli 命令行執行babel命令工具 非必裝開發依賴,packages.json的script中使用了babel命令則需安裝
babel-loader webpack中使用babel加載文件 非必裝開發依賴,webpack項目中使用
@babel/plugin-* babel編譯功能實現插件 開發依賴,按照須要的功能安裝
@babel/preset-* 功能實現插件預設 開發依賴,按照須要的功能安裝,js語言新特性轉換推薦使用preset-env
@babel/plugin-transform-runtime 複用工具函數 非必裝開發依賴,和@babel/runtime同時存在
@babel/runtime 工具函數庫 非必裝生產依賴,和@babel/plugin-transform-runtime同時存在
@babel/polyfill 低版本瀏覽器兼容庫 非必裝生產依賴,已不推薦使用,推薦經過preset-env的useBuiltIns屬性按需引入
core-js@* 低版本瀏覽器兼容庫 非必裝生產依賴,經過preset-env引入polyfill需安裝此包,並經過corejs指定版本
@babel/runtime-corejs* 不污染變量的低版本瀏覽器兼容庫 非必裝生產依賴,plugin-transform-runtime設置開啓後,能夠不污染變量的引入polyfill

babel使用的相關內容基本就這些了,至於babel編譯內部實現原理感興趣的能夠深刻研究,也能夠本身寫一些babel的plugins和preset發佈到npm上供你們使用,但願看完此文能對你們理解和使用babel有一點幫助。

能力通常,水平有限,歡迎你們多多指正討論。

所有文章列表

相關文章
相關標籤/搜索