Babel7 相關(guān)
@babel/preset-env
@babel/preset-env 主要的功能是依據(jù)項目經(jīng)過 babel 編譯構(gòu)建后產(chǎn)生的代碼所對應(yīng)運行的目標(biāo)平臺。@babel/preset-env 內(nèi)部依賴了很多插件: @babel/plugin-transform-*。這些插件的工作主要就是 babel 在處理代碼的過程當(dāng)中對于新的 ES 語法的轉(zhuǎn)換,將高版本的語法轉(zhuǎn)化為低版本的寫法。例如 @babel/plugin-transform-arrow-function 是用來轉(zhuǎn)化箭頭函數(shù)語法的。
基本的配置方法:
// babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
// 相關(guān) preset 的配置
}
]
]
}
對于 web 側(cè)的項目或者 基于 Electron 的項目,一般會搭配著 .browserlistrc (或 package.json 里的 browserslist 字段) 來使用(確定最終構(gòu)建平臺)。
相關(guān) options 配置
useBuiltIns
"usage" | "entry" | false, defaults to false.
這個配置選項也決定了 @babel/preset-env 如何去引用 polyfills。當(dāng)這個配置選項為:usage 或 entry,@babel/preset-env 會直接建立起對于 core-js 相關(guān) module 的引用。因此這也意味著 core-js 會被解析為對應(yīng)的相對路徑同時需要確保 core-js 在你的項目當(dāng)中已經(jīng)被安裝了。
因為從 @babel/polyfill 從 7.4.0 版本開始就被棄用了,因此推薦直接配置 corejs 選項,并在項目當(dāng)中直接安裝 core-js。
useBuiltIns: 'entry'
使用這種方式的配置需要在你的業(yè)務(wù)代碼當(dāng)中注入:
import 'core-js/stable'
import 'regenerator-runtime/runtime'
在 babel 處理代碼的過程當(dāng)中,會引入一個新的插件,同時 @babel/preset-env 會根據(jù)目標(biāo)平臺,例如 target 當(dāng)中的配置,或者是 .browserlistrc 等來引入對應(yīng)平臺所需要的 polyfill :
In:
import 'core-js'
Out(different based on environment):
import "core-js/modules/es.string.pad-start"
import "core-js/modules/es.string.pad-end"
注:其實這里的 useBuiltIns: entry 的配置以及需要在業(yè)務(wù)代碼當(dāng)中需要注入 core-js和 regenerator-runtime/runtime,在業(yè)務(wù)代碼當(dāng)中注入對應(yīng)的 package 從使用上來講更多的是起到了占位的作用,由 @babel/preset-env 再去根據(jù)不同的目標(biāo)平臺去引入對應(yīng)所需要的 polyfill 文件
同時在使用的過程中,如果是 import 'core-js' 那么在處理的過程當(dāng)中會引入所有的 ECMAScript 特性的 polyfill,如果你只希望引入部分的特性,那么可以:
In:
import 'core-js/es/array'
import 'core-js/proposals/math-extensions'
Out:
import "core-js/modules/es.array.unscopables.flat";
import "core-js/modules/es.array.unscopables.flat-map";
import "core-js/modules/esnext.math.clamp";
import "core-js/modules/esnext.math.deg-per-rad";
import "core-js/modules/esnext.math.degrees";
import "core-js/modules/esnext.math.fscale";
import "core-js/modules/esnext.math.rad-per-deg";
import "core-js/modules/esnext.math.radians";
import "core-js/modules/esnext.math.scale";
useBuiltIns: 'usage'
自動探測代碼當(dāng)中使用的新的特性,并結(jié)合目標(biāo)平臺來決定引入對應(yīng)新特性的 polyfill,因此這個配置是會最大限度的去減少引入的 polyfill 的數(shù)量來保證最終生成的 bundler 體積大小。
不過需要注意的是:由于 babel 處理代碼本來就是一個非常耗時的過程,因此在我們實際的項目當(dāng)中一般是對于 node_modules 當(dāng)中的 package 進行 exclude 配置給忽略掉的,除非是一些明確需要走項目當(dāng)中的 babel 編譯的 package 會單獨的去 include,所以 useBuiltIns: 'usage' 這種用法的話有個風(fēng)險點就是 node_modules 當(dāng)中的第三方包在實際的編譯打包處理流程當(dāng)中沒有被處理(例如有些 package 提供了 esm 規(guī)范的源碼,同時 package.json 當(dāng)中也配置了 module 字段,那么例如使用 webpack 這樣的打包工具的話會引入 module 字段對應(yīng)的入口文件)
同時,如果使用 useBuiltIns: 'usage' 配置的話。是會在每個文件當(dāng)中去引入相關(guān)的 polyfill 的,所以這里如果不借助 webpack 這種打包工具的話,是會造成代碼冗余的。
useBuiltIns: false
Don't add polyfills automatically per file, and don't transform import "core-js" or import "@babel/polyfill" to individual polyfills.
corejs
corejs 的配置選項需要搭配著 useBuiltIns: usage 或 useBuiltIns: entry 來使用。默認(rèn)情況下,被注入的 polyfill 都是穩(wěn)定的已經(jīng)被納入 ECMAScript 規(guī)范當(dāng)中的特性。如果你需要使用一些 proposals 當(dāng)中的 feature 的話,那么需要配置:
{
"presets": [
[
"@babel/preset-env",
{
// 相關(guān) preset 的配置
corejs: {
version: 3,
proposals: true
}
}
]
]
}
@babel/plugin-transform-runtime
出現(xiàn)的背景:
Babel 在編譯處理代碼的過程當(dāng)中會使用一些 helper 輔助函數(shù),例如 _extend。這些輔助函數(shù)一般都會被添加到每個需要的被處理的文件當(dāng)中。
因此 @babel/plugin-transform-runtime 所要解決的問題就是將所有對于需要這些 helper 輔助函數(shù)的引入全部指向 @babel/runtime/helpers 這個 module 當(dāng)中的輔助函數(shù),而不是給每個文件都添加對應(yīng) helper 輔助函數(shù)的內(nèi)容。
另外一個目的就是去創(chuàng)建一個沙盒環(huán)境。因為如果你直接引入 core-js,或者 @babel/polyfill 的話,它所提供的 polyfill,例如 Promise,Set,Map 等,是直接在全局環(huán)境下所定義的。因此會影響到所有使用到這些 API 的文件內(nèi)容。所以如果你是寫一個 library 的話,最好使用 @babel/plugin-transform-runtime 來完成相關(guān) polyfill 的引入,這樣能避免污染全局環(huán)境。
這個插件所做的工作其實也是引用 core-js 相關(guān)的模塊來完成 polyfill 的功能。最終所達到的效果和使用 @babel/polyfill 是一樣的。
配置方法:
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}
The plugin defaults to assuming that all polyfillable APIs will be provided by the user. Otherwise the corejs option needs to be specified.
需要注意的是不同 corejs 版本提供的 helpers 有一些功能上的差異:corejs: 2 僅支持全局的定義,例如 Promise,和一些靜態(tài)方法,例如 Array.from,實例上的方法是是不支持的,例如 [].includes。不過 corejs: 3 是支持實例上的方法的。
默認(rèn)情況下,@babel/plugin-transform-runtime 是不會引入對于 proposals 的 polyfill 的,如果你是使用 corejs: 3 的話,可以通過配置 proposal: true 來開啟這個功能。

技術(shù)實現(xiàn)細節(jié)
The transform-runtime transformer plugin does three things:
Automatically requires @babel/runtime/regenerator when you use generators/async functions (toggleable with the regenerator option). Can use core-js for helpers if necessary instead of assuming it will be polyfilled by the user (toggleable with the corejs option) Automatically removes the inline Babel helpers and uses the module @babel/runtime/helpers instead (toggleable with the helpers option).
Some tips
如果使用 @babel/preset-env 走 useBuiltIns: usage 搭配 browserlist 的這種 polyfill 的方式的話,polyfill 是會污染全局的(entry 模式也是污染全局)。不過這種配置的方式會依據(jù)目標(biāo)打包平臺來一定程度上減少不需要被加入到編譯打包流程的 polyfill 的數(shù)量,因此這種方式也對應(yīng)的能較少 bundle 最終的體積大小。 如果是走 @babel/plugin-transform-runtime 插件的 polyfill 的話不會污染全局。但是這個插件沒法利用 browserlist 的目標(biāo)平臺配置的策略。因此在你代碼當(dāng)中只要是使用了 ES6+ 的新 api,一律都會引入對應(yīng)的 polyfill 文件(而不考慮這個新的 api 是否被目標(biāo)瀏覽器已經(jīng)實現(xiàn)了),這樣也會造成 bundle 體積增大。針對這個問題,官方也嘗試提供一個新的 babel-polyfills package,以及策略去解決類似的問題。詳見對應(yīng)的文檔以及issue

