Babel內部模塊概述

本文會對babel文檔文檔從一個推導角度來闡述每個babel模塊的作用,嘗試理清其中脈絡,方便快速理解。

本文不是官網的copyer或者中文翻譯

<a name="xVQ3u"></a>

核心

babel的核心功能在@babel/core包中,核心api為transform系列函數:

babel.transform(code, options, function(err, result) {
  result; // => { code, map, ast }
});

該函數可以將es6+代碼轉譯成es5代碼,所以被廣泛集成在其他工具里面,完成代碼的轉譯工作,如babel-loader內部就是調該api。

在babel中,還提供了@babel/cli@babel/register兩個工具,前者提供命令行工具函數對文件進行轉譯;后者提供require鉤子:對node的require函數改造,對后續require函數在執行時自動對模塊進行源碼轉譯后在導入。

babel的目標是對代碼進行轉譯,這個過程可以拆解為:解析源碼,遍歷ast改造代碼,重新生成代碼這三個過程。為了提高使用范圍,在v7+版本中,babel將功能拆解出來了多個工具,主要有:

  • 解析源碼: @babel/parser;
  • 遍歷ast改造代碼: @babel/traverse@babel/plugin-*;
  • 重新生成代碼:@babel/generator;
    <a name="Wh9AE"></a>

解析源碼

將源碼解析,生成ast(抽象語法樹),會將:

function square(n) {
  return n * n;
}

解析成:

{
  type: "FunctionDeclaration",
  id: {
    type: "Identifier",
    name: "square"
  },
  params: [{
    type: "Identifier",
    name: "n"
  }],
  body: {
    type: "BlockStatement",
    body: [{
      type: "ReturnStatement",
      argument: {
        type: "BinaryExpression",
        operator: "*",
        left: {
          type: "Identifier",
          name: "n"
        },
        right: {
          type: "Identifier",
          name: "n"
        }
      }
    }]
  }
}

ast可以簡單的理解為源碼字符串進行語法分析后的結構化數據,方便后續進行檢查或者改造。

ast中的節點一般還會包含坐標位置,如字符串下標,行數,列數等,更多詳細內容請參考官方文檔。

老版本babel中使用的是 acornacorn-jsx,在v7以上時,進行了fork改造為@babel/parser

另外@babel/core也集成了@babel/parser功能,可以直接從@babel/core中導出api直接使用:

babel.parse(code: string, options?: Object, callback: Function)

當前的解析器默認只支持最新的es6代碼,如果需要兼容一些新語法(非語法糖之類的新特性,新表達式和新操作服,如對象解構,可選表達式,類型等),需要擴展babel語法插件

很多工具其實只需要解析代碼即可,如代碼檢驗,如語法高亮,源碼中數據收集。
<a name="rzqMT"></a>

遍歷ast改造代碼

講過解析器已經將源碼解析成更好處理的結構化數據ast,如果需求是對代碼進行調整,只需對ast數據進行調整,然后使用生成器生成新的代碼即可。但整個babel需要解決的是將所有最新的es6+特性轉譯成向后兼容的瀏覽器可執行代碼(es5),需要處理的情況眾多,如果直接對ast進行改造,那么代碼將非常臃腫。且es規范還在不停的迭代中,臃腫的代碼的對后續維護迭代也帶來巨大的挑戰。針對這種困境,必須需要進行架構上的調整,使用插件化架構。

babel即是處于這樣一個原因,采用了訪問者模式。可以簡單的理解為,在對ast進行一個遍歷時,每次進入一個新的節點或者退出一個節點時,都會拜訪每一個插件,咨詢它們是否需要對當前的情況進行處理。這鐘架構證了性能,也保證了擴展性。

另一種插件化架構,也就是流式架構,如gulp。也就是插件隊列依次對上一個插件處理后的ast對象進行更深一層次的改造。但這種架構,需要多次循環ast, 在實際使用中,一般一個生產項目,文件內容巨大,文件數量居多,會導致性能崩潰。

所以babel核心框架中,只包含了訪問節點和調用插件的邏輯。實際對ast的改造,全部轉交給了插件。這也是為什么babel自帶了那么多@babel/plugin-*插件。同樣社區也擁有非常多的插件,從能能夠支持flow, typescipt這些新語法。

插件化的架構,也允許使用者進行拔插式配置,根據當前使用場景進行高度定制。這也就是在配置文件中如babrlrc.js可以配置插件的原因。

為了支持高度動態配置化來適配復雜的場景,babel會將每個插件負責的功能劃分足夠小,一般每個插件只會負責一個特性。這會導致使用時,需要去了解每一個插件的作用,然后在配置文件中配置超長的插件列表,帶來巨大的心智負擔和維護難度。為了解決這個問題,babel提供了預設的機制。簡單的理解就是一個babel配置可以繼承另一個配置,那么我們只需要繼承社區上或者官方專業人員配置的預設即可,如:

  • @babel/preset-env
  • @babel/preset-react
  • @babel/preset-typescript
  • @babel/preset-flow

為了方便插件中的復用,babel將遍歷ast的工具也開放出來為一個單獨的模塊@babel/traverse。將節點類型的判斷和創建節點的工具庫,放在了@babel/types

另外對于一些babel中多個模塊公用的一些工具,都封裝成工具模塊,也就是@babel/helper-*系列模塊,如:

  • @babel/helper-compilation-targets
  • @babel/helper-module-imports

由于babel自帶了那么多插件,所以很多helper其實是插件的輔助工具,如helper-module-imports就是輔助生成一些導入節點。

在es6+轉成es5的過程中,很多語法糖語法(語法上的細微調整),如let, const等實現直接用插件調整代碼即可解決。但對于其他的需要大端代碼才能實現的特性,如Array#includes,生成器,迭代器,async/await, promise等,如果每次都通過代碼展開,那么編譯后的代碼將會巨大。為了解決這個問題,會將includes的實現放在補丁(polyfill)中,然后直接使用補丁中的實現。如生成器,迭代器,async/await, promise等都是通過這種機制支持。

這些的補丁(polyfill)的導入方式也有兩種,一種是全量導入,也就是導入@babel/polyfill模塊。一種是按需導入,需要使用預設@babel/preset-env,根據實際使用情況,在使用的模塊中按需導入@babel/runtime中的補丁(polyfill)。如:

var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");

var Circle = function Circle() {
  _classCallCheck(this, Circle);
};

@babel/polyfill@babel/runtime的底層實現都是core-js

實際情景下,還是存在插件無法解決的情況:一個無法用老代碼補丁實現,也無法使用語法糖替換代碼的特性,如Proxy對象,這種特性一般需要js引擎從底層提供。在使用這些特性時,需要注意瀏覽器兼容性。

<a name="uaLlx"></a>

生成代碼

使用@babel/generator即可對一個ast樹重新生成為代碼。

<a name="S8g5j"></a>

配置

我們通常見到的babel配置就是就是用于指導babel行為的配置文件,可以簡單的理解為@babel/coretransform函數的選項支持使用配置文件配置。

更多的配置詳細使用等請看官網。

<a name="sRLPS"></a>

其他官方工具

babel還提供了一些其他工具,用于擴展babel生態鏈:

  • @babel/standalone: 支持瀏覽器上運行的babel版本,用于一些在線編輯網站,如JS Bin
  • @babel/code-frame: 代碼窗口,用于輸出類似這種:
  1 | class Foo {
> 2 |   constructor()
    |                ^
  3 | }
  • @babel/template: babel插件開發工具,支持根據代碼字符串創建ast節點。因為ast節點攜帶信息較多,且結構較深,在手動創建復雜的代碼節點時十分不便。使用官方提供的這個工具,可以快速創建一整段代碼節點,并且還支持占位符:
const buildRequire = template(`
  var IMPORT_NAME = require(SOURCE);
`);

const ast = buildRequire({
  IMPORT_NAME: t.identifier("myModule"),
  SOURCE: t.stringLiteral("my-module"),
});

//  ||   ||   ||
// \\// \\// \\//

const myModule = require("my-module");
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容

  • 了解 Babel 各個模塊 本文所研究的是 babel 6 版本。babel 6 是 2015年10月30號 發布...
    shianqi閱讀 4,602評論 0 7
  • 引言 babel是一個非常強大的工具,作用遠不止我們平時的ES6 -> ES5語法轉換這么單一。在前端進階的道路上...
    AlienZHOU閱讀 2,885評論 0 5
  • 前端工程化之前 前端工程化之前,我們編寫代碼 html js css . 因為瀏覽器只能讀懂這幾個代碼因為單獨的h...
    川九閱讀 547評論 0 0
  • Babel是前端很常用的轉碼器,更準確地說是轉譯器,是從源碼到源碼的轉換編譯器,例如可以將我們按照ES6標準寫的代...
    拉面頭_7c92閱讀 1,213評論 4 2
  • Babel 是一個編譯器,和其他編譯器一樣,編譯過程分為三個階段,分別是解析(parsing)、轉換(transf...
    暖A暖閱讀 126評論 0 0