1. 程式人生 > >被低估的 Babel

被低估的 Babel

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

作者 | 袁德鑫
責編 | 陳秋歌

不僅僅是語法解析器,Babel更是一個平臺。豐富的外掛,讓它的擴充套件變得無限可能。目前餓了麼大前端部門正通過外掛開發,將Babel應用到無痕埋點、錯誤日誌收集等業務場景中。

概覽


Babel 是一個 JavaScript 語法解析器。提到它,相信大家的第一反應是把我們寫的 ECMAScript 6 程式碼轉換成瀏覽器可識別的 ECMAScript 5 程式碼,而這也正是 Babel 的前身 6to5 名字的由來。隨著 Babel 不斷的發展,現在它已經不再僅僅為了 6to5 而存在。本文旨在跟大家聊一聊如今的 Babel 是怎樣處理我們的程式碼,以及我們還能用 Babel 做些什麼。



誰動了我的程式碼


Babel 只是語法解析器

實際上說 Babel 是語法解析器並不嚴謹,因為 babel-core 僅僅是對外暴露了一些 API,而真實的解析器是 Babylon,但本文旨在以功能劃分 Babel 的幾個重要部分,遂將 Babylon 歸入 babel-core。

說到 Babel,有一個不得不提的概念就是抽象語法樹(Abstract Syntax Tree 或縮寫為 AST),即程式碼的抽象語法結構的樹狀表現形式。Babel 就是通過 AST 來理解你的程式碼,並根據預設的規則來對這棵“樹”進行編輯,然後將新的 AST 轉換為程式碼。

0?wx_fmt=png

圖1 Babel 的流程圖

那麼是 Babel 改動了我的程式碼嗎?不。Babel 自 6.0 起,就不再對程式碼進行修改。作為一個平臺,它本身只負責圖1中的 parse 和 generate 流程,修改程式碼的 transform 過程全都交給外掛去做。也就是說,Babel 只是一個語法解析器。很多初次使用 Babel 的開發者會問,“為什麼我不配置外掛 Babel 就不生效”,正是這個原因。

Plugin——轉換的執行者

上面說到 Babel 的 plugin 才是真正改動程式碼的“元凶”,現在我們就來講講 Babel 是怎樣改動程式碼的。

Babel 外掛中有一個觀察者(visitor)機制,我們可以在 visitor 中預設想要觀察的 babel-types,然後對其進行操作。下面以原始碼 foo === bar 為例:

module.exports = function ({ types: t }) {
  return {
    visitor: {
      BinaryExpression (path) {        
       const isMatchCondition = path.node.operator !== '===' && // 若操作符不為 === 則不做任何操作          t.isIdentifier(path.node.left, { name: 'foo' }) && // 若操作符左側不為變數或變數名不為 foo 則不做任何操作          t.isIdentifier(path.node.right, { name:
'bar' }) // 若操作符右側不為變數或變數名不為 bar 則不做任何操作        if (isMatchCondition) {          path.node.left = t.identifier('sebmck') // 把操作符左側的變數名改為 sebmck          path.node.right = t.identifier('dork') // 把操作符右側的變數名改為 dork        }      }    }  } }

編寫如上外掛,我們的程式碼經過編譯就會得到 sebmck === dork,一個最基本的 Babel Plugin 就大功告成啦。

Preset——轉換規則的集合

相信大家對於 Babel Preset 都不陌生。很多開發者的專案中,Babel 的配置都會有一段 "presets": ["es2015"]。接下來我們就以 babel-preset-es2015 為例,一起來看看 preset 內部究竟是什麼樣子的。

開啟 babel-preset-es2015 的原始碼,你會發現整個 preset 其實就是一些外掛的集合。如果說 plugin 是處理程式碼的規則,那麼 preset 就是一組規則的集合。你完全可以自己拼裝不同的外掛,生成一個新的預設。

通過Babel可以做什麼


Babel 最基礎和廣泛的用法就是將 ES6 程式碼轉換為 ES5 程式碼。除此之外,官方還提供了 JSX 語法支援、Flow 語法支援等外掛。接下來我們要聊的是一些由社群開發,基於 Babel 或與 Babel 息息相關的開源外掛。

Prepack——程式碼效能優化

前一陣子由 Facebook 團隊推出的 Prepack 在前端圈可謂一石激起千層浪,在各類技術社群中也引發了激烈的討論。本文暫且不談現階段 Prepack 是否適用於業務生產環境,只根據 Prepack 的特性和原理,聊一聊 Prepack 與 Babel 擦出了怎樣的火花。

Prepack 是一個 JavaScript 程式碼優化工具。它能夠完成一些可以在編譯階段執行的計算工作,將一部分程式碼替換為等價但更簡單的賦值語句,從而省去大量的計算和物件分配工作。

我們先通過一張 Sebastian McKenzie 在 React-Europe 2016 分享 Prepack 的相關視訊截圖,來看一下 Prepack 的工作原理,如圖2所示。

0?wx_fmt=jpeg

圖2 Prepack的工作原理

從上圖可見,Prepack 的 Code => ASTAST => Code 都是通過 Babel 來完成的。當你想要在預編譯階段做一些事情的時候,自己做一個符合 ECMAScript 標準的編譯器是不太明智的。Babel 完美地解決了這個問題,它既可以為業務開發者服務,又可以為編譯工具開發者服務。

babel-plugin-import——指定庫的按需載入

在 Webpack 2 推出 Tree Shaking 之前,按需引入一直是前端工程中令人頭疼的問題,即使你寫的是:

import { module } from 'library'

編譯後的結果也會是:

var module = require('library').module

也就是說,模組並沒有按需引入,依然載入了所有的程式碼。為此,ant-design 團隊做了一個叫做 babel-plugin-import 的 Babel 外掛,通過簡單的配置就可以把如下程式碼:

import { Button } from 'antd'

編譯成:

var _button = require('antd/lib/button')

這樣就避免了只想用其中一個很小的模組,卻要引入整個類庫的尷尬。

具體的實現大家可以看它的原始碼(https://github.com/ant-design/babel-plugin-import/blob/master/src/Plugin.js)。

babel-eslint——程式碼風格檢查

相信大家對 ESLint 不會很陌生。ESLint 也是通過 AST 對程式碼進行解析,但 ESLint 團隊使用的是自己開發的 Espree。這就導致當 Babel 支援了一種新的自定義語法(如 Flow)時,ESLint 無法直接支援,因此 babel-eslint 就出現了。

ESLint 團隊在 Why another parser 中寫道:

ESLint had been relying on Esprima as its parser from the beginning. While that was fine when the JavaScript language was evolving slowly, the pace of development increased dramatically and Esprima had fallen behind. ESLint, like many other tools reliant on Esprima, has been stuck in using new JavaScript language features until Esprima updates, and that caused our users frustration.

ESLint 團隊曾經為了跟隨 JavaScript 的快速發展而選擇自行開發語法解析器,現在卻又被其拖累,不得不再推出一個使用 Babel 作為解析器的版本。

定義一套語法

經常聽到有開發者報怨 JavaScript 語言設計得太爛了,要是能用 XXX 語言來寫前端就好了。那麼現在機會來了,雖然 Babel 不能識別不符合 ECMAScript 標準的語法,但是它允許你自定義規則。也就是說,你完全可以自己定義一套語法,愉快地用 XXX 語言來做前端開發!

在餓了麼大前端的一些實踐

Babel 的平臺化和外掛化使它變得極易擴充套件。這種靈活性使得我們可以對它寄予無限的期望:不管你是想對程式碼進行轉換,還是僅僅想引入一個語法解析器,Babel 都可以勝任。 除了社群提供的工具,目前餓了麼大前端部門在嘗試通過 Babel 外掛實現一些對業務的擴充套件,比如:

  • 無痕埋點:目前主流的前端埋點方式還是手工打點。這樣做的效率不高,而且每次新增新的埋點還需要發版和等待資料收集。我們正在嘗試通過 Babel 外掛注入打點程式碼,並在篩選資料時通過給函式名做標記的方式,隨時獲取過去一段時間內的埋點資訊。

  • 錯誤日誌收集:目前市面上的前端錯誤日誌收集主要還是依賴於捕獲錯誤的堆疊資訊。生產環境上的程式碼都經過了混淆,這給識別工作帶來了很大的麻煩。我們正在嘗試在 catch 語句中直接注入原始碼的資訊,儘可能收集更加準確和詳細的錯誤資訊。

總結


說了這麼多,就是希望大家改變對 Babel 的看法,不再僅僅把它當成一個 6to5 的工具來用。如果你想在預編譯階段做些什麼,Babel 是你最好的選擇。

近幾年,前端在預編譯階段的應用取得了很不錯的成績,我相信在未來的一段時間,會有越來越多與 Babel 相關的好工具出現。

作者簡介:袁德鑫,就職於餓了麼大前端,目前在探索前端預編譯階段的無痕埋點。

本文為《程式設計師》原創文章,未經允許不得轉載,更多精彩文章請點選「閱讀原文」訂閱《程式設計師》

SDCC 2017


11月25日, SDCC 2017“前端技術實戰線上峰會”將在CSDN學院以線上直播形式召開。


作為SDCC系列技術峰會的一部分,來自阿里巴巴、蘇寧雲商、美團點評、餓了麼、去哪兒網、白鷺時代等多家企業的前端專家及技術圖書作者,將圍繞React、AngularJS、Weex前端熱門框架在企業中的應用實踐,及WebAssembly、MVVM等技術熱點展開深入分享,幫助大家解決實際生產中遇到問題。每個演講時段均設有答疑交流環節,與會者和講師可零距離互動。


掃描下方二維碼,歡迎入群交流。

0?wx_fmt=png