babel是如何工作的
babel主要處理步驟分爲三個:解析、轉換、生成
解析
解析步驟接受代碼輸出AST
,該步驟分爲兩個階段:詞法分析、語法分析。
詞法分析主要是對源代碼進行分詞,產生一個叫做token
的數組,分割的單位是運算符、括號、數字、字符串、標點符號等可以處理的最小單元。
然後語法分析再將所有的tokens
組合成一個整體,分析它們的語法和關係,最後輸出AST
(源代碼的抽象語法樹)。
分成兩個階段後,更容易的對解析步驟作優化,因爲解析步驟大部分的時間都在詞法分析過程中,同時也能提高可移植性。
轉換
對AST
進行遍歷,遇到需要處理的節點就操作,包括添加、移除和更新等。這個也是插件介入的主要工作內容。(babel也提供了自定義解析步驟的插件功能)
生成
最終再將AST
轉換回字符串形式的代碼。
自定義插件開發
插件的格式
function customPlugin(babel) {
return {
visitor: {
// 定義多個訪問者
}
}
}
如上所示,一個插件就是一個普通的函數,函數接受一個babel對象(包含babel所有的api
),最後返回一個包含visitor
屬性的對象,visitor屬性中每個key都是一個ast節點的類型,值就是訪問這個節點的函數。
每個訪問者函數都會接受兩個參數:path
和state
。path對象表示兩個節點之間連接的對象,例如:
{
"parent": {
"type": "FunctionDeclaration",
"id": {...},
....
},
"node": {
"type": "Identifier",
"name": "square"
}
}
state對象包含一些額外的狀態信息,例如可以從state.opts
中取出爲插件配置的特定選項,甚至可以取出path
對象,具體內容可以自己打印看看。
下面開發一個小插件,爲代碼中的console.log
添加調用位置的信息。
module.exports = function (babel) {
const t = babel.types
return {
name: 'custom-babel-plugin-demo',
visitor: {
CallExpression(path) {
const obj = path.node.callee.object
const prop = path.node.callee.property
const arguments = path.node.arguments
if (t.isIdentifier(obj) && t.isIdentifier(prop) && obj.name === 'console' && prop.name === 'log') {
const location = `---trace: line ${path.node.loc.start.line}, column ${path.node.loc.start.column}---`;
arguments.push(t.stringLiteral(location))
}
}
}
}
}
首先你需要知道你要訪問節點的類型,如果不清楚,可以到這個網站查看。babel.types
則提供了類似lodash的工具庫功能(api)。
最後再測試下插件功能是否正常:
const { transform } = require('@babel/core')
const options = {
plugins: [ ['./src/index.js', {
option1: true,
options2: false
}] ]
}
const code = `
const str1 = 'hello'
console.log(str1)
const str2 = 'babel'
console.log(str2)
const str3 = 'plugin'
console.log(str3)
`
transform(code, options, function(err, result) {
console.log(result.code)
})
得到輸出:
const str1 = 'hello';
console.log(str1, "---trace: line 3, column 4---");
const str2 = 'babel';
console.log(str2, "---trace: line 5, column 4---");
const str3 = 'plugin';
console.log(str3, "---trace: line 7, column 4---");
如果你要編寫良好的測試用例,可以藉助babel-plugin-tester庫。