Babel 社群概覽

橡樹上發表於2019-04-19

本文釋出於 2019-04-15,總結了 babel 社群的工具使用,以及如何合理地進行配置。如果要看結論的話,直接跳到文章最後一節。

目錄:

@babel/preset-env

介紹

babel-preset-env 是一系列外掛的合集,官方已不用再使用 preset-201x 和 preset-latst 之類的包,env 包可以根據配置自動處理相容程式碼。

文件:babeljs.io/docs/en/bab…

targets

string | Array<string> | { [string]: string }, defaults to {}

針對你的專案指定生成的程式碼環境。

可以是字串:

{
  "targets": "> 0.25%, not dead"
}
複製程式碼

也可以是物件:

{
  "targets": {
    "chrome": "58",
    "ie": "11"
  }
}
複製程式碼

預設是{}

{
  "targets": {}
}
複製程式碼

其中的環境值可以參考 browserslist 專案。

targets.esmodules

boolean

也可以針對那些支援 ES Module 的瀏覽器而優化。當指定本選項時,browsers 欄位會被忽視。你可以和 <script type="module"></script> 一起使用,來生成更小的指令碼程式碼。

請注意: 當指定 esmodules 選項, browsers targets 會被忽視。

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "esmodules": true
        }
      }
    ]
  ]
}
複製程式碼

targets.node

string | "current" | true

如果要針對當前 node 版本進行編譯,可以指定 "node" :true"node":"current",它與 "node":process.versions.node 相同。

targets.safari

string | "tp"

如果要針對 Safari 的技術預覽版進行編譯,可以指定“safari”:“tp”。

targets.browsers(廢棄)

string | Array<string>

使用 browserslist 選擇瀏覽器的查詢(例如:last 2 versions, > 5%, safari tp)。

注意,browsers 的結果會被 targets 中的顯式項覆蓋。(此特性經過檢驗已廢棄)

注意:這將在更高版本中刪除,而不是直接將 targets 設定為 browserslist 的相容查詢。

{
  "targets": {
    "browsers": {
        "chrome": "58",
        "ie": "11"
    }
  }
}
// 等價於,但最新版已經不能用了
{
  "targets": {
    "chrome": "58",
    "ie": "11"
  }
}
複製程式碼

spec

boolean, 預設 false

為此預設中支援它們的任何外掛啟用更符合規範但可能更慢的轉換。

loose

boolean, 預設 false

為此預設中允許它們的任何外掛啟用 "loose" 轉換。

modules

"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false, defaults to "auto"

啟用將 ES6 模組語法轉換為其他模組型別。

將此設定為 false 將不會轉換模組。

也要注意 cjscommonjs 的別名。

debug

boolean, 預設 false

通過 console.log 輸出使用的 targets/plugins 和外掛資料中指定的版本資訊。

include

Array<string|RegExp>,預設 []

一個總是包含的外掛陣列。

以下是有效的配置:

  • Babel 外掛@babel/plugin-transform-spread 和不帶字首的 plugin-transform-spread 的寫法都支援。
  • 內建特性,比如 es6.mapes6.set,或者 es6.object.assign

可以完全或部分指定外掛名稱(或使用 RegExp )。

支援的寫法:

  • 全稱(string): "es6.math.sign"
  • 部分 (string): "es6.math.*" (解析為所有帶 es6.math 字首的外掛)
  • 正則物件: /^transform-.*$/ 或者 new RegExp("^transform-modules-.*")

注意,上面的正則物件中的 . 意思是匹配任何字元,而不是實際的 . 字元。 另請注意,匹配任何字元。.* 是在正則中使用,不同於 *glob格式中使用。

此選項主要針對於原生增強程式碼中的 BUG,或者一系列沒有起作用的不受支援的功能特性。

例如,node 4 支援原生 class 但不支援 spread。如果 super 需要 spread 特性,那麼需要包含 @babel/plugin-transform-classes 外掛。

注意:includeexclude 選項僅適用於此預設中包含的外掛; 因此,在選項中包含 @babel/plugin-proposal-do-expressions 排除或 @babel/plugin-proposal-function-bind 會丟擲錯誤(因為此預設沒有這些專案)。要使用此預設中未包含的外掛,請直接將其新增到 plugin 選項中。

exclude

Array<string|RegExp>,預設 []

一個總是排除/移除的外掛陣列。

配置選項與 include 相同。

如果您不想使用 generators 並且不想包含 regeneratorRuntime(使用 useBuiltIns 時),或者使用了其他外掛(如 fast-async)而不是 Babel's async-to-gen,則此選項可以將 @babel/plugin-transform-regenerator 等轉換禁用。

useBuiltIns

"usage" | "entry" | false, 預設是 false

此選項將 core-js 模組直接引用為裸匯入。因此,core-js 將相對於檔案本身進行解析,並且是可被訪問的。如果沒有 core-js 依賴項或者有多個版本,您可能需要將 core-js@2 指定為應用程式中的頂級依賴項。

這個選項配置了 @babel/preset-env 如何處理 polyfills。

useBuiltIns: 'entry'

注意:只需要在你整個 app 中使用 require("@babel/polyfill"); 一次。多次對 @babel/polyfill 的匯入會導致全域性衝突和其他很難跟蹤的錯誤。我們推薦建立一個單獨的檔案處理 require 語句。

這個選項會啟用一個新的外掛,將 import "@babel/polyfill" 或者 require("@babel/polyfill") 替換為 @babel/polyfill 下的各個基於不同環境的單獨項匯入。

npm install @babel/polyfill --save
複製程式碼

輸入

import "@babel/polyfill";
複製程式碼

輸出(不同的配置環境下有所區別)

import "core-js/modules/es7.string.pad-start";
import "core-js/modules/es7.string.pad-end";
複製程式碼

也可以直接匯入 core-jsimport "core-js"; or require('core-js');

useBuiltIns: 'usage'(實驗性)

在每個檔案中使用 polyfill 時,為 polyfill 新增特定匯入。我們利用 bundler 只載入一次相同的 polyfill。

輸入

a.js

var a = new Promise();
複製程式碼

b.js

var b = new Map();
複製程式碼

輸出(如果當前配置環境不支援此特性)

import "core-js/modules/es6.promise";
var a = new Promise();
複製程式碼
import "core-js/modules/es6.map";
var b = new Map();
複製程式碼

輸出(如果當前配置環境支援此特性)

var a = new Promise();
複製程式碼
var b = new Map();
複製程式碼

useBuiltIns: false

既不會在每個檔案中自動新增 polyfill,也不會將 "@babel/polyfill" 匯入為單個 polyfill。

簡單總結

'usage''entry' 的區別:

  • 'usage' 無需在頭部引入 import '@babel/polyfill',它會自動根據當前的程式碼引入對應特性,並且只引用程式碼中用到的特性(browserslist 配置 + 程式碼用到)
  • 'entry' 需要在頭部引入 '@babel/polyfill',並且是根據配置環境引入對應的特性。程式碼中沒有用到,但環境中會缺失,也會引入。(只根據 browserslist 配置)

usage 風險項:由於我們通常會使用很多 npm 的 dependencies 包來進行業務開發,babel 預設是不會檢測 依賴包的程式碼的。 也就是說,如果某個 依賴包使用了 Array.from, 但是自己的業務程式碼沒有使用到該API,構建出來的 polyfill 也不會有 Array.from, 如此一來,可能會在某些使用低版本瀏覽器的使用者出現 BUG。 所以避免這種情況發生,一般開源的第三方庫釋出上線的時候都是轉換成 ES5 的。

corejs

corejs 配置項是決定當前 Babel 使用的版本,有 23 選項。

升級文件中已經說明了,最新版的 Babel7 @babel/polyfill 移除了 polyfill proposals,所以 @babel/polyfill 僅僅是 core-js v2 的別名。

所以這裡就需要注意的一點,如果使用 corejs: 2 + useBuiltIns: 'entry' 的話,就會報警告:

`@babel/polyfill` is deprecated. Please, use required parts of `core-js` and `regenerator-runtime/runtime` separately
複製程式碼

這裡需要使用的是 corejs: 3 + useBuiltIns: 'entry',才不會出錯。

forceAllTransforms

boolean, 預設 false

由於有了 Babel7 Javascipt config file 的支援,你可以根據是否設定了 production 來控制轉換。

module.exports = function(api) {
  return {
    presets: [
      [
        "@babel/preset-env",
        {
          targets: {
            chrome: 59,
            edge: 13,
            firefox: 50,
          },
          // for uglifyjs...
          forceAllTransforms: api.env("production"),
        },
      ],
    ],
  };
};
複製程式碼

注意: targets.uglify 已被廢棄,並且在下一個版本中被移除。

預設情況下,此預設將執行目標環境所需的所有變換。如果你要強制執行所有轉換,則啟用此選項可以在需要用到 UglifyJS 或僅支援 ES5 語法的某些場景下會很有用。

configPath

string, 預設是 process.cwd()

決定配置 browserslist 搜尋的起點,一直往上到系統根目錄,直到找到。

ignoreBrowserslistConfig

boolean, 預設是 false

切換是否使用 browserslist 配置源,包括搜尋任何 browserslist 檔案或引用package.json 中的 browserslist 鍵。這對於那些不走 Babel 編譯,但使用 browserslist 配置的專案非常有用。

shippedProposals

boolean, 預設是 false

切換啟用對瀏覽器中提供的內建特性的支援。如果你的目標環境對某一個特性提案(proposal)具有原生支援,則會啟用與其匹配的解析器語法外掛,而不是執行任何轉換。請注意,這不會啟用與 @babel/preset-stage-3 相同的轉換,因為這些提案可能在正式落地瀏覽器之前會有變更。

目前支援以下內容:

內建:

特性:

@babel/preset-stage-x(廢棄)

在 babel7 中,官方已經宣佈廢棄 babel stage preset 包,大概是考慮到廣泛使用的 stage-x 不適合社群的發展,具體原因見官方部落格

在新版的 babel 配置需要根據自己的需要下載對應的 proposal 外掛,因為 stage-x 本身也是這些外掛的集合,但不包含在 env 包中,比如安裝:@babel/plugin-proposal-function-bind,使用這些還沒正式進標準但社群已經廣為使用的語言特性。

具體使用的話,以前的各個 stage 等同於下面的各個外掛的集合:

{
  "plugins": [
    // Stage 0
    "@babel/plugin-proposal-function-bind",

    // Stage 1
    "@babel/plugin-proposal-export-default-from",
    "@babel/plugin-proposal-logical-assignment-operators",
    ["@babel/plugin-proposal-optional-chaining", { "loose": false }],
    ["@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" }],
    ["@babel/plugin-proposal-nullish-coalescing-operator", { "loose": false }],
    "@babel/plugin-proposal-do-expressions",

    // Stage 2
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    "@babel/plugin-proposal-function-sent",
    "@babel/plugin-proposal-export-namespace-from",
    "@babel/plugin-proposal-numeric-separator",
    "@babel/plugin-proposal-throw-expressions",

    // Stage 3
    "@babel/plugin-syntax-dynamic-import",
    "@babel/plugin-syntax-import-meta",
    ["@babel/plugin-proposal-class-properties", { "loose": false }],
    "@babel/plugin-proposal-json-strings"
  ]
}
複製程式碼

@babel/polyfill

介紹

文件:babeljs.io/docs/en/nex…

官網資訊:從 Babel 7.4.0 開始,這個包已經被廢棄了,以支援直接匯入 core-js/stable(polyfill ECMAScript 特性)和 regenerator-runtime/runtime(需要使用轉換後的 generator 函式)

babel-polyfill 的存在意義是給瀏覽器“打補丁”,比如瀏覽器沒有 Object.assign 這個特性,它會針對這個環境建立這個特性。Babel 自身是隻轉換語法,不新增丟失的特性,polyfill 的存在就是彌補瀏覽器這部分缺失的特性(比如某些 ie)。

babel-polyfill 等同於 regenerator runtime + core-js

  • regenerator:提供對 generator 支援,如果應用程式碼中用到generator、async函式的話。
  • core-js:提供 es 新的特性。

最新版的具體用法,見 @babel/preset-envuseBuiltIns 特性。

經過我的簡單實驗,其實可以不用專門安裝這個包,而且新的 corejs v3 和 corejs v2 還不太一樣(令人困惑)。使用 useBuiltIns` 就好。

副作用

引入 babel-polyfill 也會有一定副作用,比如:

  • 引入了新的全域性物件,比如Promise、WeakMap等。
  • 修改現有的全域性物件:比如修改了Array、String的原型鏈等。

在應用開發中,上述行為問題不大,基本可控。但如果在庫、工具的開發中引入 babel-polyfill,則會帶來潛在的問題。

舉個例子,在專案中定義了跟規範不一致的Array.from()函式,同時引入了一個庫(依賴 babel-polyfill),此時,這個庫可能覆蓋了自定義的Array.from()函式,導致出錯。

這就是 babel-runtime 存在的原因。它將開發者依賴的全域性內建物件等,抽取成單獨的模組,並通過模組匯入的方式引入,避免了對全域性作用域的修改(汙染)。

因此,如果是開發庫、工具,可以考慮使用 babel-runtime。

@babel/runtime

介紹

@babel/runtime 是一個包含 Babel modular runtime helpers 和 一系列 regenerator-runtime 的庫。

文件:babeljs.io/docs/en/bab…

使用場景

babel-runtime 一般用於兩種場景:

  • 開發庫/工具
  • 移除冗餘工具函式(helper function)。

與 babel-polyfill 的區別在於:

  • babel-polyfill 會修改(覆蓋)例項方法,這在業務層很有用,但某些場景,比如引用外在的技術庫,不希望這裡的的 polyfill 覆蓋業務程式碼中的方法。
  • babel-runtime 不會修改例項方法,它只是引入一些 helper 函式,創造對應的方法。

使用 babel-runtime 一般會搭配 babel-plugin-transform-runtime 使用。babel-plugin-transform-runtime 用於構建過程的程式碼轉換,而 babel-runtime 是實際匯入專案程式碼的功能模組。

@babel/plugin-transform-runtime

介紹

文件:babeljs.io/docs/en/bab…

babel 在每個需要的檔案的頂部都會插入一些 helpers 內聯程式碼,這可能會導致多個檔案都會有重複的 helpers 程式碼。@babel/plugin-transform-runtime 的 helpers 選項就可以把這些模組抽離出來。

@babel/plugin-transform-runtime 主要做了三件事情:core-js aliasing、helper aliasing、egenerator aliasing。

  • core-js aliasing:自動匯入babel-runtime/core-js,並將全域性靜態方法、全域性內建物件 對映到對應的模組。

  • helper aliasing:將內聯的工具函式移除,改成通過babel-runtime/helpers模組進行匯入,比如_classCallCheck工具函式。

  • regenerator aliasing:如果你使用了 async/generator 函式,則自動匯入 babel-runtime/regenerator模組。

Demo

module.exports = {
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": false, // boolean 或者 number, 預設 false,指定是否需要 runtime 的 corejs aliasing,如果使用 env 的 useBuiltIns + polyfill,使用 false。
                "helpers": true, // boolean, 預設 true,指定是否內聯 babel 的 helper 程式碼 (比如 classCallCheck, extends) 
                "regenerator": false, // 通過 preset-env 已經使用了全域性的 regeneratorRuntime, 不再需要 transform-runtime 提供的 不汙染全域性的 regeneratorRuntime
                "useESModules": true, // boolean, 預設 false,使用 es modules helpers, 減少 commonJS 語法程式碼
                "absoluteRuntime": false // boolean, 預設 false,是否目錄引用 runtime 包(有些專案會引用當前專案之外的程式碼,編譯時會找不到 runtime 包)
            }
        ]
    ]
}
複製程式碼

新增新配置前編譯出來的程式碼

import "core-js/modules/es6.promise";
import "regenerator-runtime/runtime";

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); }); }; }
複製程式碼

新增新配置後編譯出來的程式碼

import "core-js/modules/es6.promise";
import "regenerator-runtime/runtime";
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
複製程式碼

babel-register

babel-register 則提供了動態編譯。換句話說,我們的原始碼能夠真正執行在生產環境下,不需要 babel 編譯這一環節。

我們先在專案下安裝 babel-register

$ npm install --save-dev @babel/register
複製程式碼

然後在入口檔案中 require

require('@babel/register')
require('./app')
複製程式碼

在入口檔案頭部引入 @babel/register 後,我們的 app 檔案中即可使用任意 es2015 的特性。

當然,壞處是動態編譯,導致程式在速度、效能上有所損耗。所以這一項基本不用在正式的生產環境中使用。

babel-node

上面所說,babel-register 提供動態編譯,能夠讓我們的原始碼真正執行在生產環境下 - 但其實不然,我們仍需要做部分調整,比如新增一個入口檔案,並在該檔案中 require('@babel/register')。而 babel-node 能真正做到一行原始碼都不需要調整:

$ npm install --save-dev @babel/core @babel/node
$ npx babel-node app.js
複製程式碼

只是,請不要在生產環境中使用 babel-node,因為它是動態編譯原始碼,應用啟動速度非常慢。

一份可用的配置

安裝對應的包

依賴:

  • @babel/core(核心包)
  • @babel/preset-env(預設)
  • @babel/polyfill(v7.4.0似乎被廢棄)
  • @babel/runtime(開發業務程式碼可不用,開發技術庫可以使用)
  • @babel/plugin-transform-runtime(合併重複的 helper 函式)
  • @babel/plugin-proposal-function-bind(沒有 stage-x 後,需要安裝單獨的外掛,支援對應的 proposal 特性)

注意:這裡 @babel/polyfill 可裝可不裝,不裝似乎也不影響沒有影響,但不確定正式允執行的時候會不會報錯。看了下原始碼,其實很簡單,就是引用到 core-js v2 的特性。官方文件介紹已經被廢棄了。

babel.config.js

const presets = [
    [
      "@babel/env",
      {
        targets: {
          edge: "17",
          firefox: "60",
          chrome: "67",
          safari: "11.1",
          ie: '8'
        },
        useBuiltIns: 'usage',
        // Babel7 需要指定引入corejs的版本,最好使用3
        corejs: 3,
        modules: 'amd', // 需要轉換成什麼樣的模組系統
      },
    ],
  ];
  
const plugins = [
    // 幫助減少 helper 函式
    [
      "@babel/plugin-transform-runtime",
      {
          "corejs": false, // 預設值,可以不寫
          "helpers": true, // 預設,可以不寫
          "regenerator": false, // 通過 preset-env 已經使用了全域性的 regeneratorRuntime, 不再需要 transform-runtime 提供的 不汙染全域性的 regeneratorRuntime
          "useESModules": true, // 使用 es modules helpers, 減少 commonJS 語法程式碼
      }
    ],
    // 由於沒有了 stage-x,需要單獨匯入需要的外掛
    [
        '@babel/plugin-proposal-function-bind'
    ]
]
module.exports = { presets, plugins };
複製程式碼

參考

Babel 的使用

Babel 的配置資訊