大前端的自動化工廠(3)—— babel


一. 關於babel

babel是ES6+語法的編譯器,官方網址:www.babeljs.io,用於將舊版本瀏覽器沒法識別的語法和特性轉換成爲ES5語法,使代碼可以適用更多環境。css

最初的babel使用起來是很是方便的,幾乎僅使用少許的配置就可使用,但隨着工具的快速升級和代碼架構的轉變,babel已經裂變成很是多的部分,每一個部分各司其職,這樣作的好處是能夠縮小生產環境的正式包的代碼體積(由於能夠按需引用)而加劇了開發環境(開發階段須要引入更多碎片化的插件),但劣勢就是將其使用門檻提得很是高,對軟件架構不熟悉的開發者難以使用。html

好比babel官方網站在webpack配置的章節,說起了babe-loader,babel-corebabel-preset-env三個插件,而當開發者在webpack中實際進行配置時除了上述三個基本插件外,又會遇到babel-polyfill,babel-runtime,babel-plugin-transform-runtime等等一系列插件,或許經過查看插件說明可以理解插件的功能,但開發者卻很難判斷本身是否該使用這個功能或者何時使用。node

二. 基本需求推演

咱們從工具設計的角度,經過問題推演的方式來看看babel的變化。webpack

ES6標準推出時,瀏覽器還不能很好地支持,但ES6的許多特性和語法又很誘人,因此你們想了個辦法,那就是用ES6編寫代碼,而後出包的時候拿個工具轉換一下,變成能被更多瀏覽器識別的ES5語法不就好了麼,因而,Babel基本模型就出現了:git

babel的功能被定義爲編譯工具,那麼理論上來講它就可使用編譯器的通用代碼框架,經過ASTparser --> traverse --> stringify 的步驟實現編譯功能,在關鍵的traverse環節,是須要一個規則集合的,但是轉碼所參考的ES6的標準並非一個定案的標準,其中每個特性都須要通過從stage0stage4這樣5個階段才能正式定稿,只有stage-2草案(draft)階段以上的特性纔會在將來被支持,而處於這個階段如下的標準是有可能被廢的,若是一味地所有轉換,不只會下降工具效率,也會爲代碼將來的維護形成隱患。程序員

那若是咱們有一個工廠函數,接受數字0-4做爲參數,而後返回全部經歷了stage-x的規則集(是ES6規則的子集)做爲規則集合,那麼就能夠在最終生成生產環境的代碼時減少代碼體積,假如在項目中經過babel_get_es6_by_stage(2)這樣一個函數返回了規則集,那麼正式代碼中就不須要stage-0stage-1的實現代碼了。基於以上的考慮,咱們對Babel工具進行第一次功能剝離:es6

推演繼續,在對規則集進行了一次體積縮減後,咱們獲得了一個相對精簡的規則集,它包含了諸多新的語法和方法,若是直接使用那的確很爽,畢竟引入了一個工具後就能夠毫無後顧之憂地使用新特性,但對於生產環境的代碼包來講,這種作法形成的代碼冗餘確是很是難以接受的。github

用你們都熟悉的bootstrap爲例,bootstrap.min.css的體積大約爲120k,可你會發現不少人引入它徹底是出於內心慣性,而在最後僅僅使用了很是基礎的btn相關的樣式類,或者僅僅爲了使用col-md-4這種響應式佈局的樣式,全部使用到的樣式可能只佔了20k-30k的空間,可是卻不得不爲項目引進一個120k大的庫,固然並非全部的項目都會在乎20k和120k之間的差異的。web

那麼咱們就須要一個可以按更小粒度組合的方法babel_get_es6_by_rules([rule , ...]),讓使用者能夠選擇本身所使用到的語法和方法,從而達到縮小引用庫體積的目的:chrome

推演繼續進行。處理過兼容性問題的開發者都知道,瀏覽器是存在版本區分的,許多特性在不一樣瀏覽器中的實現和表現都不同,對於ES6也是這樣,較高版本的瀏覽器對於ES6中的一些特性是已經逐步實現支持了的,若是咱們的目標用戶所使用的運行環境對某些ES6特性已經提供了原生支持,或者目標用戶的運行環境根本就是由開發者直接封裝好的,那麼原先「一鍋端」的轉碼方式裏就會存在不少沒有必要的部分。

好比你在規則集中選擇了對Class關鍵字來定義類這個特性進行轉碼,那麼babel就須要將其轉碼成爲使用functionprototype的ES5的實現方式,但若是你的目標用戶全都是程序員,幾乎全都是使用高版本的chrome做爲項目環境,那麼上面的轉碼可能就是多此一舉了。

綜上所述,咱們就須要爲babel提供一個判斷目標環境是否須要轉碼的方法babel_get_rule_as_need( rule_set , env_info),將通過第一次篩選後的規則集和目標用戶的環境信息傳入方法,對規則集進行再一次的精簡,那麼咱們須要再次對babel進行優化:

至此,babel便具有了針對不一樣的使用環境進行必要轉碼的能力,可這並非問題的所有,ES6的新特性除了語法的更新外,還增長了不少原生方法或類型,例如Map,Set,Promise等這類新的全局對象,或是Array.from這類靜態方法等等,語法轉義並不能完成對這些特性的識別,由於不管在ES5環境仍是ES6環境你都是這麼寫的,只有運行的時候,瀏覽器纔會報錯,告訴你某個對象或者某個方法不存在。

好比下面的代碼:

function addAll() {
  return Array.from(arguments).reduce((a, b) => a + b);
}

轉義後會變爲:

function addAll() {
  return Array.from(arguments).reduce(function(a, b) {
    return a + b;
  });
}

然而,它依然沒法隨處可用由於不是全部的 JavaScript 環境都支持 Array.from。對於這一類非語法層面的特性,咱們但願在工具中可以自動提供支持,這項工做有一個專有的稱謂,叫作【polyfill】(或稱爲墊片)。

咱們既能夠主動提供一個polyfill列表指明須要添加的墊片插件數組,也能夠採用被動的方式,在轉碼過程當中遇到的這種API類型的新特性放進一個數組,經過babel_add_polyfill ( polyfill_list )爲根據安裝相應的墊片,須要注意的是,polyfill至關於爲瀏覽器進行功能擴展,須要優先於項目業務邏輯代碼運行,那麼babel的邏輯框架就變成了:

推演繼續。在上面的邏輯結構中,咱們只是簡單地將polyfill庫添加至全局變量,而全局變量是頗有可能被重寫而失效或是與其餘第三方庫發生代碼衝突的。那麼若是不將polyfill添加至全局,就須要將其剝離爲一個具備同等功能的獨立模塊,經過相似於lodash或是underscore那樣的方式調用,咱們對邏輯結構進行再一次拆分:

至此,咱們已經完成了babel工具集基本功能的*邏輯層劃分*,經過傳說中的多退少補(也就是語法超前了就回退,方法不夠了就打補丁)的方式來實現代碼編譯。

三. 模塊劃分

根據上述業務邏輯層的劃分結果,咱們須要對Babel工具進行代碼層的模塊劃分:

babel-module

四. 真正的babel

若是你可以理解上述的需求推演和模塊劃分的章節,那麼恭喜你已經掌握了babel的基本結構,咱們將本來模塊圖中的信息更換成實際的名稱或是插件,並進行一些組件劃分,就能夠看到真正的babel工具集的基本架構:

固然真正的babel功能遠不止這樣,它爲各類環境,編輯器和自動化工具提供了接口,也開放了插件開發的API給開發者,感興趣的讀者能夠繼續深刻了解。

五. 使用babel

babel8.0以上的版本將許多插件移入官方倉庫,安裝方式發生了改變,例如babel-preset-env地址變爲了@babel/preset-env,使用時請參考babel官網進行配置。

1.babel-cli

爲了方便直接在命令行使用babel的功能,經過yarn global add babel-cli在全局安裝命令行工具babel-cli,在package.json中加入以下腳本:

"scripts":{
    "babel":"babel main.js -o maines5.js"
}

而後經過yarn run babel便可在命令行使用babel進行編譯了,但查看編譯後的代碼就能夠發現,編譯先後的文件是同樣的,由於咱們沒有爲其指定任何轉碼規則,運行babel只是把生成的AST遍歷了一下而已,想要babel可以實現轉碼,請繼續向下看。

2.babel-preset-env

提供轉碼規則,它低版本babel中使用的幾個插件的結合。babel-preset-env實際上實現的,就是咱們在問題推演中所描述的【All Rules規則集 + get_rules()方法集】,你會在node_modules文件夾中找到許多babel-plugin-transform-***這種命名的包,他們就是規則集,你既能夠經過設置preset屬性來使用,也能夠經過在plugins屬性中挑選須要的轉碼規則進行引用。

安裝babel-preset-env後在項目文件夾新建.babelrc文件並添加以下配置:

{
    "presets":["env"],
    "plugins": []
}

或自定義所須要支持的轉義規則:

{
    "presets":[],
    "plugins": [
        "babel-plugin-transform-es2015-arrow-functions"//箭頭函數轉換規則
    ]
}

再次運行babel,就能夠看到所編寫的代碼已經進行了轉換。

轉換前:

//Arrow Function  Array.from method
Array.from([1, 2, 3]).map((i) => {
    return i * i;
});

轉換後:

"use strict";
//Arrow Function  Array.from method
Array.from([1, 2, 3]).map(function (i) {
    return i * i;
});

固然也能夠指定目標瀏覽器,去除沒必要要的轉碼,例如在.babelrc指定要匹配的瀏覽器爲較高版本的chrome:

//.babelrc
{
    "presets":[ 
        ["env", {
          "targets": {
             "browsers": "chrome 56"
          }      
        }]
    ],
    "plugins":[]
}

就能夠發現編譯後的腳本文件中箭頭函數依然存在,說明這個版本的chrome瀏覽器已經支持箭頭函數了,也就沒有必要進行轉義了。

新版本的babel已經計劃支持在package.json中設置browserslist參數來指定須要適配的使用環境,也就是說同一套針對使用環境的配置被剝離出來,而被postcss,babel,autoprefixer等工具共享使用。

3.babel-polyfill

babel只負責語法轉換,好比將ES6的語法轉換成ES5。但若是有些對象、方法,瀏覽器自己不支持,好比:

  1. 全局對象:Promise、WeakMap 等。
  2. 全局靜態函數:Array.from、Object.assign 等。
  3. 實例方法:好比 Array.prototype.includes 等。

此時,須要引入babel-polyfill來模擬實現這些對象、方法。

若是上面編譯後的代碼在IE10瀏覽器中打開,就會看到瀏覽器出現不支持Array.from方法的報錯,若是生成的代碼須要在IE10中運行,那咱們就須要引入兼容補丁庫,讓IE10瀏覽器環境中可以支持這個方法。

babel-polyfill須要經過以下的方式引入,而後經過打包工具將其融入腳本:

//ES Module
import 'babel-polyfill'
//或 CommonJs
require ('babel-polyfill')

當你真的這樣去使用時,就會發現,它的確可以解決報錯的問題,可是如此打包會引入整個babel-polyfill,打包後的代碼增長了將近4000行(約400k體積增量),着實讓人難以接受。那這個插件可否像babel-preset-env同樣按需引用呢?必須能夠的。babel-polyfill是基於core-jsregenerator構建的,只須要在引用時指明便可,例如:

import 'core-js/modules/es6.array.from';
//Arrow Function  Array.from method
Array.from([1, 2, 3]).map((i) => {
    return i * i;
});

再進行打包時就會發現bundle文件的體積減少了很是多。

babel-polyfill的實現方式如問題推演中所提到的那樣,就是污染了全局環境,並且你可能已經意識到,這個工具,要麼簡單配置後代碼量激增,要麼按需引用配置繁瑣。除非是在中型以上項目中有兼容低版本IE的需求,不然不建議使用。

4.babel-runtime/babel-plugin-transform-runtime

若是一個東西難用,那麼很快就會有替代品出現,軟件的世界也是這樣,babel-runtime就是這樣一個替代品。摘錄下文資料推薦的博文中的解釋:

  • babel-polyfill

    簡單粗暴,他會污染全局環境,好比在不支持Promise的瀏覽器會polyfill一個全局的Promise對象供調用;另外,不支持的實例方法也在對應的構造函數原型鏈上添加要polyfill的方法。

  • babel-runtime

    不會污染全局環境,會在局部進行polyfill,另外不會轉換一些實例方法,如'abc'.includes('a'),其中的includes方法就不會翻譯。它通常結合babel-plugin-transform-runtime來使用。

簡單地說,除了實例方法之外,其餘的特性babel-runtime都會幫你打好補丁。使用時直接在plugins配置項中添加babel-plugin-transform-runtime便可。

總的來講,babel-polyfillbabel-plugin-transform-runtime都有各自的使用場景,也是能夠結合使用的,須要根據實際項目需求進行篩選和引入

六. 資料推薦

  • 《webpack+babel項目在IE下報Promise未定義錯誤引出的思考》

    博文裏詳細解說了babel-runtimebabel-plugin-transform-runtime的相關問題。

  • 《如何寫好.babelrc?》

    博文裏詳細解說了各個配置項和可選參數的意思,很是實用。

  • 入門指南:babel-handbook

    很是棒的入門指南,對babel中的概念和用法都作了必定解釋,建議優先閱讀,能夠幫助開發者瞭解本篇中未涉及的babel模塊。

  • 官方網站:www.babeljs.io

    不少開發者喜歡看教程卻容易忽略官網,這是很是奇怪的。官方網站會連接到很是多優秀的github倉庫,不只包括babel中封裝的底層模塊,還包括可以幫助咱們理解的指引倉庫,甚至ES2015主要特性的解釋的網站,是學習babel的主要資源。

相關文章
相關標籤/搜索