一文讀懂Babel原理及用途,及如何進行插件開發

前置閱讀:Babel 插件手冊javascript

目錄

  • Babel介紹
  • Babel用途
  • Babel怎麼轉義代碼的?
  • Babel開發API
  • Babel的內置功能
  • Babel plugin的例子

Babel介紹

Babel 的前身是 6to5,6to5 是 2014 年 發佈的,主要功能是 就是 ES6 轉成 ES5。後更名babel。css

Babel 指的是 通天塔,是巴比倫文明裏面的 通天塔 當時地上的人們都說同一種語言,當人們離開東方以後,他們來到了示拿之地。在那裏,人們千方百計燒磚好讓他們可以造出一座城和一座高聳入雲的塔來傳播本身的名聲,以避免他們分散到世界各地。上帝來到人間後看到了這座城和這座塔,說一羣只說一種語言的人之後便沒有他們作不成的事了;因而上帝將他們的語言打亂,這樣他們就不能聽懂對方說什麼了,還把他們分散到了世界各地,這座城市也中止了修建。這座城市就被稱爲「巴別城」。 -- 《創世記》java

Babel用途

1. 轉譯 esnext、typescript 等到目標環境支持的 jsnode

高級語言到到低級語言叫編譯,高級語言到高級語言叫轉譯react

2. 代碼轉換git

taro就是用babel作語法轉譯的,後面還有一個例子。github

3. 代碼分析web

模塊分析、tree-shaking 、 代碼壓縮、linter等typescript

Babel怎麼轉譯代碼的?

1.jpg

對源碼字符串進行 parse,生成 AST,把對代碼的修改轉爲對 AST 的增刪改,轉換完 AST 以後再打印成目標代碼字符串。json

Babel插件開發的API

Babel 包

babel轉譯的不一樣階段使用了不一樣的api

1.parse 階段使用@babel/parser,做用是把源碼轉成 AST

require('@babel/parser').parse(source, {

sourceType: 'module',

plugins: ['jsx', 'flow', 'classProperties', 'decorator', 'decorators-legacy'],

});

複製代碼

plugins: 指定jsx、typescript、flow 等插件來解析對應的語法

sourceType: module、script、unambiguous 3個取值,module 是解析 es module 語法

Babel的AST

Babel AST樹查看

2.transform 階段使用 @babel/traverse,能夠遍歷 AST,並調用 visitor 函數修改 AST,修改 AST 涉及到 AST 的判斷、建立、修改等,這時候就須要 @babel/types 了,當須要批量建立 AST 的時候可使用 @babel/template 來簡化 AST 建立邏輯。

require('@babel/traverse').default

function traverse(ast,vistor) //vistor能夠是函數能夠是對象,函數就是enter節點時調用的函數vistor名字爲節點名稱,對象就是enterexit調用的函數。tips多個節點能夠'FunctionDeclaration|VariableDeclaration' 複製代碼
require('@babel/traverse').default(ast, {

Program(path,state) {

}

})

複製代碼
// path api

{

"parent": {...},

"node": {...},

"hub": {...},

"contexts": [],

"data": {},

"shouldSkip": false,

"shouldStop": false,

"removed": false,

"state": null,

"opts": null,

"skipKeys": null,

"parentPath": null,

"context": null,

"container": null,

"listKey": null,

"inList": false,

"parentKey": null,

"key": null,

"scope": null,

"type": null,

"typeAnnotation": null,

get(key)

set(key, node)

inList()

getSibling(key)

getNextSibling()

getPrevSibling()

getAllPrevSiblings()

getAllNextSiblings()

isXxx(opts)

assertXxx(opts)

find(callback)

findParent(callback)

insertBefore(nodes)

insertAfter(nodes)

replaceWith(replacement)

replaceWithMultiple(nodes)

replaceWithSourceString(replacement)

remove()

traverse(visitor, state)

skip()

stop()

}

複製代碼

2.jpg

@babel/types 用於建立、判斷 AST 節點,提供了 xxx、isXxx、assertXxx 的 api

@babel/template例子

3.generate 階段會把 AST 打印爲目標代碼字符串,同時生成 sourcemap,須要 @babel/generate包,中途遇到錯誤想打印代碼位置的時候,使用@babel/code-frame

const { code,map } = generator(ast, { sourceMaps: true });

//設置sourceMaps爲true纔有sourceMaps

複製代碼

@babel/code-frame例子

Babel的內置功能

1.首先咱們看看babel須要作的功能

從高版本語法和 api 轉換成低版本的語法並自動 polyfill 缺乏的 api。

babel轉換

a. TC39 是制定 javascript 語言標準的組織,每一年都會公佈加入到語言標準的特性,[es2015]262.ecma-international.org/6.0/)、es201… 等。

b. 提議

  • 階段 0 : 只是一個想法,可能用 babel plugin 實現

  • 階段 1: 值得繼續的建議

  • 階段 2: 創建 spec

  • 階段 3: 完成 spec 而且在瀏覽器實現

  • 階段 4: 會加入到下一年的 es20xx spec

c.react、flow、typescript

因爲這些要轉換的語法

babel7 內置的實現這些特性的插件分爲 syntax、transform、proposal 3類,syntax聲明要轉譯的語法,transform對AST的轉換,proposal未加入語言標準的特性的 AST 轉換,當插件太多不少共同的邏輯須要共享因此這部分在helper中實現。

Babel 包

正由於babel的插件太多,配置困難,用門面模式設計了presets讓配置簡單。

不一樣版本的語言標準支持: preset-es201五、preset-es2016 等,babel7 後用 preset-env 代替

未加入標準的語言特性的支持: 用於 stage0、stage一、stage2 的特性,babel7 後單獨引入 proposal plugin

用於 react、jsx、flow 的支持:分別封裝相應的插件爲 preset-react、preset-jsx、preset-flow,直接使用對應 preset 便可

d.每一個特性的實現用一個 babel 插件實現,當 babel 插件多了,天然會有一些共同的邏輯。這部分邏輯怎麼共享呢?

babel 設計了插件之間共享邏輯的機制,就是 helper。

helper 分爲兩種:

  • 一種是注入到 AST 的運行時用的全局函數(如class)

  • 一種是操做 AST 的工具函數,好比變量提高這種通用邏輯(如常量定義)

babel runtime 裏面放運行時加載的模塊,會被打包工具打包到產物中。分爲:regenerator、corejs、helper。

  • corejs 這就是新的 api 的 polyfill,分爲 2 和 3 兩個版本,3 才實現了實例方法的polyfill

  • regenerator 是 facebook 實現的 aync 的 runtime 庫,babel 使用 regenerator-runtime來支持實現 async await 的支持。

  • helper 是 babel 作語法轉換時用到的函數,好比 _typeof、_extends 等

2.babel的使用

"presets": [

[

"es2015",

{

"modules": false

}

],

"react",

"stage-2"

]

//babel6

複製代碼
"presets": [

[

"@babel/preset-env",

{

"targets": "> 0.25%, not dead",

"useBuiltIns": "usage",

"corejs": 3

}

]

],

"plugins": [[

"@babel/plugin-transform-runtime",

{

"corejs": 3

}

]]

//babel7

複製代碼

這裏咱們發現一個問題爲何有targets的目標瀏覽器呢?若是沒有這個有可能你目標瀏覽器的語言原本就支持你卻增長了babel增長了包的大小

babel/preset-env會致使使用到新特性的地方注入 helper 到 AST 中,@babel/plugin-transform-runtime的用處是把這些抽離出來模塊引入。

babel 的 runtime 代碼包括 2 部分:

一部分是 polyfill 用的,也就是 corejs 和 regenerator(實現 async await),這倆都是第三方的實現,babel 作了集成

另外一部分就是轉換代碼的時候的 helper,好比實現繼承、實現 class 的那些代碼,這部分默認是注入到代碼裏的

若是使用了 @babel/plugin-transform-runtime,會作兩個改動:

  1. 把 helper 部分的代碼從注入的方式改成從 @babel/runtime 包引入的方式

  2. polyfill 部分的代碼也再也不是全局引入,會改成模塊引入。

因此 transform runtime 的好處就有兩個:

  1. 抽離重複注入的 helper 代碼,減小產物體積

  2. polyfill 不污染全局

可是也有相對應的問題

babel 會按照以下順序處理插件和 preset:

  1. 先應用 plugin,再應用 preset

  2. plugin 從前到後,preset 從後到前

這樣就會形成,尚未到targets就執行了@babel/plugin-transform-runtime,不須要的babel包被增長了,這個babel8會解決這個問題。

最後說下上回文濤分享ts的時候關於用@babel/preset-typescript、ts-loader選擇的一個問題

babel 是單文件編譯的,每一個文件處理方式都同樣。ts-loader 在內部是調用了 TypeScript 的官方編譯器 – tsc, tsc 是多文件一塊兒編譯的,由於在編譯過程當中會解析模塊語法,去作類型推導和檢查,這是二者流程上最大的區別。因此 tsc 不可避免的在多文件的時候會卡,而 babel 就和規模沒有關係。 babel 不會解析 ts 類型,因此 namespace合併、const enum 這種須要解析類型內容的就不支持。總體來講 babel 編譯 ts 代碼會快不少。並且 babel 生成的代碼也能根據 targets 按需編譯以及引入 polyfill,而 tsc 不支持。須要類型檢查單獨跑 tsc --noEmit 就行。

// tsconfig.json

{

...

"compilerOptions":{

"noEmit":true // 不輸出文件,只作類型檢查

}

}

複製代碼

babel 編譯 typescript 是更好的選擇

Babel plugin的例子

先看下需求

!Wslq5ub7vhIy2RT11.png

1.小程序的頁面預覽頁面要在b端再寫一遍

2.ui後面改動也要倆邊同時改動(有可能漏改忘改)

taro的c端代碼要應用到web端

利用babel編譯到b端解決代碼公用問題,利用postcss解決樣式問題

第一個版本

無mobx只是組件傳值 修改2倍距離的問題

第二個版本

有mobx 優化css vh等

在瞭解下taro轉換的思路

4.png

5.jpg

taro組件庫的設計

taro的目標實現 React、Vue 等框架均可以使用的組件庫。

Web Components

taro跨端組件庫設計

參考連接

相關文章
相關標籤/搜索