babel原理&plugin實戰(zhàn)(轉(zhuǎn))

本文將講解babel是如何運行的般婆,AST的結(jié)構(gòu)弧哎,以及怎么創(chuàng)建一個babel的插件。

再講babel之前淳蔼,先不講babel侧蘸,AST的這些概念,先帶你實現(xiàn)一個簡易的babel解析器鹉梨,這樣再回過頭來講這些概念就容易理解多了讳癌。

tiny-compiler 編譯器

想象一下我們有一些新特性的語法,其中add subtract是普通的函數(shù)名存皂,需要轉(zhuǎn)義到正常的javascript語法晌坤,以便讓瀏覽器能夠兼容的運行。

(add 2 2)

要轉(zhuǎn)義成如下

add(2, 2)

編譯器都分為三個步驟:

  • Parsing 解析

  • Transformation 轉(zhuǎn)義

  • Code Generation 代碼生成

Parsing 解析

Parsing階段分成兩個子階段旦袋,

  • Lexical Analysis 詞法分析

  • Syntactic Analysis語法分析骤菠,先寫好我們要轉(zhuǎn)化的代碼

// 這是我們要轉(zhuǎn)化的code

Lexical Analysis 詞法分析

Lexical Analysis 詞法分析可以理解為把代碼拆分成最小的獨立的語法單元,去描述每一個語法疤孕,可以是操作符商乎,數(shù)字,標(biāo)點符號等祭阀,最后生成token數(shù)組鹉戚。

// 第一步,Lexical Analysis柬讨,轉(zhuǎn)化成tokens類似如下

那我們開始實現(xiàn)它吧崩瓤,干!

function tokenizer(input) {

Syntactic Analysis 語法分析

Syntactic Analysis 語法分析就是根據(jù)上一步的tokens數(shù)組轉(zhuǎn)化成語法之前的關(guān)系踩官,這就是Abstract Syntax Tree,也就是我們常說的AST却桶。

// 第二步,Syntactic Analysis,轉(zhuǎn)化成AST類似如下

我們再來實現(xiàn)一個parser颖系,轉(zhuǎn)化成AST嗅剖。

function parser(tokens) {

從上述代碼來看,跟階段AST是根節(jié)點是type=Program嘁扼,body是一個嵌套的AST數(shù)組結(jié)構(gòu)信粮。再單獨處理了number和string類型之后,再遞歸的調(diào)用walk函數(shù)趁啸,以解決嵌套的括號表達式强缘。

Transformation 轉(zhuǎn)義

traverser 遍歷器

我們最終的目的肯定是想轉(zhuǎn)化成我們想要的代碼,那怎么轉(zhuǎn)化呢不傅?答案就是更改我們剛剛得到的AST結(jié)構(gòu)旅掂。

那怎么去改AST呢?直接去操作這個樹結(jié)構(gòu)肯定是不現(xiàn)實的访娶,所以我們需要遍歷這個AST商虐,利用深度優(yōu)先遍歷的方法遍歷這些節(jié)點,當(dāng)遍歷到某個節(jié)點時崖疤,再去調(diào)用這個節(jié)點對應(yīng)的方法秘车,再方法里面改變這些節(jié)點的值就輕而易舉了。

想象一下我們有這樣的一個visitor劫哼,就是上文說道的遍歷時調(diào)用的方法

var visitor = {

由于深度優(yōu)先遍歷的特性叮趴,我們遍歷到一個節(jié)點時有enterexit的概念,代表著遍歷一些類似于CallExpression這樣的節(jié)點時沦偎,這個語句疫向,enter表示開始解析,exit表示解析完畢。比如說上文中:

 *   -> Program (enter)

然后有一個函數(shù)豪嚎,接受astvistor作為參數(shù)搔驼,實現(xiàn)遍歷,類似于:

traverse(ast, {

先實現(xiàn)traverser吧侈询。

function traverser(ast, visitor) {

transformer 轉(zhuǎn)換器

有了traverser遍歷器后舌涨,就開始遍歷吧,先看看前后兩個AST的對比扔字。


這里注意多了一中ExpressionStatement的type囊嘉,以表示subtract(4, 2)這樣的結(jié)構(gòu)。

遍歷的過程就是把左側(cè)AST轉(zhuǎn)化成右側(cè)AST革为。

function transformer(ast) {

CodeGeneration 代碼生成

那最后一個階段就是用心生成的AST生成我們最后的代碼了扭粱,也是生成AST的一個反過程。

function codeGenerator(node) {

總結(jié)

這樣我們一個tiny-compiler就寫好了震檩,最后可以執(zhí)行下面的代碼去試試啦琢蛤。


從上述代碼中就可以看出來蜓堕,一個代碼轉(zhuǎn)化的過程就把包括了tokenizer詞法分析階段,parser預(yù)發(fā)分析階段(AST生成)博其,transformer轉(zhuǎn)義階段源请,codeGenerator代碼生成階段蚓炬。那么在寫babel-plugin的時候扁凛,其實就是在寫其中的transformer病附,其他的部分已經(jīng)被babel完美的實現(xiàn)了。

babel plugin 概念

先上手看一個簡單的babel plugin示例


這個plugin造成的效果:

// 源代碼

就是把所有的bool類型的值轉(zhuǎn)化成 !0 或者 !1峰髓,這是代碼壓縮的時候使用的一個技巧傻寂。

那么逐行來分析這個簡單的plugin。一個plugin就是一個function儿普,入?yún)⒕褪莃abel對象崎逃,這里利用到了babel中types對象,來自于@babel/types這個庫眉孩,然后操作path對象進行節(jié)點替換操作。

path

path是肯定會用到的一個對象勒葱。我們可以用過path訪問到當(dāng)前節(jié)點浪汪,父節(jié)點,也可以去調(diào)用添加凛虽、更新死遭、移動和刪除節(jié)點有關(guān)的其他很多方法。舉幾個示例

// 訪問當(dāng)前節(jié)點的屬性凯旋,用path.node.property訪問node的屬性

@babel/types

可以理解它為一個工具庫呀潭,類似于Lodash,里面封裝了非常多的幫做方法至非,一般用處如下

  • 檢查節(jié)點 一般在類型前面加is就是判斷是否該類型
// 判斷當(dāng)前節(jié)點的left節(jié)點是否是identifier類型
  • 構(gòu)建節(jié)點
    直接手寫復(fù)雜的AST結(jié)構(gòu)是不現(xiàn)實的钠署,所以有了一些幫助方法去構(gòu)建這些節(jié)點,示例:
// 調(diào)用binaryExpression和identifier的構(gòu)建方法荒椭,生成ast

其中每一種節(jié)點都有自己的構(gòu)造方法谐鼎,都有自己特定的入?yún)ⅲ敿氄垍⒖脊俜轿臋n

scope

最后講一下作用域的概念趣惠,每一個函數(shù)狸棍,每一個變量都有自己的作用域,在編寫babel plugin的時候要特別小心味悄,再改變或者添加代碼的時候要注意不要破壞了原有的代碼結(jié)構(gòu)草戈。

path.scope中的一些方法可以操作作用域,示例:

// 檢查變量n是否被綁定(是否在上下文已經(jīng)有引用)

plugin實戰(zhàn)

寫一個自定義plugin是什么步驟呢侍瑟?

  • 這個plugin用來干嘛

  • 源代碼的AST

  • 轉(zhuǎn)換后代碼的AST
    tip: 可以去這個網(wǎng)站查看代碼的AST唐片。

plugin的目的

現(xiàn)在就做一個自定義的plugin,大家在應(yīng)用寫代碼的時候可以通過webpack配置alias,比如說配置@ -> ./src牵触,這樣import的時候就直接從src目錄下找所需要的代碼了淮悼,那么大家有在寫組件的時候用過這個功能嗎?這就是我們這個plugin的目的揽思。

代碼

我們有如下配置

"alias": {

源代碼以及要轉(zhuǎn)化的代碼如下:

// ./src/index.js

AST

源碼的AST展示如下

圖片

那我們看見是不是只需要找到ImportDeclaration節(jié)點中將source改成轉(zhuǎn)換之后的代碼是不是就可以了袜腥。

開始寫plugin

const localPath = require('path');

用plugin

回到我們的babel配置文件中來,這里我們用的是babel.config.json

{

這樣一個plugin的流程就走完了钉汗,歡迎大家多多交流羹令。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市损痰,隨后出現(xiàn)的幾起案子福侈,更是在濱河造成了極大的恐慌,老刑警劉巖卢未,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肪凛,死亡現(xiàn)場離奇詭異,居然都是意外死亡辽社,警方通過查閱死者的電腦和手機伟墙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滴铅,“玉大人戳葵,你說我怎么就攤上這事『撼祝” “怎么了拱烁?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長噩翠。 經(jīng)常有香客問我戏自,道長,這世上最難降的妖魔是什么绎秒? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任浦妄,我火速辦了婚禮,結(jié)果婚禮上见芹,老公的妹妹穿的比我還像新娘剂娄。我一直安慰自己,他們只是感情好玄呛,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布阅懦。 她就那樣靜靜地躺著,像睡著了一般徘铝。 火紅的嫁衣襯著肌膚如雪耳胎。 梳的紋絲不亂的頭發(fā)上惯吕,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機與錄音怕午,去河邊找鬼废登。 笑死,一個胖子當(dāng)著我的面吹牛郁惜,可吹牛的內(nèi)容都是我干的堡距。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼兆蕉,長吁一口氣:“原來是場噩夢啊……” “哼羽戒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起虎韵,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤易稠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后包蓝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驶社,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年养晋,在試婚紗的時候發(fā)現(xiàn)自己被綠了衬吆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡绳泉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姆泻,到底是詐尸還是另有隱情零酪,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布拇勃,位于F島的核電站四苇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏方咆。R本人自食惡果不足惜月腋,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瓣赂。 院中可真熱鬧榆骚,春花似錦、人聲如沸煌集。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽苫纤。三九已至碉钠,卻和暖如春纲缓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喊废。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工祝高, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人污筷。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓工闺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親颓屑。 傳聞我的和親對象是個殘疾皇子斤寂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容