一文搞懂Babel配置

最近在作一次Babel6升級Babel7的操做,把升級的過程和關於babel的配置進行一次總結。前端

1 爲何講Babel配置

Babel 是一個工具鏈,主要用於將 ECMAScript 2015+ 版本的代碼轉換爲向後兼容的 JavaScript 語法,以便可以運行在當前和舊版本的瀏覽器或其餘環境中。node

其實目前前端開發,各類項目模版,你也不須要關心babel的配置,隨便拉下來一個就能運行,可是要作定製化的處理仍是要把babel搞懂。android

@babel/cli是Babel的命令行工具,咱們通常用不到,由於咱們一般都是用babel-loader,裏邊使用的是@babel/core的api形式,咱們只須要關心Babel的配置,若是有須要在編譯階段對代碼進行處理 也能夠寫本身的插件,可是大部分場景是須要咱們把Babel的配置搞清楚。ios

2 Babel的配置文件

Babel6的階段 最經常使用的是.babelrc,可是如今Babel7支持了更多格式:git

const RELATIVE_CONFIG_FILENAMES = [".babelrc", ".babelrc.js", ".babelrc.cjs", ".babelrc.mjs", ".babelrc.json"];
package.json files with a "babel" key。github

配置文件的格式以下:web

{
    "presets": [
      [
        "@babel/preset-env",
        {
          "modules": "commonjs"
        }
      ]
    ],
    "plugins": [
      [
        "@babel/plugin-transform-runtime",
        {
          "corejs": 3
        }
      ],
      "@babel/plugin-syntax-dynamic-import",
    ]
  }
}

更詳細介紹參見Babel Configchrome

2.1 pluginspreset

配置文件中主要有兩個配置pluginspreset,@babel/core自己對代碼不作任何的轉化,可是提供了對代碼的解析,各類插件在解析過程當中能夠進行代碼的轉換,好比處理箭頭函數的插件@babel/plugin-transform-arrow-functions等等,因此好比針對ES6語法的解析就須要不少插件,preset預設就是配置的子集,預設的一套配置,能夠根據參數動態的返回配置。npm

2.2 執行順序

順序問題很重要,好比某一個插件是添加'use strict', 一個插件是刪除'use strict',若是想要刪除成功,就要保證執行順序。
在一個配置裏面json

  • 插件在 presets 前運行。
  • 插件順序從前日後排列。
  • preset 順序是顛倒的(從後往前)。

因此在preset中的插件,確定比外層的插件要後執行。

2.3 傳參數

pluginspreset的配置是數組的形式,若是不須要傳參數,最基本的就是字符串名稱,若是須要傳參數,把它寫成數組的形式,數組第一項是字符串名稱,第二項是要傳的參數對象。

3 Babel的升級

3.1 廢棄的preset

@babel/preset-env已經徹底能夠替換

  • babel-preset-es2015
  • babel-preset-es2016
  • babel-preset-es2017
  • babel-preset-latest

全部stage的preset在Babel v7.0.0-beta.55版本都已經被廢棄了,
stage-x:指處於某一階段的js語言提案

  • Stage 0 - 設想(Strawman):只是一個想法,可能有 Babel插件。
  • Stage 1 - 建議(Proposal):這是值得跟進的。
  • Stage 2 - 草案(Draft):初始規範。
  • Stage 3 - 候選(Candidate):完成規範並在瀏覽器上初步實現。
  • Stage 4 - 完成(Finished):將添加到下一個年度版本發佈中。

最開始stage的出現是爲了方便開發人員,每一個階段的插件與TC39和社區相互做用,同步更新,用戶能夠直接引用對應stage支持的語法特性。關於廢棄的緣由 總結下來是:

  • 1 對用戶太黑盒了,當提案發生重大變化和廢棄時,stage內部的插件就會變化,用戶可能會出現未編譯的語法。
  • 2 當用戶想要支持某種語法時,不知道在某一個stage裏,因此最好是讓用戶本身去添加插件,或者你只須要指定瀏覽器的兼容性,preset中動態的添加對應插件。
  • 3 第三點舉了個例子,不少人都把裝飾器特性加作ES7,其實這只是階段0的實驗性建議,可能永遠不會成爲JS的一部分。不要將其稱爲「ES7」,咱們要時刻提醒開發者babel是怎麼工做的。

3.1 廢棄的polyfill

先說下已經有了Babel爲何還要polyfill,Babel默認只轉換新的JavaScript句法(syntax),而不轉換新的API,好比 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局對象,以及一些定義在全局對象上的方法(好比Object.assign)都不會轉碼。舉個栗子,ES6在Array對象上新增了Array.from方法。babel就不會轉碼這個方法。因此以前咱們都須要引入polyfill。

可是從Babel 7.4.0開始,不推薦使用此軟件包,而直接包括core-js/stable(包括regenerator-runtime/runtimepolyfill ECMAScript功能)和(須要使用轉譯的生成器函數)。

import "core-js/stable";
import "regenerator-runtime/runtime";

可是最優的方式也不是直接這樣引入,後面講@babel/preset-env的使用時會有更好的方式。

3.3 babel-upgrade

關於升級,官方提供了工具 babel-upgrade 總結關鍵點以下:

  • 1 node版本8以上 這個應該都不是問題了。
  • 2 npx babel-upgrade --write --install,兩個參數,--write會把更新的配置寫入babel的配置文件中,package.json中也會更新依賴,可是發現沒有的依賴沒有新增,因此我在更新的時候把配置中依賴的npm包,在package.json都check了一遍。--install是會進行一次安裝操做。

4 @babel/preset-env

@babel/preset-env是Babel推薦的最智能的預設,在使用了 babel-upgrade 升級以後你就能夠看到配置中會有這個預設,由於設個預設集成了經常使用插件和polyfill能力,能夠根據用戶指定的環境尋找對應的插件。

下面對它的關鍵配置項作說明。

4.1 target

string | Array<string> | { [string]: string },默認爲{}

描述您爲項目支持/目標的環境。

這能夠是與瀏覽器列表兼容的查詢:

`{
  "targets": "> 0.25%, not dead"
}`

或支持最低環境版本的對象:

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

實施例的環境中:chromeoperaedgefirefoxsafariieiosandroidnodeelectron

若是未指定目標,則旁註@babel/preset-env將默認轉換全部ECMAScript 2015+代碼,因此不建議。

4.2 useBuiltIns

"usage"| "entry"| false,默認爲false

此選項決定@babel/preset-env如何處理polyfill的引入。

前面將廢棄polyfill時 講到了polyfill如今分爲兩個npm包,是這樣引入

import "core-js/stable";
import "regenerator-runtime/runtime";

可是問題是全量引入,增長包體積,因此useBuiltIns選項就是對其進行優化。

當取值"entry"時,@babel/preset-env 會把全量引入替換爲目標環境特定須要的模塊。

當目標瀏覽器是 chrome 72 時,上面的內容將被 @babel/preset-env 轉換爲

require("core-js/modules/es.array.reduce");
require("core-js/modules/es.array.reduce-right");
require("core-js/modules/es.array.unscopables.flat");
require("core-js/modules/es.array.unscopables.flat-map");
require("core-js/modules/es.math.hypot");
require("core-js/modules/es.object.from-entries");
require("core-js/modules/web.immediate");

當取值"usage"時,咱們無需手動引入polyfill文件,@babel/preset-env 在每一個文件的開頭引入目標環境不支持、僅在當前文件中使用的 polyfills。

例如,

const set = new Set([1, 2, 3]);
[1, 2, 3].includes(2);

當目標環境是老的瀏覽器例如 ie 11,將轉換爲

import "core-js/modules/es.array.includes";
import "core-js/modules/es.array.iterator";
import "core-js/modules/es.object.to-string";
import "core-js/modules/es.set";

const set = new Set([1, 2, 3]);
[1, 2, 3].includes(2);

當目標是 chrome 72 時不須要導入,由於這個環境不須要 polyfills:

const set = new Set([1, 2, 3]);
[1, 2, 3].includes(2);

4.3 core-js

core-js就是Javascript標準庫的polyfill,@babel/preset-env的polyfill就依賴於它,因此咱們須要指定使用的core-js的版本,目前最新版本是3。
默認狀況下,僅注入穩定ECMAScript功能的polyfill,若是想使用一些提案的語法,能夠有三種選擇:

  • 使用useBuiltIns: "entry"時,能夠直接導入建議填充工具import "core-js/proposals/string-replace-all"
  • 使用useBuiltIns: "usage"時,您有兩種不一樣的選擇:

    • shippedProposals選項設置爲true。這將啓用已經在瀏覽器中發佈一段時間的投標的polyfill和transforms。
    • 使用corejs: { version: 3, proposals: true }。這樣能夠對所支持的每一個提案進行填充core-js

4.4 exclude

我以爲這個選擇有用,由於@babel/preset-env中內置的插件,咱們沒法在其後執行,好比裏面內置的"@babel/plugin-transform-modules-commonjs"插件會默認的在全部的模塊上都添加use strict 嚴格模式, 雖然有babel-plugin-remove-use-strict用於移除use strict 可是因爲執行順序的問題,仍是沒法移除。
第二個問題就是內置插件沒法傳參數的問題。
因此我想到的方法是先exclude排除掉這個插件,而後在外層再添加 這樣就能夠改變執行順序同時也能夠自定義傳參數。

5 @babel/plugin-transform-runtime

已經有了polyfill,這個包的做用是什麼?主要分兩類:

  • 1 減小代碼體積,Babel的編譯會在每個模塊都添加一些行內的代碼墊片,例如await_asyncToGeneratorasyncGeneratorStep,使用了它以後會把這些方法經過@babel/runtime/helpers中的模塊進行替換。

例如代碼

async function a () {
  await new Promise(function(resolve, reject) {
    resolve(1)
  })
}

沒使用以前,編譯結果

require("regenerator-runtime/runtime");

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }


function _a() {
  _a = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return new Promise(function (resolve, reject) {
              resolve(1);
            });

          case 2:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));
  return _a.apply(this, arguments);
}

使用以後

var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

require("regenerator-runtime/runtime");

var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/asyncToGenerator"));
  • 2 局部引入 不影響全局變量

@babel/preset-env中引入的polyfill都是直接引入的core-js下的模塊,它的問題會污染全局變量,好比

"foobar".includes("foo");

編譯後的polyfill是給String.prototype添加了includes方法,因此會影響全局的String對象。

require("core-js/modules/es.string.includes");

而使用了@babel/plugin-transform-runtime後的編譯結果

var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));

(0, _includes.default)(_context = "foobar").call(_context, "foo");

會把代碼中用的到的方法進行包裝,而不會對全局變量產生影響。

最後是 @babel/plugin-transform-runtime的配置項,關鍵的是指定 core-js的版本。

corejs: 2僅支持全局變量(例如Promise)和靜態屬性(例如Array.from),corejs: 3還支持實例屬性(例如[].includes)。

默認狀況下,@babel/plugin-transform-runtime不填充提案。若是您使用corejs: 3,則能夠經過使用proposals: true選項啓用此功能。

須要安裝對應的運行時依賴:
npm install --save @babel/runtime-corejs3

最後 你能夠基於以上知識已經建立了符合本身團隊開發的preset。

若是以爲有收穫請關注微信公衆號 前端良文 每週都會分享前端開發中的乾貨知識點。

相關文章
相關標籤/搜索