回顧 babel 6和7,來預測下 babel 8

babel 最開始叫 6to5,顧名思義,功能是 es6 轉 es5。咱們知道,es 版本一年一個,有了 es7(es2016)、es8(es2017)等等。顯然,6to5 的名字已經不合適了,因此 6to5 更名爲了 babel。javascript

babel 來自巴別塔的典故:java

當時人類聯合起來興建但願能通往天堂的高塔,爲了阻止人類的計劃,上帝讓人類說不一樣的語言,令人類相互之間不能溝通,計劃所以失敗,人類自此各散東西。此事件,爲世上出現不一樣語言和種族提供解釋。這座塔就是巴別塔。node

這個巴別塔的典故很符合 babel 的轉譯器的定位。react

babel 的編譯流程

babel 從最初到如今一直的目的都很明確,就是把源碼中的新語法和 api 轉成目標瀏覽器支持的。它採用了微內核的架構,整個流程比較精簡,全部的轉換功能都是經過插件來完成的。git

babel 的編譯流程就是 parse、transform、generate 3步, parse 是把源碼轉成 AST,transform 是對 AST 的轉換,generate 是把 AST 轉成目標代碼,而且生成 sourcemap。es6

在 transform 階段,會應用各類內置的插件來完成 AST 的轉換。內置插件作的轉換包括兩部分,一是把不支持的語法轉成目標環境支持的語法來實現相同功能,二是不支持的 api 自動引入對應的 polyfill。github

babel 的編譯流程和目的從沒有變過,可是完成這個目的的方式卻變化很大,咱們來回顧一下 babel 6babel 7 都是怎麼設計的,babel 8 又會怎麼作,或許能幫你真正理解 babel。數據庫

babel 6

es 的標準一年一個版本,也就意味着 babel 插件要實時的去跟進,一年實現一系列插件。json

新的語法和 api 進入 es 標準也是有個過程的,這個過程分爲這幾個階段:api

  • 階段 0 - Strawman: 只是一個想法,可能用 babel plugin 實現
  • 階段 1 - Proposal: 值得繼續的建議
  • 階段 2 - Draft: 創建 spec
  • 階段 3 - Candidate: 完成 spec 而且在瀏覽器實現
  • 階段 4 - Finished: 會加入到下一年的 es20xx spec

有這麼多特性要 babel 去轉換,每一個特性用一個 babel 插件來作。可是特性多啊,也就是說插件多,總不能讓用戶本身去配一個個插件吧,因此 babel 6 引入了 preset 的概念,就是 plugin 的集合。

若是咱們想用 es6 語法就用 babel-preset-es2015,es7 就在引入 babel-preset-es2016 等等。若是是想用還沒加入標準的特性,則分別用 babel-preset-stage0、babel-preset-stage1 等來引入。這樣經過選擇不一樣的 preset,加上手動引入一些插件,就是全部 babel 會作的轉換。

能夠把這個過程理解爲集合求並集的過程。

並集的結果就是全部支持的特性。

babel 6 就是經過這樣的方式來支持各類目標環境不支持的特性轉換的配置。

細想一下,這樣的方式有沒有問題?

這樣雖然能達到目的,可是是有問題的,主要有兩點:

  • es 的標準每一年都在變,如今的 stage-0 可能很快就 stage-2 了,那 preset 怎麼維護,要不要跟着變,用戶怎麼知道這個 stage-x 都支持什麼特性?

  • 只能轉成 es5,那目標環境支持一些 es6 特性了,那這些轉換和 polyfill 豈不是無用功? 並且還增長了產物的體積。

  • polyfill 手動引入,比較麻煩,有沒有更好的方式

這兩個問題是 babel 6 的時候一直存在的。因此這種方案算是及格,可是仍是有問題的,咱們給 70 分不過度吧。 (能完成功能就能夠給 60 分,多加 10 分是給 babel 6 引入的 preset,確實簡化了不少配置)

那怎麼解決 babel 6 的問題呢?babel 7 給出了答案。

babel 7

babel 7 改動挺大的,好比全部的包都遷移到了 @babel 的 scope 下,也就是 @babel/xxx,這些咱們無論,只看 babel 7 是怎麼解決 babel 6 的問題的,

babel 7 廢棄了 preset-20xx 和 preset-stage-x 的 preset 包,而換成了 preset-env,preset-env 默認會支持全部 es 標準的特性,若是沒進入標準的,再也不封裝成 preset,須要手動指定 plugin-proposal-xxx。

它的集合是這樣的:

是否是比起 babel 6 更簡單了。

(preset-react 等不是 es 標準語法,也沒有啥變化,就不包括在裏面了)。

可是 preset 和 plugin proposal 的改變只是解決了以前的 preset 常常變的問題。那麼多轉換了一些環境支持的特性,這個問題是怎麼解決的呢?

答案是 compat-table,它給出了每一個特性在不一樣瀏覽器或者 node 環境中的最低支持版本,babel 基於這個本身維護了一份數據庫,在 @babel/compat-data 下。

其中有每一個特性在不一樣環境的什麼版本支持的數據:

有了這些數據,那麼只要用戶指定他的目標環境是啥就能夠了,這時候能夠用 browserslist 的 query 來寫,好比 last 1 version, > 1% 這種字符串,babel 會使用 brwoserslist 來把它們轉成目標環境具體版本的數據。

有了不一樣特性支持的環境的最低版本的數據,有了具體的版本,那麼過濾出來的就是目標環境不支持的特性,而後引入它們對應的插件便可。這就是 preset-env 作的事情。

配置方式好比:

{
    "presets": [["@babel/preset-env", { "targets": "> 0.25%, not dead" }]]
}
複製代碼

這樣就經過 preset-env 解決了轉換了目標環境已經支持的特性的問題。其實 polyfill 也能夠經過 targets 來過濾。

再也不手動引入 polyfill,那麼怎麼引入? 固然是用 preset-env 自動引入了。可是也不是默認就會啓用這個功能,須要配置。

{
    "presets": [["@babel/preset-env", { 
        "targets": "> 0.25%, not dead",
        "useBuiltIns": "usage",// or "entry" or "false"
        "corejs": 3
    }]]
}
複製代碼

配置下 corejs 和 useBuiltIns。

  • corejs 就是 babel 7 所用的 polyfill,須要指定下版本,corejs 3 才支持實例方法(好比 Array.prototype.fill )的 polyfill。

  • useBuiltIns 就是使用 polyfill (corejs)的方式,是在入口處所有引入(entry),仍是每一個文件引入用到的(usage),或者不引入(false)。

配置了這兩個 option 就能夠自動引入 polyfill 了。

polyfill 默認是全局引入的,有的時候不想污染全局變量就要用 @babel/plugin-transform-runtime 轉換下。(這個插件 babel 6 就有了)。

這樣就再也不污染全局環境了,而是使用一個惟一的標識符來引入。

看起來,babel 7 好像已經很完美了,能夠打 90 多分了?

不是的,babel 7 有 babel 7 的問題。

babel 7 的問題

@babel/plugin-transform-runtime 是不支持配置 targets 的,由於不知道目標環境支持啥,它只能所有作轉換。你可能說不是有 preset-env 麼?

babel 中插件的應用順序是:先 plugin 再 preset,plugin 從左到右,preset 從右到左,這樣 plugin-transform-runtime 是在 preset-env 前面的。

等 @babel/plugin-transform-runtime 轉完了以後,再交給 preset-env 這時候已經作了無用的轉換了。

咱們來試驗一下:

咱們先看一下 Array.prototype.fill 的環境支持狀況:

能夠看到在 Chrome 45 及以上支持這個特性,而在 Chrome 44 就不支持了。

咱們先單獨試一下 preset-env:

當指定 targets 爲 Chrome 44 時,應該自動引入polyfill:

當指定 targets 爲 Chrome 45 時,不須要引入polyfill:

結果都符合預期,44 引入,45 不引入。

咱們再來試試 @babel/plugin-transform-runtime:

是否是發現問題了,Chrome 45 不是支持 Array.prototype.fill 方法麼,爲啥仍是引入了 polyfill。

因而我就去問了下做者,提了個 feature request,做者說能夠用最新的 @babel/polyfills 解決了這個問題.

我去看了下,這個包還在試驗階段,確實解決了這個問題。

這個包估計在 babel 8 會內置到 babel。

那麼給 babel 7 打個分吧,原本 preset-env 的引入使咱們能更精準的轉換代碼和引入 polyfill,想給 90 分,可是 plugin-transform-runtime 的問題讓我給它減了 10 分,綜合給 80 分吧。

babel 8

babel 8 還沒出來,可是咱們知道 babel 再怎麼更新也是圍繞主線來的,也就是對目標環境不支持的特性自動進行精準的轉換和 polyfill。每一個版本都是解決了上個版本的問題的,babel 8 的 @babel/polyfills 包就解決了 babel 7 的 @babel/plugin-transform-runtime 的遺留問題,能夠經過 targets 來按需精準引入 polyfill 了。

它支持配置一個 polyfill provider,也就是說你能夠指定 corejs二、corejs三、es-shims 等 polyfill,還能夠自定義 polyfil,也就是你可使用本身的 polyfill。

而後有了 polyfill 源以後,使用 polyfill 的方式也把以前 transform-runtime 作的事情內置了,也就是從以前的 useBuiltIns: entry、 useBuiltIns: usage 的兩種,變成了 3 種:

  • entry-global: 這個和以前的 useBuiltIns: entry 對標,就是全局引入 polyfill。

  • usage-entry: 這個和 useBuiltIns: usage 對標,就是具體模塊引入用到的 polyfill。

  • usage-pure:這個就是以前須要 transform-runtime 插件作的事情,使用不污染全局變量的 pure 的方式引入具體模塊用到的 polyfill.

其實這三種方式 babel 7 也支持,可是如今再也不須要插件了,並且還支持了 polyfill provider 的配置,因此到了 babel 8 的階段, @babel/preset-env 纔是功能完備的。

那麼插件若是想用 targets 該怎麼用呢?

由於我最近在寫 《babel 插件通關祕籍》 的小冊,因此比較關注對插件的影響,我就問了一下 babel 維護者是否是須要在 @babel/core 調用插件的時候注入到 api 中,讓插件能夠拿到 targets。

上午問的,下午我就驚喜的發現 babel 文檔補充了 @babel/helper-compilation-targets 的文檔。helper 是用於插件之間複用代碼的方式,也就是給插件開發用的庫。

我看了下,這個庫提供了 3 個 api:

  • 根據 query 查詢目標環境版本: getTargets
  • 過濾目標環境: filterItems
  • 判斷某個插件是否須要:isRequired

分別對應咱們前面聊到的須要 先經過 query 肯定目標環境,而後對目標環境作過濾,以後判斷某個插件是否須要的 3個階段。

插件裏面經過 api.targets() 拿到環境的配置,而後經過 isRequired 來肯定某個插件有沒有必要用。

這樣,不論是內置 plugin 和 preset 的實現方式也好,仍是插件所能用的 api 也好,都完美支持了 targets,到了這個階段 targets 纔算真正融入進了 babel 中。

這個階段的 babel,我以爲已經能夠給出 90 分的分數了:

支持按照配置的目標環境按需進行 polyfill 和 transform,支持 polyfill 的切換和自定義,配置方式也足夠簡單,插件中也能夠用 targets,並且提供了方便的 helper 包。

babel 發展規律

babel 8 還在路上,可是咱們已經可以隱約看到他會是什麼樣子了,其實 babel 從最開始到如今,核心的思路始終沒有變過,就像最開始的名字 6to5 同樣,就是爲了 把目標環境中不支持的語法和 api 進行轉換或 polyfill,儘可能的準確、配置儘可能的簡單、插件更容易書寫能作到更多事情

因此針對這個目標,babel 一路發展而來, 設計出了 preset(babel 6)、preset-env (babel 7)、polyfill provider(babel 8),plugin-transform-runtime (babel 6)等。

插件可以用的 api、helper 等也愈來愈豐富。

babel 一直在發展,可是目標和本質從未變過。咱們去學習一個東西,也要去抓住它的本質來學,因此我寫了《babel 插件通關祕籍》 的小冊(即將上線),但願能幫你「通關」 babel!

相關文章
相關標籤/搜索