Js解析器Parser
是Webpack中最重要的工具之一,不一樣類型的文件在通過loader
處理後,將會被轉換輸出成爲一串js字符串。緊接着Parser
會將其轉換爲AST
語法樹,有了語法樹就能夠對代碼隨心所欲了,其中最重要的功能就是分析出這段代碼依賴了哪些模塊。Webpack將會基於解析出來的信息進行依賴組合及內容輸出,因此Parser
在其中扮演很是重要的角色,值得咱們認真研究。webpack
因爲解析器總體代碼仍是比較複雜,除了解析依賴還作了不少其餘功能處理,並且依賴解析還兼容CommonJS、ES6Module、AMD等,剛開始看源碼容易蒙圈。爲了簡單起見,這裏不會把這些內容都分析一遍,其實大部分操做其實都大同小異,因此只要理解它的思路就能作到一通百通。web
下面咱們先經過簡單的一行代碼,分析Parser
是如何解析出CommonJS依賴:express
// 普通的引入模塊操做
require('./increment')
複製代碼
首先來到Parser
的入口函數,能夠看到入口仍是很是簡潔,解析AST的操做交給acorn
庫去處理,Parser
的主要內容集中在對AST的解析上,同時這裏初始化的state
用於保存本次解析的結果:數據結構
/** Parser.js */
class Parser {
parse(code, initialState) {
// 將代碼解析成AST
var ast = acornParser.parse(code);
// 本次解析的上下文,最頻繁的做用就是往state.module裏添加依賴
this.state = initialState;
// 處理解析內容
this.walkStatements(ast.body);
return this.state
}
}
複製代碼
AST就是用來表示源碼的一個數據結構,咱們能夠利用在線解析器來可視化AST,例如上面這行語句將會轉換成如下內容:函數
能夠看到僅僅一行代碼,轉換後就獲得一大坨內容,解析AST的基本思路就是遍歷整個AST並提取想要的數據。工具
因爲AST語法樹是層層嵌套的結構,且結構類型很是多,因此遍歷AST相關的代碼就佔了一大半,好在這部分代碼仍是比較簡單,只要對照着AST樹來看仍是很容易理解。ui
提取數據是這裏的複雜點,Parser
自己只支持做用域和部分表達式相關的核心內容處理,其餘包括解析依賴等都是各類插件提供的能力,這樣作使得Parser
靈活性和擴展性很是好,固然代碼調試起來就複雜多了,因此這裏咱們將處理CommonJS
等相關的插件寫成同步調用方便分析。this
接下來咱們來看看具體執行代碼:lua
/** Parser.js */
// 遍歷全部語句
walkStatements(statements) {
for (let index = 0, len = statements.length; index < len; index++) {
const statement = statements[index];
this.walkStatement(statement);
}
}
// 處理單個語句,交給對應類型語句處理函數,這裏是表達式語句
walkStatement(statement) {
switch (statement.type) {
case "ExpressionStatement":
this.walkExpressionStatement(statement);
break;
// ...
}
}
// 處理具體的表達式內容
walkExpressionStatement(statement) {
this.walkExpression(statement.expression);
}
// 交給不一樣類型表達式處理函數,這裏是函數調用表達式
walkExpression(expression) {
switch (expression.type) {
case "CallExpression":
this.walkCallExpression(expression)
break;
// ...
}
}
// 處理函數調用表達式
walkCallExpression(expression) {
// "callee": { "type": "Identifier", "name": "require" },
// "arguments": [ { "type": "Literal", "value": "./increment", "raw": "'./increment'" } ]
const callee = this.evaluateExpression(expression.callee);
// 這裏判斷函數調用類型是`require(xxx)`,即`require`是標識符的狀況,其餘狀況如`a.require(xx)`是成員函數的狀況則過濾掉
if (callee.isIdentifier()) {
/** CommonJsRequireDependencyParserPlugin.js */
const param = parser.evaluateExpression(expression.arguments[0]);
// 參數是字符串的解析方法
if (param.isString()) {
// 添加模塊依賴,用於遞歸加載解析模塊
const dep = new CommonJsRequireDependency(param.string, param.range);
dep.loc = expr.loc;
dep.optional = !!parser.scope.inTry;
parser.state.current.addDependency(dep);
// 另一個依賴,用於將代碼中的`require`轉換成`__webpack_require__`
const dep = new RequireHeaderDependency(expression.callee.range);
dep.loc = expression.loc;
parser.state.current.addDependency(dep);
return
}
} else
// 參數是表達式處理方式:`require(1 > 0 ? './example' : './increment')`
if(param.isConditional()) {
// ...
}
// ...
}
// 計算表達式的值
evaluateExpression(expression) {
let result
switch(expression.type) {
case "Identifier":
result = evaluateIdentifierExpression(expression)
break;
case "Literal":
result = evaluateLiteralExpression(expression)
break;
}
if (result !== undefined) {
result.setExpression(expression);
return result;
}
}
// 處理標識符類型的表達式方法,this.hooks.evaluate.for("Identifier")
evaluateIdentifierExpression(expression) {
if(expression.name === 'require') {
/** CommonJsPlugin.js */
let evex = new BasicEvaluatedExpression()
.setIdentifier('require')
.setRange(expr.range);
return evex;
}
}
// 處理字面量類型的表達式方法,this.hooks.evaluate.for("Literal")
evaluateLiteralExpression(expression) {
switch (typeof expr.value) {
case "number":
return new BasicEvaluatedExpression()
.setNumber(expr.value)
.setRange(expr.range);
case "string":
return new BasicEvaluatedExpression()
.setString(expr.value)
.setRange(expr.range);
case "boolean":
return new BasicEvaluatedExpression()
.setBoolean(expr.value)
.setRange(expr.range);
}
}
複製代碼
通過上面一頓操做後,輸出了兩個依賴CommonJsRequireDependency
和RequireHeaderDependency
,至此Webpack成功將文件內容轉換爲內部對象,這些對象將在後續分依賴及輸出時發揮做用。spa
固然這裏只分析了簡易版Parser
的執行流程,中間可能有些你們不熟悉的名詞,這裏只要能看懂他們實現的結果就行,後續將會詳細分析裏面的內容。