Webpack源碼分析 - 初識Js解析器

初識Js解析器

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語法樹是層層嵌套的結構,且結構類型很是多,因此遍歷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);
    }
}
複製代碼

結尾

通過上面一頓操做後,輸出了兩個依賴CommonJsRequireDependencyRequireHeaderDependency,至此Webpack成功將文件內容轉換爲內部對象,這些對象將在後續分依賴及輸出時發揮做用。spa

固然這裏只分析了簡易版Parser的執行流程,中間可能有些你們不熟悉的名詞,這裏只要能看懂他們實現的結果就行,後續將會詳細分析裏面的內容。

參考文章

AST在線編譯查看

相關文章
相關標籤/搜索