本文釋出於 2019-04-15,總結了 babel 社群的工具使用,以及如何合理地進行配置。如果要看結論的話,直接跳到文章最後一節。
目錄:
@babel/preset-env
@babel/preset-stage-x
(廢棄)@babel/polyfill
@babel/runtime
@babel/plugin-transform-runtime
babel-register
babel-node
- 一份可用的配置
- 參考
@babel/preset-env
介紹
babel-preset-env 是一系列外掛的合集,官方已不用再使用 preset-201x 和 preset-latst 之類的包,env 包可以根據配置自動處理相容程式碼。
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
將不會轉換模組。
也要注意 cjs
是 commonjs
的別名。
debug
boolean
, 預設 false
。
通過 console.log
輸出使用的 targets/plugins 和外掛資料中指定的版本資訊。
include
Array<string|RegExp>
,預設 []
。
一個總是包含的外掛陣列。
以下是有效的配置:
- Babel 外掛,
@babel/plugin-transform-spread
和不帶字首的plugin-transform-spread
的寫法都支援。 - 內建特性,比如
es6.map
,es6.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
外掛。
注意:
include
和exclude
選項僅適用於此預設中包含的外掛; 因此,在選項中包含@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-js
(import "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 使用的版本,有 2
和 3
選項。
升級文件中已經說明了,最新版的 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
介紹
官網資訊:從 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-env
的 useBuiltIns
特性。
經過我的簡單實驗,其實可以不用專門安裝這個包,而且新的 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 的庫。
使用場景
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
介紹
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 };
複製程式碼