Babel 社群概覽
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";
orrequire('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
。
由於有了 Babel7Javascipt 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 @babel/runtime @babel/plugin-transform-runtime @babel/plugin-proposal-function-bind
注意:這裡@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 }; 複製程式碼