babel 7 的使用的個人理解

babel 7 的使用的個人理解

最近看了很多關于babel的使用方法,大部分在一些點上都沒有說明白,同時給出的代碼也很難再現,為了能夠理解和防止以后自己遺忘,特寫此文。
首先我們要明白babel存在的意義,babel的目的就是為了解決瀏覽器的自身對于es語言的差異性而帶來的一款工具,有了babel就首先不用擔心瀏覽器不支持es語言這件事(當然現在的瀏覽器尤其是chrome對es6的支持越來越好),其實最重要的不是支持,而是解決差異性,這種差異性不僅介于瀏覽器之間,對于node這樣的環境也會存在這樣的問題,各個node版本對于es的支持,或者對于es的一些尚未提交的草案的支持都是不同的,所以不論是瀏覽器下還是node下都需要到使用babel的場景。

我這里就以node下的使用為例,來介紹babel的具體使用
首先在node項目里創建的index.js文件:

console.log([1,2,3].findIndex(x => x == 4))

console.log('abc'.padStart(10)) 

const alertMe = (msg) => {
    console.log(msg)
}
class Robot {
    constructor (msg) {
        this.message = msg
    }
    say () {
        alertMe(this.message)
    }
}
const marvin = new Robot('hello babel')
marvin.say()

這段代碼很簡單,沒有特別含義,主要是測試babel對es6下的轉義能力
在具體使用babel之前,必須介紹一下babel的各個組成部分,注意這里我們使用babel7

  • @babel/cli
  • @babel/core
  • @babel/preset-env
  • @babel/polyfill
  • @babel/runtime
  • @babel/plugin-transform-runtime
  • @babel/plugin-transform-xxx
    以上這些就是我們以后常常會使用的babel的各個重要部分了
    這里要注意一下這個@這個符號,這個是只有babel7才特有的,babel6都木有,市面上大量代碼都是基于6的所以要特別注意,安裝的時候要使用 npm install i -S @babel\cli 而不能是npm install i -S babel-cli了
    這是 babel 7 的一大調整,原來的 babel-xx 包統一遷移到babel域 下 - 域由 @ 符號來標識
    搞懂了這個之后,我們來一個一個看他們的使用:

@babel/cli

@babel/cli是babel提供的內建的命令行工具,主要是提供babel這個命令來對js文件進行編譯,這里要注意它與另一個命令行工具@babel/node的區別,首先要知道他們二者都是命令行工具,但是官方文檔明確對他們定義了他們各自的使用范圍:
@babel/cli 是一個適合安裝在本地項目里,而不是全局安裝

While you can install Babel CLI globally on your machine, it's much better to install it locally project by project.

@babel/node 跟node cli類似,不適用在產品中,意味著適合全局安裝

babel-node is a CLI that works exactly the same as the Node.js CLI
You should not be using babel-node in production

除了這個不同以外,他們還有一個共同點就是,直接使用它們編譯,上來就會報個錯(找不到@babel/core),原因是@babel/cli是依賴一個叫@babel/core的包文件的,沒有這個包文件是絕對不能執行任何編譯操作的(因為執行編譯的transform方法在這個包里),好吧,那我們就趕緊npm install --save @babel/core,接著再來執行babel編譯,結果!結果是編譯了之后都不會產生任何的編譯效果,例如下面這個箭頭函數所在的文件:test.js

let fun = () => console.log('hello babel')

我們在安裝了@babel/cli或者@babel/node之后
使用@babel/cli編譯
$ babel test.js
使用@babel/node編譯
$ babel-node test.js
兩個的編譯結果都是該文件無任何變化
這個問題的發生來自 babel 6 。Babel 6 做了大量模塊化的工作,將原來集成一體的各種編譯功能分離出去,獨立成插件。這意味著,默認情況下,當下版本的 babel 不會編譯代碼。

這里就扯淡了。。。你不能將箭頭函數編譯成es5,那搞個毛呀。。。

好吧,既然安裝了@babel/core,安裝了@babelb/cli這兩個還是不行,那就說明它還需要別人配合,這也就是所謂的光有刀(@babel/core,@babelb/cli)不行,還得有料(@babel/plugin-transform-xxx)才行,一堆配合前兩者,真正發揮作用的插件(@babel/plugin-transform-xxx)就登場了。
例如我們要將上面的箭頭函數編譯,我們需要這個插件
@babel/plugin-transform-arrow-functions
npm install --save-dev @babel/plugin-transform-arrow-functions
在安裝這個插件之后,再次使用babel命令行工具@babel/cli
babel test.js --plugins @babel/plugin-transform-arrow-functions
結果是箭頭函數成功的被成功編譯成了:

let fun = function () {
     return console.log('hello babel');
};

這說明要使用各種真正的編譯功能是需要配合各種插件的,要將箭頭函數編譯成普通函數需要使用@babel/plugin-transform-arrow-functions,要將const或者let變量編譯成var變量需要@babel/plugin-transform-block-scoping,要將class關鍵字轉化成傳統基于原型的類需要@babel/plugin-transform-classes,所以為了真正的編譯我們是可能需要大量各種的插件的,具體插件有哪些請點這里

使用插件是可以編譯了,但是大量的使用插件會產生兩個問題:
  • 第一個問題是在使用babel 調用插件采用的是命令行參數的形式
    如上面的:--plugins @babel/plugin-transform-arrow-functions
    這種寫法在單個插件的情況下還好,多個插件的使用就是噩夢了,所以為了解決第一種問題,出現了一個叫.babelrc的配置文件,我們可以將多個插件的信息寫入到配置文件中,因為@babel/cli在調用之時都會去調用.babelrc文件,這樣就不用寫老長的命令行參數了。
    例如我們同樣是編譯這段代碼:let fun = () => console.log('hello babel')
    我們在.babelrc文件中寫上
{
  "plugins": [
    "@babel/plugin-transform-arrow-functions"
    "@babel/plugin-transform-block-scoping" 
  ]
}

這樣我們就可以將代碼編譯成下面的樣子:

var fun = function () {
  return console.log('hello babel');
};

這樣第一個問題就解決了,好了第二個問題是什么了?

  • 第二個問題是同樣是關于多個插件的使用的,如果我的代碼中大量使用插件,那我依然避免不了在配置文件中大量填寫插件信息的工作,但是偉大的babel為了讓程序員們有更多的時間做自己喜愛的事情,而不是浪費生命在一個一個的挑選插件,然后把它們寫在.babelrc上,它提供了一個叫做preset的概念,說好聽點叫預設,直白點就是插件包的意思,意味著babel會預先替我們做好了一系列的插件包,例如下面這些babel認為程序員會用到的常用的插件包:
注意呀,除了這些插件包外,還有很多別的哦,這些只是常用的而已

我們這里主要介紹這個叫@babel/preset-env的插件包
我們還是通過示例來研究研究:
代碼是什么了,是我們在文章之初就創建的那個index.js文件,里面可有大量es6的語法代碼,那么我們在安裝了@babel/preset-env,并且在.babelrc中配置了@babel/preset-env之后

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "node": "4"
        }
      }
    ]
  ]
}

這時候運行babel index.js之后,我們就可以帶到index.js被編譯之后的結果:

"use strict";

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

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

console.log([1, 2, 3].findIndex(function (x) {
  return x == 4;
}));
console.log('abc'.padStart(10));

var alertMe = function alertMe(msg) {
  console.log(msg);
};

var Robot =
/*#__PURE__*/
function () {
  function Robot(msg) {
    _classCallCheck(this, Robot);

    this.message = msg;
  }

  _createClass(Robot, [{
    key: "say",
    value: function say() {
      alertMe(this.message);
    }
  }]);

  return Robot;
}();

var marvin = new Robot('hello babel');
marvin.say();

媽呀,嚇死哥哥了,居然產生了這么多鬼東西,我不要看這些,我要看es6

這說明在node下我們成功將es6的代碼轉化成了編譯后的js代碼。
這時候我們再回過頭來看@babel/preset-env這個插件包

Without any configuration options, babel-preset-env behaves exactly the same as babel-preset-latest (or babel-preset-es2015, babel-preset-es2016, and babel-preset-es2017 together).

默認情況下,babel-preset-env 等效于三個插件包,而不巧我們前面提到的單個的插件已經囊括在 babel-preset-es2015 中。
當然現在編譯結果已經出來了,但是馬上就有眼尖的同學發現新的問題,

  • 第一個問題是這個:
{
 "targets": {
      "node": "4"
  }
}

這個是什么鬼?這個targets實際上是針對上面的@babel/preset-env這個插件包的一個配置參數,它所代表的是你編譯代碼所針對的目標平臺,我們這里的目標是版本號為4的node(友情提示:node -v 命令可以檢查node的版本),也就是我編譯之后的代碼能夠在node版本號為4的環境下運行,同樣大家可以做個試驗,如果將node這個4改為6,再次編譯,你會發現編譯之后的代碼和編譯之前的代碼沒有任何變化,這表明原始的代碼實際上已經可以直接在版本為6的node上直接運行,不需要babel的編譯了。
當然這里的targets參數配置除了可以設置node環境外,還可以設置針對各個瀏覽器環境的配置,例如:

{
  "targets": {
    "chrome": "58",
    "ie": "11"
  }
}

當然針對瀏覽器差異的設置比node更多更靈活和復雜,感興趣的同學可以點這里看所有的配置參數信息。
好了,第一個問題解決了,我們可以引出第二個問題。

  • 第二個問題眼尖的同學可以在代碼編譯之后的結果中找到,那就是代碼中的
    findIndex方法和padStart方法,這兩個方法作為es6提出的新方法,居然沒有被babel編譯解析,這樣如果我直接使用node命令執行編譯后的index.js文件,那么必然是會報錯的,因為我版本為4的node環境哪里認識什么findIndex和padStart,那這樣就很尷尬了,所以光是使用@babel/preset-env是不夠的,我們還需要一個叫@babel/polyfill的包來解決。

引用別人的一段理解:解釋的很好
babel 編譯過程處理第一種情況 - 統一語法的形態,通常是高版本語法編譯成低版本的,比如 ES6 語法編譯成 ES5 或 ES3。而 babel-polyfill 處理第二種情況 - 讓目標瀏覽器支持所有特性,不管它是全局的,還是原型的,或是其它。這樣,通過 babel-polyfill,不同瀏覽器在特性支持上就站到同一起跑線。

我對polyfill的理解:polyfill我們又稱墊片,見名知意,所謂墊片也就是墊平不同瀏覽器或者不同環境下的差異,因為有的環境支持這個函數,有的環境不支持這種函數,解決的是有與沒有的問題,這個是靠單純的@babel/preset-env不能解決的,因為@babel/preset-env解決的是將高版本寫法轉化成低版本寫法的問題,因為不同環境下低版本的寫法有可能不同而已。

所以我們要使用node命令正常運行編譯后的index.js,那么要在編譯后的代碼里加上require('@babel/polyfill')這句

"use strict";
require('@babel/polyfill')
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

console.log([1, 2, 3].findIndex(function (x) {
  return x == 4;
}));
console.log('abc'.padStart(10)); 

var alertMe = function alertMe(msg) {
  console.log(msg);
};

var Robot =
/*#__PURE__*/
function () {
  function Robot(msg) {
    _classCallCheck(this, Robot);

    this.message = msg;
  }

  _createClass(Robot, [{
    key: "say",
    value: function say() {
      alertMe(this.message);
    }
  }]);

  return Robot;
}();

var marvin = new Robot('hello babel');
marvin.say();

再次執行node命令,就可以看到結果了


QQ截圖20181108223713.png

再介紹完以上的babel包之后我們還剩最后兩個包給大家介紹一下,分別是:

  • @babel/runtime
  • @babel/plugin-transform-runtime
    @babel/runtime的作用是提供統一的模塊化的helper,那什么是helper,我們舉個例子:
    我們編譯之后的index.js代碼里面有不少新增加的函數,如_classCallCheck,_defineProperties,_createClass,這種函數就是helper。那這種helper跟我們的@babel/runtime有什么關系了,我們接著看,比如像這個_createClass就是我們將es6的class關鍵字轉化成傳統js時生成的一個函數,那么如果我有很多個js文件中都定義了class類,那么在編譯轉化時就會產生大量相同的_createClass方法,那這些_createClass這樣的helper方法是不是冗余太多,因為它們基本都是一樣的,所以我們能不能采用一個統一的方式提供這種helper,也就是利用es或者node的模塊化的方式提供helper,將這些helper做成一個模塊來引入到代碼中,豈不是可以減少這些helper函數的重復書寫。
    那我們現在就
    npm install --save @babel/runtime @babel/plugin-transform-runtime
    然后就只需要在.babelrc中寫上:
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "node": "4"
        }
      }
    ]
  ],
  "plugins": [
    "@babel/plugin-transform-runtime"
  ]
}

這次再次使用babel命令編譯index.js就可以得到以下結果:

"use strict";

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

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

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

console.log([1, 2, 3].findIndex(function (x) {
  return x == 4;
}));
console.log('abc'.padStart(10)); 

var alertMe = function alertMe(msg) {
  console.log(msg);
};

var Robot =
/*#__PURE__*/
function () {
  function Robot(msg) {
    (0, _classCallCheck2.default)(this, Robot);
    this.message = msg;
  }

  (0, _createClass2.default)(Robot, [{
    key: "say",
    value: function say() {
      alertMe(this.message);
    }
  }]);
  return Robot;
}();

var marvin = new Robot('hello babel');
marvin.say();

注意:我們這里要在編譯結果后人為添加require('@babel/polyfill')之后才可以用node命令去正常執行index.js

重點在:

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

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

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

這一類helper已經是被從@babel/runtime包require進來了,這都是@babel/runtime的功勞,但是事情還沒完,我們還有個包@babel/plugin-transform-runtime沒提到就用了,這個包的作用其實就是輔助@babel/runtime的,因為有了@babel/plugin-transform-runtime它會幫我自動動態require @babel/runtime中的內容,如果沒有這個@babel/plugin-transform-runtime,那么我們要使用@babel/runtime中的內容,就只有像require('@babel/polyfill')那樣人工去手動添加了,所以@babel/plugin-transform-runtime非常方便,由于@babel/plugin-transform-runtime是一個插件,所以它是需要配置到.babelrc中的,這一點要記住。

好了自此關于babel的知識就暫時告一段落,關于babel的內容肯定還有很多很多,但常用的這些相信也夠理解它的主要意義和基本使用了。

最后放上本人在gitee上的代碼例子:點這里
友情提示:使用npm run build來編譯哦,使用node命令執行lib/index.js就ok了

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容