剛復工的時候我司業務太多了,我已不記得咱們連續做戰了多少天,最近算是有時間能夠學習學習個人babel大寶貝了,上週末看了下babel的一些核心模塊以及babel的一些配置,今天繼續以博客的形式記錄總結下來。react
@babel/core
,@babel/cli
,@babel/plugin*
,@babel/preset-env
,@babel/polyfill
,@babel/runtime
,@babel/plugin-transform-runtime
。@babel/core 這個包裏主要都是一些去對代碼進行轉換的核心方法,具體裏面的一些api方法我就不作介紹了;引用官網的一句話:「全部轉換將使用本地配置文件」,因此待會兒咱們的babel.config.js配置文件就很重要了;再一個core就是核心的意思嘛,因此咱們話很少說先把它裝起來,gogogoios
npm install --save-dev @babel/core
這個 @babel/cli就是babel帶有的內置cli,能夠用來讓咱們從命令行來編譯咱們的文件,有了他咱們就很方便的對babel進行學習了,那話很少說,先裝起來?git
npm install --save-dev @babel/cli
裝完以後你就能夠這樣來編譯你的文件:es6
npx babel study.js --watch --out-file study-compiled.js
簡單介紹下上面命令用到的幾個參數:--out-file
用來把編譯後的目標文件輸出到對應的文件;若是但願在每次更改目標文件時都進行編譯,能夠加上 --watch
選項;固然還有一些別的選項,不過在我學習babel以及配置的話,這兩個選項已經夠我用了。github
在操做的過程當中若是改了babel配置發現編譯出來的文件並無實時編譯的狀況,須要注意下,若是改了配置文件那就須要從新執行這段命令,要否則babel讀不到新的配置。web
若是你已經建立了study.js文件而且執行了這段命令,你會發現,對應的study-compiled.js還沒發生變化,由於咱們還沒開始寫babel的配置文件,莫慌,立刻開始。
npm
babel插件和babel預設是babel配置的兩個主要模塊,因此我就放在一塊兒說了。json
/* babel.config.js */ module.exports = { presets: [ ], plugins: [ "@babel/plugin-transform-arrow-functions" ] }
而後執行咱們上面那段cli的命令,就會獲得這種效果:api
/* study.js */ const study = () => {} /* study-compiled.js */ const study = function () {};
固然,若是咱們想要使用es6給數值新增的指數運算符怎麼辦,只須要添加相應的 @babel/plugin-transform-exponentiation-operator 插件便可:promise
/* babel.config.js */ module.exports = { presets: [ ], plugins: [ "@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-exponentiation-operator" ] }
對應的es6語法就會變成:
/* study.js */ const exponentiation = 2 ** 2 /* study-compiled.js */ const exponentiation = Math.pow(2, 2);
從上面的插件化咱們就知道須要哪一個插件就去引入就完事兒,那麼問題來了,es6新增的語法那麼多,我總不能一個插件一個插件去添加吧,這樣也太麻煩了,這個時候就要用到babel提供的presets了。
presets也就是預設的意思,大概意思就是能夠預先設定好一些東西,就免得咱們一個個的去引入插件了。官方提供了不少presets,好比preset-env(處理es6+規範語法的插件集合)、preset-stage(一些處理尚在提案階段的語法的插件集合,固然這種預設的方式在 babel 7+ 版本已經被廢棄了)、preset-react(處理react語法的插件集合)等等。
咱們主要來介紹下preset-env:preset-env是一個智能預設,配置了它就可讓你用es6+去書寫你的代碼,並且他會按需去加載所須要的插件,讓你的生活更加美好。。。接下來咱們記得先install這個 @babel/preset-env一波,不配任何插件,而後咱們再來看看效果如何:
/* babel.config.js */ module.exports = { presets: [ "@babel/preset-env" ], plugins: [ ] }
對應的es6語法就會變成:
/* study.js */ const study = () => {} const arr1 = [1, 2, 33] const arr2 = [...arr1] const exponentiation = 2 ** 2 // 新增API new Promise(() => {}) new Map() /* study-compiled.js */ var study = function study() {}; var arr1 = [1, 2, 33]; var arr2 = [].concat(arr1); var exponentiation = Math.pow(2, 2); // 新增API new Promise(function () {}); new Map();
你會發現es6+的語法都被編譯了,咱們並無設置任何插件哦,應該也看到了新增的API方法並無被編譯,在這裏咱們埋下伏筆,等下文講到polyfill的時候再治他。
關於preset-env,咱們還能夠提供一個targets
配置項指定運行環境,就是咱們能夠配置對應目標瀏覽器環境,那麼babel就會編譯出對應目標瀏覽器環境能夠運行的代碼。相信有同窗遇到過在低版本系統ios手機裏本身的項目會白屏,實際上是某些語法在ios低版本系統裏不支持,這個時候咱們能夠直接配置ios 7瀏覽器環境均可以支持的代碼:
/* babel.config.js */ module.exports = { presets: [ [ "@babel/preset-env", { 'targets': { 'browsers': ['ie >= 8', 'iOS 7'] // 支持ie8,直接使用iOS瀏覽器版本7 } } ] ], plugins: [ ] }
固然babel的Browserslist集成還支持在package.json文件裏或者新建一個 .browserslistrc 文件來指定對應目標環境。browserslist配置源
上文也提到了像Promise這種API我們的babel並無給轉義,那是由於babel默認是隻會去轉義js語法的,不會去轉換新的API,好比像Promise、Generator、Symbol這種全局API對象,babel是不會去編譯的,這個時候就要掏出 @babel/polyfill 了。用法很簡單,先安裝一波,而後咱們只須要在入口文件頂部引入 @babel/polyfill 就可使用新增的API了。
/* study.js */ import '@babel/polyfill' // 新增API new Promise(function () {}); /* study-compiled.js */ require("@babel/polyfill"); // 新增API new Promise(function () {});
小細節:import被編譯成了require,若是想要編譯出來的模塊引入規範仍是import,則能夠在preset-env的配置項中添加"modules": false便可。
modules的options:"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false,默認爲"auto"
可是問題又來了,有時候咱們項目裏並無用到那麼多的新增API,可是 @babel/polyfill 會把全部瀏覽器環境的的polyfill都引入,整個包的體積就會很大,咱們想要對目標環境按需引入相應的polyfill應該怎麼辦呢,這個時候咱們就可使用 preset-env 的配置項中的useBuiltIns
屬性來按需引入polyfill。
/* babel.config.js */ module.exports = { presets: [ [ "@babel/preset-env", { "modules": false, "useBuiltIns": "entry", 'targets': { 'browsers': ['ie >= 8', 'iOS 7'] // 支持ie8,直接使用iOS瀏覽器版本7 } } ] ], plugins: [ ] }
這個時候就會在入口處只把全部ie8以上以及iOS 7瀏覽器不支持api的polyfill引入進來。
最終效果:
/* study.js */ import '@babel/polyfill' // 新增API new Promise(function () {}); /* study-compiled.js */ import "core-js/modules/es6.array.copy-within"; import "core-js/modules/es6.array.every"; ...//省略若干 import "core-js/modules/web.immediate"; import "core-js/modules/web.dom.iterable"; import "regenerator-runtime/runtime"; // 新增API new Promise(function () {});
此時你會發現,import '@babel/polyfill'沒有了,引入的是咱們目標環境相應的polyfill。可是有沒有發現引入的都是import 'core-js/...'的內容,標題已經說啦,@babel/polyfil是由core-js2和regenerator-runtime組成的一個集成包。
這個時候你又會想,假如個人項目裏面只用到了Promise這個API,能不能只給我引入Promise相應的API呢?答案是必能夠!,讓咱們先來好好了解下preset-env的配置項中的useBuiltIns
屬性。
選項:"usage"| "entry"| false,默認爲false。
entry
咱們已經用過了,意義就是在入口處將根據咱們配置的瀏覽器兼容,將目標瀏覽器環境全部不支持的API都引入。
usage
就很nb了,當配置成usage的時候,babel會掃描你的每一個文件,而後檢查你都用到了哪些新的API,跟進咱們配置的瀏覽器兼容,只引入相應API的polyfill,咱們把useBuiltIns
屬性設置爲usage
再來看下編譯效果:
/* study.js */ import '@babel/polyfill' // 新增API new Promise(function () {}); /* study-compiled.js */ import "core-js/modules/es.object.to-string"; import "core-js/modules/es.promise"; // 新增API new Promise(function () {});
我就問你帥不帥!徹底的按需引入,牛逼了!
相信你也看到了一個東西,當咱們使用useBuiltIns
選項的時候,你的命令行裏面是否是顯示了一坨這樣的警告,大概是在配置文件中未指定core-js版本時,默認會使用core-js2:
WARNING: We noticed you're using the
useBuiltIns
option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via thecorejs
option.
前面也說到了 @babel/polyfil 是由core-js2和regenerator-runtime組成的一個集成包,如今core-js3已經發布了,並且很穩定。可是core-js2在18年的時候已經再也不維護了;@babel/polyfil引入的是2不是3,而且 @babel/polyfill 在babel7.4.0已經再也不推薦使用了,要廢掉(好像是由於@babel/polyfill不支持core-js2平滑的過渡到core-js3)。因此core-js官方如今推薦咱們使用polyfill的時候直接引入core-js和regenerator-runtime/runtime這兩個包徹底取代 @babel/polyfil 來爲了防止重大更改。
固然,咱們須要在preset-env配置項中指定core-js版本,這樣就不會再有警告⚠️了:
/* babel.config.js */ module.exports = { presets: [ [ "@babel/preset-env", { "modules": false, "useBuiltIns": "entry", "corejs": "3", 'targets': { 'browsers': ['not ie >= 8', 'iOS 7'] // 支持ie8,直接使用iOS瀏覽器版本7 } } ] ], plugins: [ ] }
有的時候一些語法的轉換會比較複雜,babel會引入一些helper函數,好比說對es6的class進行轉換:
/* study.js */ class Test {} /* study-compiled.js */ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Test = function Test() { _classCallCheck(this, Test); };
能夠看到上面引入了helper函數來處理class的轉換。可是問題又來了,若是好多文件都使用到了複雜語法的轉換,這個仍是簡單點的,有些helper函數是很複雜代碼量不少的,那豈不是每一個文件都會定義一遍這些個函數,每一個文件的代碼會不少?若是說能夠把這些helper函數都抽離到一個公共的包裏,用到的地方只須要引入對應的函數便可,咱們的編譯出來的代碼量會大大滴減小,這個時候就須要用到 @babel/plugin-transform-runtime 插件來配合@babel/runtime進行使用。記得先安裝一波,而後在插件選項中加入 @babel/plugin-transform-runtime 這個插件,而後咱們來看看編譯後的效果:
/* study.js */ class Test {} /* study-compiled.js */ import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; var Test = function Test() { _classCallCheck(this, Test); };
固然若是咱們只是爲了減小編譯出來的文件中代碼量而使用這個插件的話就過小看他了,並且也沒有必要。
@babel/plugin-transform-runtime還有一個最重要的做用:好比說像上面咱們說的Promise就須要提供相應的polyfill去解決,這樣作會有一個反作用,就是會污染全局變量。若是咱們只是在一個業務項目這樣搞還好,也沒別人要用到。可是若是咱們是在維護一個公共的東西,好比公共組件庫,咱們這樣搞,你的一些polyfill可能會把一些全局的api給改掉,反作用就會很明顯,別人用你的組件庫的時候就可能會出問題。@babel/plugin-transform-runtime插件爲咱們提供了一個配置項corejs,他能夠給這些polyfill提供一個沙箱環境,這樣就不會污染到全局變量,無反作用你說美不美。
記得安裝 @babel/runtime-corejs2 這個包(穩定版用2就能夠),注意若是不配置的話,是不會提供沙箱環境的。而後在 @babel/plugin-transform-runtime 插件配置corejs:
/* babel.config.js */ module.exports = { presets: [ [ "@babel/preset-env", { "modules": false, "useBuiltIns": "usage", "corejs": "3", 'targets': { 'browsers': ["ie >= 8", "iOS 7"] // 支持ie8,直接使用iOS瀏覽器版本7 } } ] ], plugins: [ [ "@babel/plugin-transform-runtime", { "corejs": 2 } ] ] }
咱們來看下編譯後的效果:
/* study.js */ new Promise(() => {}) class Test {} /* study-compiled.js */ import _classCallCheck from "@babel/runtime-corejs2/helpers/classCallCheck"; import _Promise from "@babel/runtime-corejs2/core-js/promise"; new _Promise(function () {}); var Test = function Test() { _classCallCheck(this, Test); };
接下來本人會去繼續研究babel是如何解析編譯的,target:理解babel如何解析編譯,可以手寫一個babel插件出來。最近需求比較多,下一篇估計得等到Q2了。。。
最近也是疫情期間,你們必定要記得儘可能少出門,出門必帶口罩。像白衣天使們致敬!