剛接觸bable的同學可能會認為使用了Babel,配置了preset預設后就可以愉快的使用 es6+做開發了,事實上,在默認情況下Babel只會做語法轉換(let、const、class、箭頭函數等),而不做新api的轉換,新的api總結起來分為兩類:
- 全局對象和全局對象相關的方法,例如Promise、Map、Set、Object.assign......
- 實例的新方法,例如數組的find、flat等等......
想讓es6+的api在不支持的瀏覽器上運行,就需要借助polyfill技術,它使用一系列低版本的js代碼模擬了瀏覽器原生api的實現,俗稱打補丁。效果對比請點我。
要使用babel,需要安裝@babel/core、@babel/preset-env,如果是直接使用babel編譯,還需要安裝@babel/cli。
1. 手動引入方式補丁包
在babel 7.4之前,只需要安裝@babel/polyfill這個包就可以了;從7.4版本開始,雖然@babel/polyfill還會更新,但它內部的core-js包版將一直使用2.x,無法使用core-js 3.x中新增的補丁代碼,例如數組的includes方法等,因此官方建議直接安裝core-js和regenerator-runtime這兩個包。
接下來在在項目的入口文件中引入補丁包:
//index.js import “core-js” import “regenerator-runtime/runtime”
也可以在webpack配置文件的entry節點引入,例:“entry”:['core-js', 'regenerator-runtime/runtime', './index.js']。
這種方法會引入所有的補丁代碼,導致打包出來的代碼體積巨大,因此還需要使用@babel/preset根據目標瀏覽器版本引入瀏覽器未實現的api補丁。
改進:
babel使用browserslist庫的語法格式來聲明瀏覽器版本,聲明瀏覽器版本既可以在package.json中,也可以在babel配置中,建議在package.json中進行聲明,這樣其他的工具也可以共享該配置。package.json中的聲明方法是增加一個“browserslist”節點,值是數組格式,聲明好后可以通過運行npx browserslist命令檢查聲明是否有效。
接下來在babel的配置文件中為@babel/preset-env增加如下的配置:
module.exports = { presets: [["@babel/preset-env", { useBuiltIns: "entry", "corejs": 3, }]], }
注意:如果引入的是@babel/polyfill庫,則corejs版本需要指定為2
2. 自動引入方式
開發者無需在入口處引入core-js和regenerator-runtime, babel會根據目標瀏覽器版本+項目中實際用到的api來按需引入,打包出來的代碼體積更小。
要實現自動引入,首先需要在package.json文件中聲明目標瀏覽器及版本,然后可以使用下面兩種方法中的一種:
- 將@babel/preset-env中的useBuiltIns的值改為usage。
- 使用@babel/plugin-transform-runtime插件。此種方式不需要安裝core-js、regenerator-runtime,只需要安裝@babel/runtime-corejs3和@babel/plugin-transform-runtime,並將babel配置修改為:
module.exports = { presets:['@babel/preset-env'], plugins: [ ["@babel/plugin-transform-runtime", { "corejs": 3 }] ] }
}
這兩種方式有何差別?以目標瀏覽器版本為firefox 60、使用數組flat方法為例,兩種方法引入的補丁代碼如下:
方法1:
"use strict"; require("core-js/modules/es.array.flat.js"); require("core-js/modules/es.array.unscopables.flat.js"); var arr = [[1, 2, [3, 4]]]; var b = arr.flat();
方法2:
"use strict"; var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); var _flat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/flat")); var arr = [[1, 2, [3, 4]]]; var b = (0, _flat.default)(arr).call(arr);
從上面的代碼對比中可以看到,方法1通過修改Array對象的原型鏈方式實現了api;方法2通過一個局部函數來實現api,避免了對全局對象的污染。既然兩種方法都能實現打補丁,該如何選擇?若是開發項目,應采用方法1,可節省代碼、縮減代碼體積;若是開發組件或庫等具備復用的代碼,應采用方法2,原因是當庫和項目使用了同一api,但庫的兼容目標需要使用補丁代碼,而項目的兼容目標中該api已由瀏覽器原生實現時,若采用方法1,則庫和項目調用該api時,使用的都是js模擬代碼,模擬代碼的執行效率肯定沒有瀏覽器原生api實現代碼執行效率高。