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


之前在用babel 的時候有個地方一直挺暈的,`@babel/preset-env` 和 `@babel/plugin-transform-runtime`都具有轉換語法的能力, 並且都能實現按需 `polyfill` ,但是網上又找不到比較明確的答案, 趁這次嘗試 roullp 的時候試了試. 如果我們什么都不做, 沒有為babel 編寫參數及配置, 那babel 並沒有那么大的威力, 它什么都不會做, 正是因為各個預設插件的靈活組合、賦能, 讓 babel 充滿魅力, 創造奇跡 首先是 @babel/preset-env ## @babel/preset-env 這是一個我們很常用的預設, 幾乎所有的教程和框架里都會讓你配置它, 它的出現取代了 `preset-es20**` 系列的babel 預設, 你再也不需要繁雜的兼容配置了。 每出一個新提案就加一個? 太蠢了。 有了它, 我們就可以擁有全部, 並且! 它還可以做到按需加載我們需要的 polyfill。 就是這么神奇。 但是吧, 它也不是那么自動化的, 如果你要是不會配置,很有可能就沒有用起它的功能 不管怎么養, 首先試一下,眼見為實 首先創建一個 `index.js `,內容如下, 很簡單 ```js function test() { new Promise() } test() const arr = [1,2,3,4].map(item => item * item) console.log(arr) ``` 然后我們在根目錄下創建一個 `.babelrc`文件, 幫我們剛剛說的預設加進去 ```js { "presets": [ ["@babel/preset-env"] ] } ``` 然后我我們打包一下(這里我用的是roullup) 看一下產出的結果 ![2019-12-03-21-00-52](https://img2018.cnblogs.com/blog/1397109/201912/1397109-20191204004433070-847521735.png) 我們可以看到, 它babel幫我們做了這幾件事情: 1. 轉換箭頭函數 2. const 變為 var 奇怪, 為什么 babel 不幫我們轉換 map ? 還有 promise 這些也都是es6的特性呀 嗯~,會不會是我們的目標瀏覽器不對, babel 覺得不需要轉換了, 會不會是這樣, 那我們加一個 .browserslistrc 試一下 那就。讓我們在根目錄下創建一個 `.browserslistrc` ![2019-12-03-21-06-54](https://img2018.cnblogs.com/blog/1397109/201912/1397109-20191204004433295-1734007870.png) 好。現在讓我們再打包一次. ![2019-12-03-21-07-43](https://img2018.cnblogs.com/blog/1397109/201912/1397109-20191204004433466-1461016989.png) 咦, 沒什么效果。 跟剛剛一樣啊。 說明不是目標瀏覽器配置的問題, 是babel 做不了這個事。 因為默認 @babel/preset-env 只會轉換語法,也就是我們看到的箭頭函數、const一類。 如果進一步需要轉換內置對象、實例方法,那就得用polyfill, 這就需要你做一點配置了, 這里有一個至關重要的參數 "useBuiltIns",他是控制 @babel/preset-env 使用何種方式幫我們導入 polyfill 的核心, 它有三個值可以選 ### entry 這是一種入口導入方式, 只要我們在打包配置入口 或者 文件入口寫入 `import "core-js"` 這樣一串代碼, babel 就會替我們根據當前你所配置的目標瀏覽器(browserslist)來引入所需要的polyfill 。 像這樣, 我們在 index.js 文件中加入試一下`core-js` ```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配置如下 ```js{ [ "presets": [ ["@babel/preset-env", { "useBuiltIns": "entry" } ] ] } ``` 當前 .browserslistrc 文件(更改目標瀏覽器為 Chrome 是為了此處演示更直觀,簡潔), 我們只要求兼容 chrome 50版本以上即可(當下最新版本為78) ```js Chrome > 50 ``` 那打包后如何呢? ![2019-12-03-21-46-14](https://img2018.cnblogs.com/blog/1397109/201912/1397109-20191204004440472-1487849830.png) 恐怖如斯啊,babel把我們填寫的 `import "core-js"`替換掉, 轉而導入了一大片的polyfill, 而且都是一些我沒有用到的東西。 那我們提升一下目標瀏覽器呢? 它還會導入這么多嗎? 此時, 我們把目標瀏覽器調整為比較接近最新版本的 75(當下最新版本為78) ```text // .browserslistrc Chrome > 75 ``` 此刻打包后引入的 polyfill 明顯少了好多。 ![2019-12-03-21-51-46](https://img2018.cnblogs.com/blog/1397109/201912/1397109-20191204004440902-500428601.png) 但同樣是我們沒用過的。 這也就是印證了上面所說的, 當 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` ```js { "presets": [ ["@babel/preset-env", { "useBuiltIns": "usage", "corejs": 3 } ] ] } ``` 此時的 index.js ```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 ```text > 1% last 10 versions not ie <= 8 ``` 打包后: ![2019-12-03-22-59-03](https://img2018.cnblogs.com/blog/1397109/201912/1397109-20191204004441553-334131430.png) 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 ```js { "presets": [ ["@babel/preset-env"] ], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 3 } ] ] } ``` 當前的 index.js ```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](https://img2018.cnblogs.com/blog/1397109/201912/1397109-20191204004449158-550004880.png) 我們看到使用 @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 ```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 ```js { "presets": [ ["@babel/preset-env", { "useBuiltIns": "usage", "corejs": 3 } ] ], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 3 } ] ] } ``` 打包結果: ![2019-12-04-00-23-24](https://img2018.cnblogs.com/blog/1397109/201912/1397109-20191204004452095-2040070700.png) ### entry 和 @babel/runtime 跟 useage 的情況不一樣, entry 模式下, 在經過 @babel/runtime 處理后不但有了各種幫助函數還引入了許多polyfill, 這就會導致打包體積無情的增大 > 個人分析: entry 模式下遭遇到入口的 `import "core-js"` 及就立即替換為當前目標瀏覽器下所需的所有 polyfill, 所以也就跟 @babel/runtime 互不沖突了, 導致了重復引入代碼的問題, 所以這兩種方式千萬不要一起使用, 二選一即可 實現過程如下: 當前 index.js: ```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 ```js { "presets": [ ["@babel/preset-env", { "useBuiltIns": "entry" } ] ], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 3 } ] ] } ``` 當前 .browserslistrc 的目標版本(為了減少打包后的文件行數為又改為chrome 了, 懂那個意思就行) ```text Chrome > 70 ``` 打包結果: ![2019-12-04-00-32-40](https://img2018.cnblogs.com/blog/1397109/201912/1397109-20191204004504635-997113291.png) ## 總結 1. @babel/preset-env 擁有根據 useBuiltIns 參數的多種polyfill實現,優點是覆蓋面比較全(entry), 缺點是會污染全局, 推薦在業務項目中使用 * entry 的覆蓋面積全, 但是打包體積自然就大, * useage 可以按需引入 polyfill, 打包體積就小, 但如果打包忽略node_modules 時如果第三方包未轉譯則會出現兼容問題 2. @babel/runtime 在 babel 7.4 之后大放異彩, 利用 corejs 3 也實現了各種內置對象的支持, 並且依靠 @babel/plugin-transform-runtime 的能力,沙箱墊片和代碼復用, 避免幫助函數重復 inject 過多的問題, 該方式的優點是不會污染全局, 適合在類庫開發中使用 上面 1, 2 兩種方式取其一即可, 同時使用沒有意義, 還可能造成重復的 polyfill 文件


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM