由於 Babel 7.4 之後不再推薦使用 @babel/polyfill
,而 @babel/preset-env
和 plugin-transform-runtime
二者都可以設定 corejs
來處理 polyfill
。
@babel/polyfill
廢棄的主要原因有:
- 此包僅僅是引入了 stable core-js 和 regenerator-runtime/runtime,其中後者可以使用外掛 @babel/plugin-transform-regenerator 代替。
- 此包不能從core-js@2 平滑過渡到 core-js@3。
Babel 簡介
簡單來說,Babel
是一個編譯器,主要用於將採用 ECMAScript 2015+
語法編寫的程式碼轉換為向後相容的 JavaScript
語法,以便能夠執行在新舊版本的瀏覽器等各個環境中。而 Babel 程式碼轉換功能以 plugin
的方式實現,plugin
就是小型的 JavaScript 程式。
preset
可以被看作是一組 Babel 外掛或 options
配置的可共享模組。
- plugins 在 presets 前執行;
- plugins 從前往後順序執行;
- presets 根據排列順序倒序執行;
babel 主要實現的兩個功能:
- 轉換新語法。將新版 js 語法用舊版語法實現,從而在對應環境中執行,比如箭頭函式;
轉換新 API。為舊版執行時打補丁(也被稱為polyfill),從而使用在新版 js 中定義但在舊版執行時提供的功能,包括三類:
- 新定義的內建物件,例如
Promise
- 原有內建物件新新增的靜態方法,例如
Array.from
- 原有內建物件新新增的例項方法,例如
Array.prototype.includes
- 新定義的內建物件,例如
preset-env
preset-env
既可以轉換新語法,也可以通過配置轉換新的 API
。preset-env
的 polyfill
會汙染全域性環境。
target
這個欄位可以填寫 browserslist
的查詢字串,官方推薦使用 .browserslistrc
檔案去指明編譯的 target
,這個配置檔案還可以和autoprefixer
、stylelint
等工具一起共享配置。所以不推薦在.babelrc
的preset-env
配置中直接使用targets
進行配置。
如果需要單獨在這裡配置targets
的話,preset-env
中指明ignoreBrowserslistConfig
為true
則忽略.browserslistrc
的配置項。
useBuiltIns
是否使用其polyfill
功能(全域性環境的core-js)。有三個值:
false
:預設值。在不主動import
的情況下不使用preset-env
來實現polyfills
,只使用其預設的語法轉換功能。如果使用預設值false
,則應該避免在入口檔案引入polyfill
,使得打包體積過大。entry
:需要手動在入口處引入polyfill
,根據瀏覽器目標環境(targets
)的配置,引入全部瀏覽器暫未支援的polyfill
模組,無論在專案中是否使用到。import "core-js/proposals/string-replace-all"
usage
: 不需要手動在入口檔案引入polyfill
,Babel
將會根據程式碼使用情況自動注入polyfill
,如此一來在打包的時候將會相對地減少打包體積。
corejs
配置core-js,預設值"2.0"。此選項僅在與 useBuiltIns: usage
或 useBuiltIns: entry
一起使用時有效。
core-js: JavaScript 的模組化標準庫,包含 Promise
、Symbol
、Iterator
和許多其他的特性,它可以讓你僅載入必需的功能。
- version: 【string】版本號;
- proposals: 【boolean】是否實現提案中的特性;
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"chrome": "80" // 推薦使用 .browserslistrc
},
"useBuiltIns": "usage",
"corejs": {
"version": 3, // 2 和 3 版本都需要手動安裝庫:yarn add core-js@3
"proposals": false
}
}
]
]
}
plugin-transform-runtime
plugin-transform-runtime
主要做了三件事:
- 當開發者使用非同步或生成器的時候,自動引入
@babel/runtime/regenerator
,開發者不必在入口檔案做額外引入; 動態引入
polyfill
,提供沙盒環境,避免全域性環境的汙染;如果直接匯入 core-js 或 @babel/polyfill 以及它提供的 Promise、Set 和 Map 等內建元件,這些都會汙染全域性。雖然這不影響應用程式或命令列工具,但如果程式碼是要釋出給其他人使用的庫,或者無法準確控制程式碼將執行的環境,則會出現問題。
- 所有 helpers 幫助模組都將引用模組 @babel/runtime,以避免編譯輸出中的重複,減小打包體積;
corejs
配置值:false, 2, 或者 { version: number, proposals: boolean }
,預設值 false。
corejs | 安裝建議 |
---|---|
false | npm install --save @babel/runtime |
2 | npm install --save @babel/runtime-corejs2 |
3 | npm install --save @babel/runtime-corejs3 |
helpers
配置值 boolean 型別,預設值 true。
是否用對 moduleName 的呼叫替換內聯 Babel 幫助程式(classCallCheck、extends等)。
regenerator
配置值 boolean 型別,預設值 true。
是否將生成器函式轉換為使用不汙染全域性範圍的再生器執行時。
useESModules
配置值 boolean 型別,預設值 false。
啟用後,轉換將使用幫助程式,而不是@babel/plugin-transform-modules-commonjs。這允許在 webpack 等模組系統中進行較小的構建,因為它不需要保留 commonjs 語義。
使用場景與例項分析
@babel/preset-env
和 plugin-transform-runtime
二者都可以設定使用 corejs
來處理polyfill
,二者各有使用場景,在專案開發和類庫開發的時候可以使用不同的配置。
不要同時為二者配置 core-js,以免產生複雜的不良後果。
專案開發
useBuiltIns
使用 usage
。plugin-transform-runtime
只使用其移除內聯複用的輔助函式的特性,減小打包體積。
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": {
"version": 3,
"proposals": false
}
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": false
}
]
]
}
類庫開發
類庫開發儘量不使用汙染全域性環境的 polyfill
,因此 @babel/preset-env
只發揮語法轉換的功能, polyfill
由 plugin-transform-runtime
來處理,推薦使用 core-js@3
,並且不使用未進入規範的特性。
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": {
"version": 3,
"proposals": false
},
"useESModules": true
}
]
]
}
打包分析
測試程式碼如下:
// syntax
class Person {}
typeof Person
const array = [1, 2, 3]
const fun = () => {}
// api
const a = Array.isArray([3, 5, 8])
const b = array.map(itm => itm * 2)
const p1 = Promise.resolve(true)
const p2 = Promise.reject(false)
Promise.allSettled([p1, p2]).then(() => {
console.log('then')
}).catch(() => {
console.log('catch')
}).finally(() => {
console.log('finally')
})
通過 preset-env 配置 core-js:
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
require("core-js/modules/es.array.is-array.js");
require("core-js/modules/es.array.map.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
require("core-js/modules/es.promise.finally.js");
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/esnext.promise.all-settled.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/web.dom-collections.iterator.js");
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
// syntax
var Person = /*#__PURE__*/(0, _createClass2["default"])(function Person() {
(0, _classCallCheck2["default"])(this, Person);
});
(0, _typeof2["default"])(Person);
var array = [1, 2, 3];
var fun = function fun() {}; // api
var a = Array.isArray([3, 5, 8]);
var b = array.map(function (itm) {
return itm * 2;
});
var p1 = Promise.resolve(true);
var p2 = Promise.reject(false);
Promise.allSettled([p1, p2]).then(function () {
console.log('then');
})["catch"](function () {
console.log('catch');
})["finally"](function () {
console.log('finally');
});
通過 plugin-transform-runtime 配置 core-js:
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _isArray = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/is-array"));
var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _typeof2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/typeof"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
// syntax
var Person = /*#__PURE__*/(0, _createClass2["default"])(function Person() {
(0, _classCallCheck2["default"])(this, Person);
});
(0, _typeof2["default"])(Person);
var array = [1, 2, 3];
var fun = function fun() {}; // api
var a = (0, _isArray["default"])([3, 5, 8]);
var b = (0, _map["default"])(array).call(array, function (itm) {
return itm * 2;
});
var p1 = _promise["default"].resolve(true);
var p2 = _promise["default"].reject(false);
_promise["default"].allSettled([p1, p2]).then(function () {
console.log('then');
})["catch"](function () {
console.log('catch');
})["finally"](function () {
console.log('finally');
});
通過打包後程式碼分析,以 preset-env 的方式會引入不必須的 polyfill,以 plugin-transform-runtime 的方式只會引入當前頁面所需的 polyfill。