@babel/preset-env 與@babel/plugin-transform-runtime 使用及場景區別

以前在用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幫咱們作了這幾件事情:

  1. 轉換箭頭函數
  2. 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


那打包後如何呢?

2019-12-03-21-46-14

恐怖如斯啊,babel把咱們填寫的 import "core-js"替換掉, 轉而導入了一大片的polyfill, 並且都是一些我沒有用到的東西。

那咱們提高一下目標瀏覽器呢? 它還會導入這麼多嗎?
此時, 咱們把目標瀏覽器調整爲比較接近最新版本的 75(當下最新版本爲78)

// .browserslistrc

Chrome > 75


此刻打包後引入的 polyfill 明顯少了好多。

2019-12-03-21-51-46
但一樣是咱們沒用過的。
這也就是印證了上面所說的, 當 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


打包後:

2019-12-03-22-59-03
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) )


打包後以下:

2019-12-04-00-01-39
咱們看到使用 @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
      }
    ]
  ]
}


打包結果:

2019-12-04-00-23-24

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


打包結果:

2019-12-04-00-32-40

總結

  1. @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 文件

相關文章
相關標籤/搜索