前沿:文章起源在於,朋友跟樹醬說在解決項目兼容IE11瀏覽器過程當中,遇到「眼花繚亂」的babel配置和插件等,傻傻分不清配置間的區別、以及不瞭解如何引用babel插件才能讓性能更佳,若是你也有這方面的疑慮,這篇文章可能適合你
babel是個什麼玩意? Babel本質上是一個編輯器,也就是個「翻譯官」的角色,好比樹醬聽不懂西班牙語,須要別人幫我翻譯成爲中文,我才曉得。那麼Babel就是幫助瀏覽器翻譯的,讓web應用可以運行舊版本的瀏覽器中,好比IE11瀏覽器不支持Promise等ES6語法,那這個時候在IE11打開你寫的web應用,應用就沒法正常運行,這時候就須要Babel來「翻譯」成爲IE11能讀懂的
本質上單獨靠Babel是沒法完成「翻譯」,好比官網的例子
const babel = code => code;
不借助Babel插件的前提,輸出是不會把箭頭函數「翻譯」的,若是想完成就須要用到插件,更多概念點點擊
官方文檔
Babel工做原理本質上就是三個步驟:解析、轉換、輸出,以下👇所示,html
👨🎓 啊斌同窗: 上面說到的抽象語法樹AST又是什麼玩意?
答:咱們上文提到,Babel在解析是時候會經過將code轉換爲AST抽象語法樹,本質上是代碼語法結構的一種抽象表示,經過以樹🌲形的結構形式表現出它的語法結構,抽象在於它的語言形態不會體如今原始代碼code中前端
下面介紹下在前端項目開發中一些AST的應用場景:vue
Vue模版解析
: 咱們平時寫的.vue
文件經過vue-template-compiler
解析,.vue文件處理爲一個ASTBabel的「翻譯」
: 如將ES6轉換爲ES5過程當中轉爲ASTwebpack的插件UglifyJS
: uglifyjs-webpack-plugin
用來壓縮資源,uglifyjs會遇到須要解析es6語法,這個過程當中本質上也是藉助babel-loader
你能夠安裝經過本地安裝babel-cli
作個驗證,經過babel-cli編譯js文件,玩玩「翻譯」webpack
🌲推薦閱讀:git
👨🎓 啊可同窗: 樹醬,我想本身使用AST開發一個babel插件須要使用到哪些東西呢?
答:咱們上一節中提到babel不借助「外援」的話,本身是沒法完成翻譯,而一個完整的「翻譯」的過程是須要走完解析、轉換、輸出才能完成整個閉環,而這其中的每一個環節都須要藉助babel如下這些APIes6
@babel/parser
: babel解析器將源代碼code解析成 AST@babel/generator
: 將AST解碼生成js代碼 new Code@babel/traverse
: 用來遍歷AST樹,能夠用來改造AST~,如替換或添加AST原始節點@babel/core
:包括了整個babel工做流下面是一個簡單「翻譯」的demo~github
👦:啊寬同窗:你不是說@babel/parser
是也將源代碼code解析成 AST嗎?爲啥@babel/core
也是?
答:@babel/core包含的是整個babel工做流,在開發插件的過程當中,若是每一個API都單獨去引入豈不是矇蔽了來吧~因而就有了@babel/core插件,顧名思義就是核心插件,他將底層的插件進行封裝(包含了parser、generator等),提升原有的插件開發效率,簡化過程,好一個「🍟肯德基全家桶」web
🌲推薦閱讀:vue-cli
講完Babel的基本使用,接下來聊聊插件,上文提到單獨靠babel是「難成大器」的,須要插件的輔助才能實現霸業,那插件是怎麼搞的呢?
經過第一節的學習咱們知道完成第一步驟解析完AST後,接下來是進入轉換,插件在這個階段就起到關鍵做用了。npm
告訴Babel該作什麼以前,咱們須要建立一個配置文件.babelrc或者babel.config.js文件
若是我想把es2015
的語法轉化爲es5 及支持es2020的鏈式寫法,我能夠這樣寫
上圖所示👆,咱們能夠看到咱們配置兩個東西 present
和plugin
👨🎓 啊可同窗:babel不是隻須要plugin來幫忙翻譯嗎,這個present又是什麼玩意?
答:presets是預設,舉個例子:有一天樹醬要去肯德基買雞翅、薯條、可樂、漢堡。而後我發現有個套餐A包含了(薯條、可樂、漢堡),那這個present就至關於套餐A,它包含了一些插件集合,一個大套餐,這樣我就只須要一個套餐A+雞翅就搞定了,不用配置不少插件。
就比如上面的es2015「套餐」,其實就是Babel團隊將同屬ES2015相關的不少個plugins
集合到babel-preset-es2015
一個preset中去
👧 啊琪同窗:@babel/preset-env這個是什麼?我看不少babel的配置都有
答:@babel/preset-env這個是一個present預設,換句話說就是「豪華大禮包」,包括一系列插件的集合,包含了咱們經常使用的es2015,es2016, es2017等最新的語法轉化插件,容許咱們使用最新的js語法,好比 let,const,箭頭函數等等,但不包括stage-x階段的插件。換句話說,他包含了咱們上文提到了es2015
,是個「全家桶」了,而不只是個套餐了。
👦 啊斌同窗:樹醬,那我是否是能夠本身搞一個預設present?
答: 能夠的,可是你能夠以 babel-preset-* 的命名規範來建立一個新項目,而後建立一個packjson並安裝好定影的依賴和一個index.js 文件用於導出 .babelrc,最終發佈到npm中,以下所示
好比咱們在開發中使用,會使用到一些es6的新特徵好比Array.from
等,但不是全部的 JavaScript 環境都支持 Array.from,這個時候咱們可使用 Polyfill(代碼填充,也可譯做兼容性補丁)的「黑科技」,由於babel只轉換新的js語法,如箭頭函數等,但不轉換新的API,好比Symbol、Promise等全局對象,這時候須要藉助@babel/polyfill
,把es的新特性都裝進來,使用步驟以下
npm 安裝
: npm install --save @babel/polyfill
文件頂部導入 polyfill
: import @babel/polyfilll
🙅♂️:缺點:全局引入整個 polyfill包,如promise會被全局引入,污染全局環境,因此不建議使用,那有沒有更好的方式?能夠直接使用@babel/preset-env並修改配置,由於@babel/preset-env
包含了@babel/polyfill
插件,看下一節
完成上面的配置,而後用Babel編譯代碼,咱們會發現有時候打出的包體積很大,由於@babel/polyfill有些會被全局引用,那你要弄清楚@babel/preset-env的配置
@babel/preset-env
中與 @babel/polyfill
的相關參數有兩個以下:
targets
: 支持的目標瀏覽器的列表useBuiltIns
: 參數有 「entry」、」usage」、false 三個值。默認值是false,此參數決定了babel打包時如何處理@babel/polyfilll 語句主要聊聊關於useBuiltIns
的不一樣配置以下:
entry
: 去掉目標瀏覽器已支持的polyfilll 模塊,將瀏覽器不支持的都引入對應的polyfilll 模塊。usage
: 打包時會自動根據實際代碼的使用狀況,結合 targets 引入代碼裏實際用到部分 polyfilll模塊false
: 不會自動引入 polyfilll 模塊,對polyfilll模塊屏蔽🌲建議:使用 useBuiltIns: usage來根據目標瀏覽器的支持狀況,按需引入用到的 polyfill 文件,這樣打包體積也不會過大
對於@babel/core
、@babel/preset-env
、@babel/polyfill
等這些插件,當咱們在使用webpack進行打包的時候,如何讓webpack知道按這些規則去編譯js。這時就須要babel-loader
了,它至關於一箇中間橋樑,經過調用babel/core中的API來告知webpack要如何處理。
👦 啊斌同窗:我開發了一個工具庫,也使用了babel,若是引用polyfill,如何避免使用致使的污染環境?
答:在開發工具庫或者組件庫時,就不能再使用babel-polyfill了,不然可能會形成全局污染,可使用@babel/runtime
。它不會污染你的原有的方法。遇到須要轉換的方法它會另起一個名字,不然會直接影響使用庫的業務代碼,使用@babel/runtime
主要在於
@babel/runtime
以前,庫和工具包通常不會直接引入 polyfill。不然像 Promise 這樣的全局對象會污染全局命名空間,這就要求庫的使用者本身提供 polyfill。這些 polyfill 通常在庫和工具的使用說明中會提到,好比不少庫都會有要求提供 es5 的 polyfill。在使用 babel-runtime 後,庫和工具只要在 package.json 中增長依賴 babel-runtime,交給 babel-runtime 去引入 polyfill就能夠了如何使用 @babel/runtime
npm install --save-dev @babel/plugin-transform-runtime npm install --save @babel/runtime
👦:啊呆同窗:babel-core和@babel/core是什麼區別?
答;@babel是在babel7中版本提出來的,就相似於 vue-cli 升級後使用@vue/cli同樣的道理,因此babel7之後的版本都是使用 @babel
開頭聲明做用域,