以前在用babel 的時候有個地方一直挺暈的,@babel/preset-env 和 @babel/plugin-transform-runtime都具備轉換語法的能力, 而且都能實現按需 polyfill ,可是網上又找不到比較明確的答案, 趁此次嘗試 roullp 的時候試了試.javascript
若是咱們什麼都不作, 沒有爲babel 編寫參數及配置, 那babel 並無那麼大的威力, 它什麼都不會作, 正是由於各個預設插件的靈活組合、賦能, 讓 babel 充滿魅力, 創造奇蹟java
首先是 @babel/preset-envnode
@babel/preset-env
這是一個咱們很經常使用的預設, 幾乎全部的教程和框架裏都會讓你配置它, 它的出現取代了 preset-es20** 系列的babel 預設, 你不再須要繁雜的兼容配置了。 每出一個新提案就加一個? 太蠢了。es6
有了它, 咱們就能夠擁有所有, 而且! 它還能夠作到按需加載咱們須要的 polyfill。 就是這麼神奇。
可是吧, 它也不是那麼自動化的, 若是你要是不會配置,頗有可能就沒有用起它的功能chrome
無論怎麼養, 首先試一下,眼見爲實promise
首先建立一個 index.js,內容以下, 很簡單瀏覽器
function test() { new Promise() } test() const arr = [1,2,3,4].map(item => item * item) console.log(arr)
而後咱們在根目錄下建立一個 .babelrc文件, 幫咱們剛剛說的預設加進去babel
{ "presets": [ ["@babel/preset-env"] ] }
而後我咱們打包一下(這裏我用的是roullup)框架
看一下產出的結果函數
咱們能夠看到, 它babel幫咱們作了這幾件事情:
- 轉換箭頭函數
- const 變爲 var
奇怪, 爲何 babel 不幫咱們轉換 map ? 還有 promise 這些也都是es6的特性呀
嗯~,會不會是咱們的目標瀏覽器不對, babel 以爲不須要轉換了, 會不會是這樣, 那咱們加一個 .browserslistrc 試一下
那就。讓咱們在根目錄下建立一個 .browserslistrc
好。如今讓咱們再打包一次.
咦, 沒什麼效果。 跟剛剛同樣啊。 說明不是目標瀏覽器配置的問題, 是babel 作不了這個事。
由於默認 @babel/preset-env 只會轉換語法,也就是咱們看到的箭頭函數、const一類。
若是進一步須要轉換內置對象、實例方法,那就得用polyfill, 這就須要你作一點配置了,
這裏有一個相當重要的參數 「useBuiltIns」,他是控制 @babel/preset-env 使用何種方式幫咱們導入 polyfill 的核心, 它有三個值能夠選
entry
這是一種入口導入方式, 只要咱們在打包配置入口 或者 文件入口寫入 import "core-js" 這樣一串代碼, babel 就會替咱們根據當前你所配置的目標瀏覽器(browserslist)來引入所須要的polyfill 。
像這樣, 咱們在 index.js 文件中加入試一下core-js
// src/index.js import "core-js"; function test() { new Promise() } test() const arr = [1,2,3,4].map(item => item * item) console.log(arr)
babel配置以下
[ "presets": [ ["@babel/preset-env", { "useBuiltIns": "entry" } ] ] }
當前 .browserslistrc 文件(更改目標瀏覽器爲 Chrome 是爲了此處演示更直觀,簡潔), 咱們只要求兼容 chrome 50版本以上便可(當下最新版本爲78)
Chrome > 50
那打包後如何呢?
恐怖如斯啊,babel把咱們填寫的 import "core-js"替換掉, 轉而導入了一大片的polyfill, 並且都是一些我沒有用到的東西。
那咱們提高一下目標瀏覽器呢? 它還會導入這麼多嗎?
此時, 咱們把目標瀏覽器調整爲比較接近最新版本的 75(當下最新版本爲78)
// .browserslistrc Chrome > 75
此刻打包後引入的 polyfill 明顯少了好多。
但一樣是咱們沒用過的。
這也就是印證了上面所說的, 當 useBuiltIns 的值爲 entry 時, @babel/preset-env 會按照你所設置的目標瀏覽器在入口處來引入所需的 polyfill,
無論你需不須要。
如此,咱們能夠知道, useBuiltIns = entry 的優勢是覆蓋面積就比較廣, 一股腦所有搞定, 可是缺點就是打出來的包就大了多了不少沒有用到的 polyfill, 而且還會污染全局
useage
這個就比較神奇了, useBuiltIns = useage 時,會參考目標瀏覽器(browserslist) 和 代碼中所使用到的特性來按需加入 polyfill
固然, 使用 useBuiltIns = useage, 還須要填寫另外一個參數 corejs 的版本號,
core-js 支持兩個版本, 2 或 3, 不少新特性已經不會加入到 2 裏面了, 好比: flat 等等最新的方法, 2 這個版本里面都是沒有的, 因此建議你們用3
此時的 .babelrc
{ "presets": [ ["@babel/preset-env", { "useBuiltIns": "usage", "corejs": 3 } ] ] }
此時的 index.js
function test() { new Promise() } test() const arr = [1,2,3,4].map(item => item * item) const hasNumber = (num) => [4, 5, 6, 7, 8].includes(num) console.log(arr) console.log( hasNumber(2) )
此時的 .browserslistrc
> 1% last 10 versions not ie <= 8
打包後:
nice ,夠神奇, 咱們用的幾個新特性真的統統都加上了
這種方式打包體積不大,可是若是咱們排除node_modules/目錄,趕上沒有通過轉譯的第三方包,就檢測不到第三方包內部的 ‘hello‘.includes(‘h‘)這種句法,這時候咱們就會遇到bug
false
剩下最後一個 useBuiltIns = false , 那就簡單了, 這也是默認值 , 使用這個值時不引入 polyfill
@babel/runtime
這種方式會藉助 helper function 來實現特性的兼容,
而且利用 @babel/plugin-transform-runtime 插件還能以沙箱墊片的方式防止污染全局, 並抽離公共的 helper function , 以節省代碼的冗餘
也就是說 @babel/runtime 是一個核心, 一種實現方式, 而 @babel/plugin-transform-runtime 就是一個管家, 負責更好的重複使用 @babel/runtime
@babel/plugin-transform-runtime 插件也有一個 corejs 參數須要填寫
版本2 不支持內置對象 , 但自從Babel 7.4.0 以後,擁有了 @babel/runtime-corejs3 , 咱們能夠放心使用 corejs: 3 對實例方法作支持
當前的 .babelrc
{ "presets": [ ["@babel/preset-env"] ], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 3 } ] ] }
當前的 index.js
function test() { new Promise() } test() const arr = [1,2,3,4].map(item => item * item) const hasNumber = (num) => [4, 5, 6, 7, 8].includes(num) console.log(arr) console.log( hasNumber(2) )
打包後以下:
咱們看到使用 @babel/plugin-transform-runtime 編譯後的代碼和以前的 @babel/preset-env 編譯結果大不同了,
它使用了幫助函數, 而且賦予了別名 , 抽出爲公共方法, 實現複用。 好比它用了 _Promise 代替了 new Promise , 從而避免了建立全局對象
上面兩種方式一塊兒用會怎麼樣
useage 和 @babel/runtime
useage 和 @babel/runtime 同時使用的狀況下比較智能, 並無引入重複的 polyfill
我的分析緣由應該是: babel 的 plugin 比 prset 要先執行, 因此preset-env 獲得了 @babel/runtime 使用幫助函數包裝後的代碼,而 useage 又是檢測代碼使用哪些新特性來判斷的, 因此它拿到手的只是一堆 幫助函數, 天然沒有效果了
實驗過程以下:
當前index.js
function test() { new Promise() } test() const arr = [1,2,3,4].map(item => item * item) const hasNumber = (num) => [4, 5, 6, 7, 8].includes(num) const hasNumber2 = (num) => [4, 5, 6, 7, 8, 9].includes(num) console.log(arr) console.log( hasNumber(2)) console.log( hasNumber2(3) )
當前 .babelrc
{ "presets": [ ["@babel/preset-env", { "useBuiltIns": "usage", "corejs": 3 } ] ], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 3 } ] ] }
打包結果:
entry 和 @babel/runtime
跟 useage 的狀況不同, entry 模式下, 在通過 @babel/runtime 處理後不但有了各類幫助函數還引入了許多polyfill, 這就會致使打包體積無情的增大
我的分析: entry 模式下遭遇到入口的 import "core-js" 及就當即替換爲當前目標瀏覽器下所需的全部 polyfill, 因此也就跟 @babel/runtime 互不衝突了, 致使了重複引入代碼的問題, 因此這兩種方式千萬不要一塊兒使用, 二選一便可
實現過程以下:
當前 index.js:
import "core-js" function test() { new Promise() } test() const arr = [1,2,3,4].map(item => item * item) const hasNumber = (num) => [4, 5, 6, 7, 8].includes(num) const hasNumber2 = (num) => [4, 5, 6, 7, 8, 9].includes(num) console.log(arr) console.log( hasNumber(2)) console.log( hasNumber2(3) )
當前 .babelrc
{ "presets": [ ["@babel/preset-env", { "useBuiltIns": "entry" } ] ], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 3 } ] ] }
當前 .browserslistrc 的目標版本(爲了減小打包後的文件行數爲又改成chrome 了, 懂那個意思就行)
Chrome > 70
打包結果:
總結
- @babel/preset-env 擁有根據 useBuiltIns 參數的多種polyfill實現,優勢是覆蓋面比較全(entry), 缺點是會污染全局, 推薦在業務項目中使用
1. entry 的覆蓋面積全, 可是打包體積天然就大,
2. useage 能夠按需引入 polyfill, 打包體積就小, 但若是打包忽略node_modules 時若是第三方包未轉譯則會出現兼容問題。
2. @babel/runtime 在 babel 7.4 以後大放異彩, 利用 corejs 3 也實現了各類內置對象的支持, 而且依靠 @babel/plugin-transform-runtime 的能力,沙箱墊片和代碼複用, 避免幫助函數重複 inject 過多的問題, 該方式的優勢是不會污染全局, 適合在類庫開發中使用
上面 1, 2 兩種方式取其一便可, 同時使用沒有意義, 還可能形成重複的 polyfill 文件