成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

探索babel和babel插件是怎么工作的

dongxiawu / 739人閱讀

摘要:我們更多要去做的是去修改和改變生成的這個(gè)抽象語法樹。我們已經(jīng)知道會(huì)遍歷節(jié)點(diǎn)組成的抽象語法樹,每一個(gè)節(jié)點(diǎn)都會(huì)有自己對應(yīng)的比如變量節(jié)點(diǎn)等。

你有可能會(huì)聽到過這個(gè)詞 webpack工程師 ,這個(gè)看似像是一個(gè)專業(yè)很強(qiáng)的職位其實(shí)很多時(shí)候是一些前端對現(xiàn)在前端工作方式對一些吐槽,對于一個(gè)之前沒有接觸過webpack,nodejs,babel 之類的工具的人來說,看到大量的配置文件后很多人都會(huì)看懵

很多人就干脆不管這些東西,直接上手寫業(yè)務(wù)代碼,把這些構(gòu)建工具就相當(dāng)于黑科技,我們把所有的文件都經(jīng)過這些工具最終生成一個(gè)或者幾個(gè)打包后的文件,其中關(guān)于優(yōu)化和代碼轉(zhuǎn)換問題其實(shí)一大部分都是在這些配置里面的。如果我們不去了解其中的一部分原理,后面遇到很多問題(如打包后文件體積過大)時(shí)候都是束手無策,而且萬一哪天構(gòu)建工具出現(xiàn)問題時(shí)候可能連工作都開展不下去了。

既然我們?nèi)粘6家玫?,最好的方式就是去研究一下這些工具的原理的作用,讓這些工具成為我們手中的利器,而不是工作上的絆腳石,而且這些工具的設(shè)計(jì)者都是頂級(jí)的工程師,當(dāng)你敲開壁壘探究內(nèi)部秘密時(shí)候,我相信你會(huì)感受到其中的編程之美。

這里我們?nèi)ヌ剿饕幌?b>babel的原理

babel 是什么?

Babel · The compiler for writing next generation JavaScript

6to5

你在npm上可以看到這樣一個(gè)包名字是6to5, 光看名字可能會(huì)讓人感覺到很詫異,名字看起來可能有點(diǎn)奇怪,其實(shí)babel 在開始的時(shí)候名字就是這個(gè)。簡單粗暴es6 -> es5,一下子就看懂了babel 是用來干啥的,但是很明顯這不是一個(gè)好名字,這個(gè)名字會(huì)讓人感覺到es6普及之后這個(gè)庫就沒用了,為了保持活力這個(gè)庫可能要不停的修改名字。下面是babel作者一次分享中假設(shè)如果按這個(gè)命名法則可能出現(xiàn)的名稱

很明顯發(fā)生這種情況是很不合理的,團(tuán)隊(duì)內(nèi)部經(jīng)過大量討論后,最終選擇了babel,這與電影銀河系漫游指南中的Babel fish相應(yīng),也有關(guān)系到圣經(jīng)中的一個(gè)故事Tower of Babel。(ps.優(yōu)秀的人總是也很有情懷。)

babel is the new jQuery

redux 的作者曾說過這樣一句話,可以換一種理解為

babel : AST :: jQuery : DOM

babel 對于 AST 就相當(dāng)于 jQuery 對于 DOM, 就是說babel給予了我們便捷查詢和修改 AST 的能力。(AST -> Abstract Syntax Tree) 抽象語法樹 后面會(huì)講到。

為什么要用babel轉(zhuǎn)換代碼

我們之前做一些兼容都會(huì)都會(huì)接觸一些 Polyfill 的概念,比如如果某個(gè)版本的瀏覽器不支持 Array.prototype.find 方法,但是我們的代碼中有用到Arrayfind 函數(shù),為了支持這些代碼,我們會(huì)人為的加一些兼容代碼

if (!Array.prototype.find) {
  Object.defineProperty(Array.prototype, "find", {
      // 實(shí)現(xiàn)代碼
      ...
  });
}

對于這種情況做兼容也很好實(shí)現(xiàn),引入一個(gè) Polyfill 文件就可以了,但是有一些情況我們使用到了一些新語法,或者一些其他寫法

// 箭頭函數(shù)
var a = () => {}
// jsx
var Component = () => 

這種情況靠 Polyfill, 因?yàn)橐恍g覽器根本就不識(shí)別這些代碼,這時(shí)候就需要把這些代碼轉(zhuǎn)換成瀏覽器識(shí)別的代碼。babel就是做這個(gè)事情的。

babel做了哪些事情

為了轉(zhuǎn)換我們的代碼,babel做了三件事

Parser 解析我們的代碼轉(zhuǎn)換為AST

Transformer 利用我們配置好的plugins/presetsParser生成的AST轉(zhuǎn)變?yōu)樾碌?b>AST。

Generator 把轉(zhuǎn)換后的AST生成新的代碼

從圖上看 Transformer 占了很大一塊比重,這個(gè)轉(zhuǎn)換過程就是babel中最復(fù)雜的部分,我們平時(shí)配置的plugins/presets就是在這個(gè)模塊起作用。

從簡單的說起

可以看到要想搞懂babel, 就是去了解上面三個(gè)步驟都是在干什么,我們先把比較容易看懂的地方開始了解一下。

Parser 解析

解析步驟接收代碼并輸出 AST,這其中又包含兩個(gè)階段詞法分析語法分析。詞法分析階段把字符串形式的代碼轉(zhuǎn)換為 令牌(tokens) 流。語法分析階段會(huì)把一個(gè)令牌流轉(zhuǎn)換成 AST 的形式,方便后續(xù)操作。

Generator 生成

代碼生成步驟把最終(經(jīng)過一系列轉(zhuǎn)換之后)的 AST 轉(zhuǎn)換成字符串形式的代碼,同時(shí)還會(huì)創(chuàng)建源碼映射(source maps)。代碼生成其實(shí)很簡單:深度優(yōu)先遍歷整個(gè) AST,然后構(gòu)建可以表示轉(zhuǎn)換后代碼的字符串。

babel的核心內(nèi)容

看起來babel的主要工作都集中在把解析生成的AST經(jīng)過plugins/presets然后去生成新的AST這上面了。

AST抽象語法樹

我們一直在提到AST它究竟是什么呢,既然它的名字叫做抽象語法樹,我們可以想象一下如果把我們的程序用樹狀表示會(huì)是什么樣呢。

var a = 1 + 1
var b = 2 + 2

我們想象一下要表示上述代碼應(yīng)該是什么樣子,首先必須有東西可以表示這些具體的聲明,變量,常量的具體信息,比如(這棵樹上肯定有二個(gè)變量,變量名是a和b,肯定有兩個(gè)運(yùn)算語句,操作符是 + ),有了這些信息還不夠,我們必須建立起它們之間的關(guān)系,比如一個(gè)聲明語句,聲明類型是 var, 左側(cè)是變量, 右側(cè)是表達(dá)式。有了這些信息我們就可以還原這個(gè)程序,這也是把代碼解析成AST時(shí)候所做的事情,對應(yīng)上面我們說的詞法分析語法分析

AST中我們用node(節(jié)點(diǎn))來表示各個(gè)代碼片段,比如我們上面程序整體就是一個(gè)節(jié)點(diǎn)Program節(jié)點(diǎn)(所有的 AST 根節(jié)點(diǎn)都是 Program 節(jié)點(diǎn)),因?yàn)樗旅嬗袃蓷l語句所以它的 body屬性上就兩個(gè)聲明節(jié)點(diǎn)VariableDeclaration。所以上面程序的AST就類似這樣

可以看到在節(jié)點(diǎn)上用各個(gè)的屬性去表示各種信息以及程序之間的關(guān)系,那這些節(jié)點(diǎn)每一個(gè)叫什么名字,都用哪些屬性名呢?我們可以在說明文檔上找到這些說明。

關(guān)于接口

看這個(gè)文檔時(shí)候我們可以看到說明大多是類似這種

interface Node {
  type: string;
  loc: SourceLocation | null;
}

這里提到interface這個(gè)我們在其他語言中是比較常見的,比如Node規(guī)定了typeloc屬性,如果其他節(jié)點(diǎn)繼承自Node,那么它也會(huì)實(shí)現(xiàn)typeloc屬性就是說繼承自Node的節(jié)點(diǎn)也會(huì)有這些屬性,基本所有節(jié)點(diǎn)都繼承自Node,所以我們基本可以看到loc這個(gè)屬性loc表示個(gè)一些位置信息。

節(jié)點(diǎn)單位

我們程序很多地方都會(huì)被拆分成一個(gè)個(gè)的節(jié)點(diǎn),節(jié)點(diǎn)里面也會(huì)套著其他的節(jié)點(diǎn),我們在文檔中可以看到AST結(jié)構(gòu)的各個(gè) Node 節(jié)點(diǎn)都很細(xì)微,比如我們聲明函數(shù),函數(shù)就是一個(gè)節(jié)點(diǎn)FunctionDeclaration,函數(shù)名和形參那么參數(shù)都是一個(gè)變量節(jié)點(diǎn)Identifier。生成的節(jié)點(diǎn)往往都很復(fù)雜,我們可以借助astexplorer來幫助我們分析AST結(jié)構(gòu)。

圖像展示

有了上面這些概念我們已經(jīng)可以大概了解AST的概念,以及各個(gè)模塊代表的含義,假設(shè)我們有這樣一個(gè)程序,我們用圖形簡易的分析下它的結(jié)構(gòu)

function square (n) {
    return n * n
}

節(jié)點(diǎn)遍歷

經(jīng)過一番努力我們終于了解了AST以及其中內(nèi)容的含義,但是這一部分基本不需要我們做什么,babel會(huì)借助Babylon幫我們生成我們需要的AST結(jié)構(gòu)。我們更多要去做的是去修改和改變Babylon生成的這個(gè)抽象語法樹。

babel拿到抽象語法樹后會(huì)使用babel-traverse進(jìn)行遞歸的樹狀遍歷,對于每一個(gè)節(jié)點(diǎn)都會(huì)向下遍歷到盡頭,然后向上遍歷退出分支去尋找下一個(gè)分支。這樣確保我們能找到任何一個(gè)節(jié)點(diǎn),也就是能訪問到我們代碼的任何一個(gè)部分??墒俏覀円趺慈ネ瓿尚薷牟僮髂兀?b>babel給我們提供了下面這兩個(gè)概念。

visitor

我們已經(jīng)知道babel會(huì)遍歷節(jié)點(diǎn)組成的抽象語法樹,每一個(gè)節(jié)點(diǎn)都會(huì)有自己對應(yīng)的type,比如變量節(jié)點(diǎn)Identifier等。我們需要給babel提供一個(gè)visitor對象,在這個(gè)對象上面我們以這些節(jié)點(diǎn)的type做為key,已一個(gè)函數(shù)作為值,類似如下,

const visitor = {
    Identifier: {
        enter() {
              console.log("traverse enter a Identifier node!")
        },
        exit() {
              console.log("traverse exit a Identifier node!")
        }
      }
}

這樣在遍歷進(jìn)入到對應(yīng)到節(jié)點(diǎn)時(shí)候,babel就會(huì)去執(zhí)行對應(yīng)的enter函數(shù),向上遍歷退出對應(yīng)節(jié)點(diǎn)時(shí)候,babel就會(huì)去執(zhí)行對應(yīng)的exit函數(shù),接著上面的代碼我們可以做一個(gè)測試

const babel = require("babel-core")

const code = `var a = b + c + d`

// 如果plugins是個(gè)函數(shù)則返回的對象要有visitor屬性,如果是個(gè)對象則直接定義visitor屬性
const MyVisitor = {
  visitor
}

babel.transform(code, {
  plugins: [MyVisitor]
})

我們執(zhí)行對應(yīng)代碼可以看到上面enterexit函數(shù)分別執(zhí)行了四次

traverse enter a Identifier node! 
traverse exit a Identifier node!  
... x4

從上面簡單的代碼上也可以看到a,b,c,d四個(gè)變量,它們應(yīng)該屬于同一級(jí)別的節(jié)點(diǎn)樹上,所以遍歷時(shí)候會(huì)分別進(jìn)入對應(yīng)節(jié)點(diǎn)然后退出再去下一個(gè)節(jié)點(diǎn)。

Paths

我們通過visitor可以在遍歷到對應(yīng)節(jié)點(diǎn)執(zhí)行對應(yīng)的函數(shù),可是要修改對應(yīng)節(jié)點(diǎn)的信息,我們還需要拿到對應(yīng)節(jié)點(diǎn)的信息以及節(jié)點(diǎn)和所在的位置(即和其他節(jié)點(diǎn)間的關(guān)系), visitor在遍歷到對應(yīng)節(jié)點(diǎn)執(zhí)行對應(yīng)函數(shù)時(shí)候會(huì)給我們傳入path參數(shù),輔助我們完成上面這些操作。注意 Path 是表示兩個(gè)節(jié)點(diǎn)之間連接的對象,而不是當(dāng)前節(jié)點(diǎn),我們上面訪問到了Identifier節(jié)點(diǎn),它傳入的 path參數(shù)看起來是這樣的

{
  "parent": {
    "type": "VariableDeclarator",
    "id": {...},
    ....
  },
  "node": {
    "type": "Identifier",
    "name": "..."
  }
}

從上面我們可以看到 path 表示兩個(gè)節(jié)點(diǎn)之間的連接,通過這個(gè)對象我們可以訪問到節(jié)點(diǎn)、父節(jié)點(diǎn)以及進(jìn)行一系列跟節(jié)點(diǎn)操作相關(guān)的方法。我們修改一下上面的 visitor 函數(shù)

const visitor = {
    Identifier: {
    enter(path) {
      console.log("traverse enter a Identifier node the name is " + path.node.name)
    },
    exit(path) {
      console.log("traverse exit a Identifier node the name is " + path.node.name)
    }
  }
}

在執(zhí)行一下上面的代碼就可以看到name打印出來的依次是a,b,c,d。這樣我們就有可以修改操作我們需要改變的節(jié)點(diǎn)了。另外path對象上還包含添加、更新、移動(dòng)和刪除節(jié)點(diǎn)有關(guān)的其他很多方法,我們可以通過文檔去了解。

一些有用的工具

babel為了方便我們開發(fā),在每一個(gè)環(huán)節(jié)都有很多人性化的定義也提供了很多實(shí)用性的工具,比如之前我們在定義visitor時(shí)候分別定義了enter,exit函數(shù),可很多時(shí)候我們其實(shí)只用到了一次在enter的時(shí)候做一些處理就行了。所以我們?nèi)绻覀冎苯佣x節(jié)點(diǎn)的key為函數(shù),就相當(dāng)于定義了enter函數(shù)

const visitor = {
    Identifier(){
        // dosmting
    }
}

// 等同于 ↓ ↓ ↓ ↓ ↓ ↓

const visitor = {
    Identifier: {
        enter() {
            // dosmting
        }
    }
}

上面我們還提到了plugins是函數(shù)的情況,其實(shí)我們寫的差距一般都是一個(gè)函數(shù),這個(gè)入口函數(shù)上babel也會(huì)穿入一個(gè)babel-types,這是一個(gè)用于AST 節(jié)點(diǎn)的 Lodash 式工具庫(類似lodash對于js的幫助), 它包含了構(gòu)造、驗(yàn)證以及變換 AST 節(jié)點(diǎn)的方法。 該工具庫包含考慮周到的工具方法,對編寫處理AST邏輯非常有用。

實(shí)際運(yùn)用

假如我們有如下代碼

const a = 3 * 103.5 * 0.8
log(a)
const b = a + 105 - 12
log(b)

我們發(fā)現(xiàn)這里把console.log簡寫成了log,為了讓這些代碼可以執(zhí)行,我們現(xiàn)在用babel裝置去轉(zhuǎn)換一下這些代碼。

改變log函數(shù)調(diào)用本身

既然是console.log沒有寫全,我們就改變這個(gè)log函數(shù)調(diào)用的地方,把每一個(gè)log替換成console.log,我們看一下log(*)屬于函數(shù)執(zhí)行語句,相對應(yīng)的節(jié)點(diǎn)就是CallExpression,我們看下它的結(jié)構(gòu)

interface CallExpression <: Expression {
  type: "CallExpression";
  callee: Expression | Super | Import;
  arguments: [ Expression | SpreadElement ];
  optional: boolean | null;
}

callee是我們函數(shù)執(zhí)行的名稱,arguments就是我們穿入的參數(shù),參數(shù)我們不需要改變,只需要把函數(shù)名稱改變就好了,之前的callee是一個(gè)變量,我們現(xiàn)在要把它變成一個(gè)表達(dá)式(取對象屬性值的表達(dá)式),我們看一下手冊可以看到是一個(gè)MemberExpression類型的值,這里也可以借助之前提到的網(wǎng)站astexplorer來幫助我們分析。有了這些信息我們就可以去實(shí)現(xiàn)我們的目的了,我們這里手動(dòng)引入一下babel-types輔助我們創(chuàng)建新的節(jié)點(diǎn)

const babel = require("babel-core")
const t = require("babel-types")

const code = `
    const a = 3 * 103.5 * 0.8
    log(a)
    const b = a + 105 - 12
    log(b)
`

const visitor = {
    CallExpression(path) {
        // 這里判斷一下如果不是log的函數(shù)執(zhí)行語句則不處理
        if (path.node.callee.name !== "log") return
        // t.CallExpression 和 t.MemberExpression分別代表生成對于type的節(jié)點(diǎn),path.replaceWith表示要去替換節(jié)點(diǎn),這里我們只改變CallExpression第一個(gè)參數(shù)的值,第二個(gè)參數(shù)則用它自己原來的內(nèi)容,即本來有的參數(shù)
        path.replaceWith(t.CallExpression(
            t.MemberExpression(t.identifier("console"), t.identifier("log")),
            path.node.arguments
        ))
    }
}

const result = babel.transform(code, {
    plugins: [{
        visitor: visitor
    }]
})

console.log(result.code)

執(zhí)行后我們可以看到結(jié)果

const a = 3 * 103.5 * 0.8;
console.log(a);
const b = a + 105 - 12;
console.log(b);
直接在模塊中聲明log

我們已經(jīng)知道每一個(gè)模塊都是一個(gè)對于的AST,而AST根節(jié)點(diǎn)是 Program 節(jié)點(diǎn),下面的語句都是body上面的子節(jié)點(diǎn),我們只要在body頭聲明一下log變量,把它定義為console.log,后面這樣使用就也正常了。

這里簡單的修改下visitor

const visitor = {
    Program(path) {
        path.node.body.unshift(
      t.VariableDeclaration(
        "var",
        [t.VariableDeclarator(
          t.Identifier("log"),
          t.MemberExpression(t.identifier("console"), t.identifier("log"))
        )]
      )
    )
    }
}

執(zhí)行后生成的代碼為

var log = console.log;

const a = 3 * 103.5 * 0.8;
log(a);
const b = a + 105 - 12;
log(b);
總結(jié)

到這里我們已經(jīng)簡單的分析代碼,修改一些抽象語法樹上的內(nèi)容來達(dá)到我們的目的,但是還是有很多中情況還沒考慮進(jìn)去,而babel現(xiàn)階段不僅僅代表著去轉(zhuǎn)換es6代碼之類的功能,實(shí)際上我們自己可以寫出很多有意思的插件,歡迎來了解babel,按照自己的想法寫一些插件或者去貢獻(xiàn)一些代碼,相信在這個(gè)過程中你收獲的絕對比你想象中的要更多!

本文首發(fā)與個(gè)人博客

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/107405.html

相關(guān)文章

  • Babel 插件原理理解與深入

    摘要:抽象語法樹是怎么生成的談到這點(diǎn),就要說到計(jì)算機(jī)是怎么讀懂我們的代碼的。需要注意什么狀態(tài)狀態(tài)是抽象語法樹轉(zhuǎn)換的敵人,狀態(tài)管理會(huì)不斷牽扯我們的精力,而且?guī)缀跛心銓顟B(tài)的假設(shè),總是會(huì)有一些未考慮到的語法最終證明你的假設(shè)是錯(cuò)誤的。 現(xiàn)在談到 babel 肯定大家都不會(huì)感覺到陌生,雖然日常開發(fā)中很少會(huì)直接接觸到它,但它已然成為了前端開發(fā)中不可或缺的工具,不僅可以讓開發(fā)者可以立即使用 ES 規(guī)范...

    draveness 評(píng)論0 收藏0
  • gulp-work-flow 前端工作流原來可以這么簡單

    摘要:話不多說,今天的主題是使用打造傳統(tǒng)項(xiàng)目的前端工作流。是一個(gè)廣泛使用的轉(zhuǎn)碼器,可以將代碼轉(zhuǎn)為代碼,從而在現(xiàn)有環(huán)境執(zhí)行。這意味著,你可以用的方式編寫程序,又不用擔(dān)心現(xiàn)有環(huán)境是否支持。 概述 最近前端一直是一個(gè)火熱的話題,前端技術(shù)棧也是伴隨著nodejs的出現(xiàn)而更替的飛快,導(dǎo)致大部分前端開發(fā)者曾一度迷茫在這各種技術(shù)選型上,比如前端自動(dòng)化工具就有Grunt,Gulp,Webpack,F(xiàn)is3等...

    weakish 評(píng)論0 收藏0
  • JavaScript如何工作:深入類繼承內(nèi)部原理+Babel TypeScript 之間轉(zhuǎn)換

    摘要:下面是用實(shí)現(xiàn)轉(zhuǎn)成抽象語法樹如下還支持繼承以下是轉(zhuǎn)換結(jié)果最終的結(jié)果還是代碼,其中包含庫中的一些函數(shù)??梢允褂眯碌囊子谑褂玫念惗x,但是它仍然會(huì)創(chuàng)建構(gòu)造函數(shù)和分配原型。 這是專門探索 JavaScript 及其所構(gòu)建的組件的系列文章的第 15 篇。 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你! 如果你錯(cuò)過了前面的章節(jié),可以在這里找到它們: JavaScript 是...

    PrototypeZ 評(píng)論0 收藏0
  • 平庸前端碼農(nóng)之蛻變 — AST

    摘要:為什么要談抽象語法樹如果你查看目前任何主流的項(xiàng)目中的,會(huì)發(fā)現(xiàn)前些年的不計(jì)其數(shù)的插件誕生。什么是抽象語法樹估計(jì)很多同學(xué)會(huì)和圖中的喵一樣,看完這段官方的定義一臉懵逼。它讀取我們的代碼,然后把它們按照預(yù)定的規(guī)則合并成一個(gè)個(gè)的標(biāo)識(shí)。 前言 首先,先說明下該文章是譯文,原文出自《AST for JavaScript developers》。很少花時(shí)間特地翻譯一篇文章,咬文嚼字是件很累的事情,實(shí)在...

    dreamans 評(píng)論0 收藏0
  • 深入淺出 ES6:ES6 與 Babel / Broccoli 聯(lián)用

    摘要:深入淺出指的是添加在標(biāo)準(zhǔn)第六版中的編程語言的新特性,簡稱為。登場在一個(gè)具體的項(xiàng)目中,使用有幾種不同的方法。這是為了避免在安裝時(shí)使用根管理權(quán)限。以用戶角度展示系統(tǒng)響應(yīng)速度,以地域和瀏覽器維度統(tǒng)計(jì)用戶使用情況。 深入淺出 ES6 指的是添加在 ECMASript 標(biāo)準(zhǔn)第六版中的 JavaScript 編程語言的新特性,簡稱為 ES6。 雖然 ES6 剛剛到來,但是人們已經(jīng)開始談?wù)?ES7 ...

    NervosNetwork 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<