談到 babel
確定你們都不會感受陌生。html
babel-plugin-component
,咱們能夠只引入須要的組件,以達到減少項目體積的目的。babel-polyfill
,開發者能夠當即使用 ES 規範中的最新特性。transform-vue-jsx
、 react
,咱們在 vue 和 react 開發中能夠直接使用 JSX 編寫模板。組件能按需引入究竟是怎麼實現的? Babel
的工做原理是怎樣的呢?vue
帶着疑問,咱們嘗試對其原理深刻探索和理解。node
Babel
是一個 JavaScript
編譯器。react
和大多數其餘語言的編譯器類似,Babel
的編譯過程可分爲三個階段:typescript
Parse
:將代碼字符串解析成抽象語法樹(AST
)。簡單來講就是對 JS
代碼進行詞法分析與語法分析。Transform
:對抽象語法樹進行轉換操做。這裏操做主要是添加、更新及移除。Generate
: 根據變換後的抽象語法樹再生成代碼字符串。Parse
Babel
會把源代碼抽象出來,變成 AST
。npm
能夠看看 var answer = 6 * 7;
抽象以後的結果。element-ui
{
"type": "Program", // 根結點
"body": [
{
"type": "VariableDeclaration", // 變量聲明
"declarations": [
{
"type": "VariableDeclarator", // 變量聲明器
"id": {
"type": "Identifier",
"name": "answer"
},
"init": {
"type": "BinaryExpression", // 表達式
"operator": "*", // 操做符是 *
"left": {
"type": "Literal", // 字面量
"value": 6,
"raw": "6"
},
"right": {
"type": "Literal",
"value": 7,
"raw": "7"
}
}
}
],
"kind": "var"
}
],
"sourceType": "script"
}
複製代碼
Program
、 VariableDeclaration
、 VariableDeclarator
、 Identifier
、 BinaryExpression
、 Literal
均爲節點類型。每一個節點都是一個有意義的語法單元。這些節點經過攜帶的屬性描述本身的做用。數組
其中的全部節點名詞,均來源於 ECMA 規範 。bash
ATS 生成過程分爲兩個步驟:babel
token
。JS
中的語法單元主要包括如下這麼幾種:
const
、 let
、 var
等。if/else
、 return
、 function
等。+
、 -
、 *
、 /
等。好比下面的代碼生成的語法單元數組:
var answer = 6 * 7;
// Tokens
[
{
"type": "Keyword",
"value": "var"
},
{
"type": "Identifier",
"value": "answer"
},
{
"type": "Punctuator",
"value": "="
},
{
"type": "Numeric",
"value": "6"
},
{
"type": "Punctuator",
"value": "*"
},
{
"type": "Numeric",
"value": "7"
},
{
"type": "Punctuator",
"value": ";"
}
]
複製代碼
分詞的大體思路:遍歷字符串,經過各類方式(如:正則)匹配當前字符串片斷對應的語法單元類型,而後生成數組 token
。
先了解語法分析的兩個概念:
語法分析就是識別語句和表達式,這是一個遞歸的過程(理解爲深度優先遍歷)。Babel
會在解析過程當中設置一個暫存器,用來暫存當前讀取到的語法單元,若是解析失敗,就會返回以前的暫存點,再按照另外一種方式進行解析,若是解析成功,則將暫存點銷燬,不斷重複以上操做,直到最後生成對應的語法樹。
Transform
Plugins
插件應用於 Babel
的轉譯過程。若是不使用任何插件,那麼 Babel
會原樣輸出代碼。
Presets
Babel
官方已經針對經常使用環境編寫了一些 preset
:
Preset
的路徑:
若是 preset
在 npm
上,你能夠輸入 preset
的名稱,Babel
將檢查是否已經將其安裝到 node_modules
目錄下了
{
"presets": ["babel-preset-myPreset"]
}
複製代碼
你還能夠指定指向 preset
的絕對或相對路徑。
{
"presets": ["./myProject/myPreset"]
}
複製代碼
Preset
的排列順序:
Preset
是逆序排列的(從後往前)。
{
"presets": [
"a",
"b",
"c"
]
}
複製代碼
將按以下順序執行: c
、b
而後是 a
。
這主要是爲了確保向後兼容,因爲大多數用戶將 es2015
放在 stage-0
以前。
Generate
用 babel-generator
經過 AST
樹生成 ES5
代碼。
例如 ElementUI
中把 import { Button } from 'element-ui'
轉成 import Button from 'element-ui/lib/button'
能夠先對比下 AST
:
// import { Button } from 'element-ui'
{
"type": "Program",
"body": [
{
"type": "ImportDeclaration",
"specifiers": [
{
"type": "ImportSpecifier",
"local": {
"type": "Identifier",
"name": "Button"
},
"imported": {
"type": "Identifier",
"name": "Button"
}
}
],
"source": {
"type": "Literal",
"value": "element-ui",
"raw": "'element-ui'"
}
}
],
"sourceType": "module"
}
// import Button from 'element-ui/lib/button'
{
"type": "Program",
"body": [
{
"type": "ImportDeclaration",
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"local": {
"type": "Identifier",
"name": "Button"
}
}
],
"source": {
"type": "Literal",
"value": "element-ui/lib/button",
"raw": "'element-ui/lib/button'"
}
}
],
"sourceType": "module"
}
複製代碼
能夠發現, specifiers
的 type
和 source
的 value、raw
不一樣。
而後 ElementUI
官方文檔中,babel-plugin-component
的配置以下:
// 若是 plugins 名稱的前綴爲 'babel-plugin-',你能夠省略 'babel-plugin-' 部分
{
"presets": [["es2015", { "modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
複製代碼
直接幹:
import * as babel from '@babel/core'
const str = `import { Button } from 'element-ui'`
const { result } = babel.transform(str, {
plugins: [
function({types: t}) {
return {
visitor: {
ImportDeclaration(path, { opts }) {
const { node: { specifiers, source } } = path
// 比較 source 的 value 值 與配置文件中的庫名稱
if (source.value === opts.libraryName) {
const arr = specifiers.map(specifier => (
t.importDeclaration(
[t.ImportDefaultSpecifier(specifier.local)],
// 拼接詳細路徑
t.stringLiteral(`${source.value}/lib/${specifier.local.name}`)
)
))
path.replaceWithMultiple(arr)
}
}
}
}
}
]
})
console.log(result) // import Button from "element-ui/lib/Button";
複製代碼
完美!咱們的第一個 Babel
插件完成了。
你們有沒有對 Babel
有本身的理解了呢?
若是本文對你有幫助,就點個贊支持下吧!感謝閱讀。