webpack 源碼分析(四)——complier模塊

webpack 源碼分析(四)——complier模塊

上一篇咱們看到,webpack-cli 經過 `yargs 對命令行傳入的參數和配置文件裏的配置項作了轉換包裝,而後傳遞給 webpackcompiler 模塊去編譯。javascript

這一篇咱們來看看 compiler 作了些什麼事。java

入口

首先,咱們來看看 webpack 是在什麼地方引入 Compiler 這個模塊的,在 webpack/lib/webpack.js 中咱們發現,這個文件第一行就引入了它,並在下面的主要邏輯中使用了它:node

const Compiler = require("./Compiler");

……

/*
* options:這就是由 webpack-cli 轉換包裝後的配置項
* callback:第二篇分析時,發現 `webpack-cli` 只是傳入了 options對象來獲取 `compiler` 對象,並無傳入回調 
*/

const webpack = (options, callback) => {

    // 驗證配置項的格式
    // webpackOptionsSchema 是一個json格式的描述文件,它描述了webpack可接受的全部配置項及其格式
    // options 是用戶定義的webpack.config.*.js中導出的全部配置項
    // validateSchema 使用 ajv 包,根據 webpackOptionsSchema 中定義的數據類型和描述來校驗 options 中的各項配置項,最後返回一個錯誤對象,其中包含全部錯誤的配置項及說明
    const webpackOptionsValidationErrors = validateSchema(
        webpackOptionsSchema,
        options
    );
    // 若是存在配置項錯誤,則拋出全部錯誤
    if (webpackOptionsValidationErrors.length) {
        throw new WebpackOptionsValidationError(webpackOptionsValidationErrors);
    }
    //判斷配置項是否數組,若是是數組則使用MultiCompiler進行編譯,不然使用Compiler模塊進行編譯,通常狀況下,咱們的 options 都是對象不是數組
    let compiler;
    if (Array.isArray(options)) {
        compiler = new MultiCompiler(options.map(options => webpack(options)));
    } else if (typeof options === "object") {
        //使用默認配置項處理輸入配置項
        // WebpackOptionsDefaulter 繼承自 OptionsDefaulter 類
        // 在這個類裏有兩個對象:
        //    * this.defaults : 用來存放配置項的值
        //  * this.config : 用來存放配置項的值的類型
        // process 方法的處理邏輯:
        //     * 若是defaults對象中存在,但在config對象中不存在,而且在options中也不存在,就從 defaults對象中複製一份到options中
        //  * 若是在config對象中存在而且值爲 「call」,則說明它的值是一個方法調用,就直接調用,並將options做爲參數傳入
        //  * 若是在config對象中存在且值爲 「make」,而且在 options中沒有,則說明它是一個方法,就直接調用它,並將options做爲參數傳入,拿到返回值,賦值給options中對應的項
        //  * 若是在config對象中存在切值爲 「append」,則取出options中對應的值,若是它不是數組,就把它重置爲數組,而且把defaults對象中的值複製到數組中,最後將這個數組做爲值賦值給options相應的項
        options = new WebpackOptionsDefaulter().process(options);
        // new 一個compiler實例,參數爲當前執行node命令的目錄路徑
    // Compiler類繼承自咱們上一篇講過的Tapable類,在構造函數中,初始化了各類類型的鉤子實例
    // compiler 類的內部邏輯,後面詳解++++++++++++++++++++++
        compiler = new Compiler(options.context);
    // WebpackOptionsDefaulter類處理後返回的options 複製給 compiler
        compiler.options = options;
    // 使用NodeEnviromentPlugin 類給compiler添加文件輸入輸出的能力
    // NodeEnviromentPlugin的內部邏輯,後面詳解++++++++++++++++++++++++++
        new NodeEnvironmentPlugin().apply(compiler);
    // 若是配置項中有插件配置而且插件配置爲數組
    // 遍歷插件數組,若是插件是一個函數,則使用complier來調用它,,而且將compier做爲參數出入
    // 不然,使用 WebpackPluginInstance 的 apply 方法來返回一個 void 值(至關於undefined)
        if (options.plugins && Array.isArray(options.plugins)) {
            for (const plugin of options.plugins) {
                if (typeof plugin === "function") {
                    plugin.call(compiler, compiler);
                } else {
                    plugin.apply(compiler);
                }
            }
        }
    // 觸發 environment 同步鉤子,這裏的 call() 是咱們講過的Tapable鉤子事件的觸發方法
    // environment 準備好以後,執行插件
        compiler.hooks.environment.call();
    // 觸發 afterEnvironment 同步鉤子
    // environment 安裝完成以後,執行插件
        compiler.hooks.afterEnvironment.call();
    // 使用 WebpackOptionsApply 類處理選項,返回處理過的選項對象
    // WebpackOptionsApply 的處理邏輯,後面詳解++++++++++++++
        compiler.options = new WebpackOptionsApply().process(options, compiler);
    } else {
    //若是配置既不是數組類型也不是對象類型,拋出錯誤
        throw new Error("Invalid argument: options");
    }
  // 若是傳入了回到函數
    if (callback) {
    // 若是回調不是函數,拋出參數類型錯誤
        if (typeof callback !== "function") {
            throw new Error("Invalid argument: callback");
        }
    // 若是是監聽模式,或者(配置是數組且數組中的項中有監聽選項爲true),則初始化監聽配置,最後返回compiler實例的監聽方法
    // 實例的監聽方法會返回一個 Watching類的實例,它本質上是一個觀察者
        if (
            options.watch === true ||
            (Array.isArray(options) && options.some(o => o.watch))
        ) {
            const watchOptions = Array.isArray(options)
                ? options.map(o => o.watchOptions || {})
                : options.watchOptions || {};
            return compiler.watch(watchOptions, callback);
        }
    // 使用compiler的run方法運行回調
        compiler.run(callback);
    }
  //返回compiler實例
    return compiler;
};

經過以上源碼分析,咱們得知,在 webpack() 中,實際上總共作了三件事:webpack

  1. 對參數進行校驗和規範化處理;
  2. new 一個編譯器實例而且初始化各類tapable鉤子,而且在環境準備好和安裝完成後執行響應的鉤子
  3. 初始化監聽。

在上面的分析中,咱們看到最核心的其實就是compiler實例,接下來咱們就看下它的類的內部邏輯。web

compiler 分析

主體結構

首先,咱們來看主要結構:json

/*…… *// 各類引入

// Compiler 類繼承自Tapable
class Compiler extends Tapable {
    constructor(context) {……}//構造函數執行各類初始化操做
    watch(watchOptions, handler) {……}//監聽初始化
    run(callback) {……}// 運行編譯
    runAsChild(callback) {...} // 做爲子編譯進程運行
    purgeInputFileSystem() {...} // 淨化輸入
    emitAssets(compilation, callback) {...} // 發佈資源
    emitRecords(callback) {...} // 發佈記錄
    readRecords(callback) {...} // 讀取記錄
    createChildCompiler(
        compilation,
        compilerName,
        compilerIndex,
        outputOptions,
        plugins
    ) {...} // 建立子編譯器
    isChild() {return !!this.parentCompilation;} // 是否子彙編
    createCompilation() {return new Compilation(this);} // 建立彙編實例
    newCompilation(params) {return compilation;} // 根據參數建立新的彙編實例
    createNormalModuleFactory() {return normalModuleFactory;} // 建立普通模塊的工廠
    createContextModuleFactory() {return contextModuleFactory;} // 建立上下文模塊的工廠
    newCompilationParams() {return params;} // 獲取一個新的彙編參數對象
    compile(callback) {} // 編譯
}

module.exports = Compiler;

class SizeOnlySource extends Source {} // 定義了一個類,做用是+++++++++++

下一講,咱們將逐個攻破數組

相關文章
相關標籤/搜索