淺析webpack原始碼之Compiler.js模組(八)

織雪紗奈發表於2019-02-16

webpack.js小尾巴

const webpack = (options, callback) => {
    //... 
    if (callback) {
        if (typeof callback !== "function") {
            throw new Error("Invalid argument: callback");
        }
        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(callback);
    }
    return compiler;
}

最終返回了compiler

  
exports.version = version;

// ...屬性掛載,把引入的函式模組全部暴露出來
webpack.WebpackOptionsDefaulter = WebpackOptionsDefaulter;
webpack.WebpackOptionsApply = WebpackOptionsApply;
webpack.Compiler = Compiler;
webpack.MultiCompiler = MultiCompiler;
webpack.NodeEnvironmentPlugin = NodeEnvironmentPlugin;
// @ts-ignore Global @this directive is not supported
webpack.validate = validateSchema.bind(this, webpackOptionsSchema);
webpack.validateSchema = validateSchema;
webpack.WebpackOptionsValidationError = WebpackOptionsValidationError;

下面暴露了一些外掛

const exportPlugins = (obj, mappings) => {
    for (const name of Object.keys(mappings)) {
        Object.defineProperty(obj, name, {
            configurable: false,
            enumerable: true,
            get: mappings[name]
        });
    }
};

exportPlugins(exports, {
    AutomaticPrefetchPlugin: () => require("./AutomaticPrefetchPlugin"),
    BannerPlugin: () => require("./BannerPlugin"),
    CachePlugin: () => require("./CachePlugin")}
)

再通俗一點的解釋:

比如當你api.AutomaticPrefetchPlugin你能
呼叫AutomaticPrefetchPlugin檔案下的方法

這個和上面的不同在於上面的是掛在webpack函式物件上的

Compiler.js正題

要想理解Compiler.js
必須要理解tapable
再寫一遍 git地址

我們先簡單的理解它為一個通過tap 註冊外掛
call是run外掛的事件流,裡面還有一些非同步的操作

Compiler整理如下

class Compiler extends Tapable {
    constructor(context) {
        super();
        this.hooks = {
            //羅列一些出現頻率比較高的
            shouldEmit: new SyncBailHook(["compilation"]),
            done: new AsyncSeriesHook(["stats"]),
            beforeRun: new AsyncSeriesHook(["compiler"]),
            run: new AsyncSeriesHook(["compiler"]),
            emit: new AsyncSeriesHook(["compilation"]),
            afterEmit: new AsyncSeriesHook(["compilation"]),
            thisCompilation: new SyncHook(["compilation", "params"]),
            compilation: new SyncHook(["compilation", "params"]),
            beforeCompile: new AsyncSeriesHook(["params"]),
            compile: new SyncHook(["params"]),
            make: new AsyncParallelHook(["compilation"]),
            afterCompile: new AsyncSeriesHook(["compilation"]),
            watchRun: new AsyncSeriesHook(["compiler"]),
            //...
            
        }
        // 新增事件流
        this._pluginCompat.tap("Compiler", options => {
            switch (options.name) {
                case "additional-pass":
                case "before-run":
                case "run":
                case "emit":
                case "after-emit":
                case "before-compile":
                case "make":
                case "after-compile":
                case "watch-run":
                    options.async = true;
                    break;
            }
        });
        
    }
    watch(){
           //...
    }
    
    run(callback) {
        //... 
    }
    // 清除輸入檔案系統
    purgeInputFileSystem() {
        if (this.inputFileSystem && this.inputFileSystem.purge) {
            this.inputFileSystem.purge();
        }
    }
    emitAssets(compilation, callback) {
        //...
    }
    createChildCompiler(
        compilation,
        compilerName,
        compilerIndex,
        outputOptions,
        plugins
    ) {
        //...
    
        
    }
    //...
    compile(callback){
        //...
    }    
}

和tapable用法一個模子裡刻出來的,我們仔細的研究run函式

compiler.js是webpack 的核心

run(callback) {
   //如果正在running,返回報錯處理
    if (this.running) return callback(new ConcurrentCompilationError());
    
    //running呼叫finalCallback 
    const finalCallback = (err, stats) => {
        this.running = false;

        if (callback !== undefined) return callback(err, stats);
    };
    //記錄初始化running時間
    const startTime = Date.now();
    //設定running標誌,防止多次running
    this.running = true;
    
    //正在編譯
    const onCompiled = (err, compilation) => {
        //如果報錯,編譯結束
        if (err) return finalCallback(err);

        //如果沒有編譯完成
        if (this.hooks.shouldEmit.call(compilation) === false) {
             // Stats模組有1400行,
             // compiler.js是webpack 的核心,new Stats(compilation)是compiler.js的核心
            const stats = new Stats(compilation);
             //    stats物件掛載startTime,endTime 
            stats.startTime = startTime;
            stats.endTime = Date.now();
            // 非同步呼叫完成事件流,running結束
            this.hooks.done.callAsync(stats, err => {
                if (err) return finalCallback(err);
                return finalCallback(null, stats);
            });
            return;
        }
        // 呼叫emitAsset方法,emitAsset主要負責寫入檔案輸出檔案,不影響我們先看編譯
        this.emitAssets(compilation, err => {
            // 類似同上, 如果報錯,編譯結束
            if (err) return finalCallback(err);
            // 如果需要額外的編譯,上次的沒編譯完成嗎?
            if (compilation.hooks.needAdditionalPass.call()) {
                compilation.needAdditionalPass = true;
                //再來一遍new Stats
                const stats = new Stats(compilation);
                stats.startTime = startTime;
                stats.endTime = Date.now(); 
                //繼續非同步呼叫時間流
                this.hooks.done.callAsync(stats, err => {
                    if (err) return finalCallback(err);
                    //  這次多了一個時間流,呼叫額外編譯,告知編譯終於編完了
                    this.hooks.additionalPass.callAsync(err => {
                        if (err) return finalCallback(err);
                        //呼叫compile,把onCompiled的返回值傳入compile函式,onCompiled的返回值也就是new Stats的物件
                        this.compile(onCompiled);
                    });
                });
                return;
            }
            // 如果都不走上面的分支,即編譯完成,不需要額外的編譯
            // 呼叫emitRecords方法,主要用來記錄輸出的
            this.emitRecords(err => {
                if (err) return finalCallback(err);
                // 同樣new Stats
                const stats = new Stats(compilation);
                stats.startTime = startTime;
                stats.endTime = Date.now();
                // 非同步呼叫完成事件
                this.hooks.done.callAsync(stats, err => {
                    if (err) return finalCallback(err);
                    return finalCallback(null, stats);
                });
            });
            //最終總結,無論走那個分支都是 new Stats(compilation) 返回stats的回撥函式,按照目前的流程走的是最後一個分支,呼叫 this.emitRecords
        });
    };
    
    // 呼叫beforeRun鉤子
    this.hooks.beforeRun.callAsync(this, err => {
        if (err) return finalCallback(err);
        // 呼叫run鉤子
        this.hooks.run.callAsync(this, err => {
            if (err) return finalCallback(err);
            //讀檔案記錄
            this.readRecords(err => {
                if (err) return finalCallback(err);
                //把onCompiled函式傳入,呼叫compile
                this.compile(onCompiled);
            });
        });
    });
}

new Stats(compilation)是compiler.js的核心

compilation和Stats分別對應兩個模組

compilation.js 2500行,Stats.js 1400行

接下來我們看一下compile函式

newCompilationParams() {
        const params = {
            // 普通模組工廠
            normalModuleFactory: this.createNormalModuleFactory(),
            // 上下文模組工廠
            contextModuleFactory: this.createContextModuleFactory(),
            // 編譯依賴
            compilationDependencies: new Set()
        };
        return params;
    }


compile(callback) {
    // params值如下
    const params = this.newCompilationParams();
    // 非同步呼叫beforeCompile鉤子
    this.hooks.beforeCompile.callAsync(params, err => {
        if (err) return callback(err);
        // 呼叫compile鉤子
        this.hooks.compile.call(params);
        // 終於出現了compilation,之前一直沒有講了compilation是什麼,newCompilation我們看如下函式
        const compilation = this.newCompilation(params);

        this.hooks.make.callAsync(compilation, err => {
            if (err) return callback(err);

            compilation.finish();

            compilation.seal(err => {
                if (err) return callback(err);
                // 非同步呼叫afterCompile,返回回撥函式
                this.hooks.afterCompile.callAsync(compilation, err => {
                    if (err) return callback(err);

                    return callback(null, compilation);
                });
            });
        });
    });
}

newCompilation(params) {
   // compilation 是 this.createCompilation(),繼續往下
    const compilation = this.createCompilation();
    //給compilation物件掛載屬性
    compilation.fileTimestamps = this.fileTimestamps;
    compilation.contextTimestamps = this.contextTimestamps;
    compilation.name = this.name;
    compilation.records = this.records;
    compilation.compilationDependencies = params.compilationDependencies;
    //告知鉤子呼叫完畢
    this.hooks.thisCompilation.call(compilation, params);
    this.hooks.compilation.call(compilation, params);
    return compilation;
}

createCompilation() {
   // 原來compilation 是 newCompilation而來,Compilation一共2500行,堪稱整個compiler.js的核心
    return new Compilation(this);
}

params如下

{ normalModuleFactory:
   NormalModuleFactory {
     _pluginCompat:
      SyncBailHook {
      // key是tapable 方法名
        _args: [Array],
        taps: [Array],
        interceptors: [],
        call: [Function: lazyCompileHook],
        promise: [Function: lazyCompileHook],
        callAsync: [Function: lazyCompileHook],
        _x: undefined },
     hooks:
      { resolver: [SyncWaterfallHook],
        factory: [SyncWaterfallHook],
        beforeResolve: [AsyncSeriesWaterfallHook],
        afterResolve: [AsyncSeriesWaterfallHook],
        createModule: [SyncBailHook],
        module: [SyncWaterfallHook],
        createParser: [HookMap],
        parser: [HookMap],
        createGenerator: [HookMap],
        generator: [HookMap] },
     resolverFactory:
      ResolverFactory {
        _pluginCompat: [SyncBailHook],
        hooks: [Object],
        cache1: [WeakMap],
        cache2: Map {} },
     ruleSet: RuleSet { references: {}, rules: [Array] },
     cachePredicate: [Function: bound Boolean],
     //檔案路徑
     context: `/Users/orion/Desktop/react-beauty-highcharts`,
     parserCache: {},
     generatorCache: {} },
  contextModuleFactory:
   ContextModuleFactory {
     _pluginCompat:
      SyncBailHook {
        _args: [Array],
        taps: [Array],
        interceptors: [],
        call: [Function: lazyCompileHook],
        promise: [Function: lazyCompileHook],
        callAsync: [Function: lazyCompileHook],
        _x: undefined },
     hooks:
      { beforeResolve: [AsyncSeriesWaterfallHook],
        afterResolve: [AsyncSeriesWaterfallHook],
        contextModuleFiles: [SyncWaterfallHook],
        alternatives: [AsyncSeriesWaterfallHook] },
     resolverFactory:
      ResolverFactory {
        _pluginCompat: [SyncBailHook],
        hooks: [Object],
        cache1: [WeakMap],
        cache2: Map {} } },
  compilationDependencies: Set {} }

終極總結

  • Compiler建構函式 ->
  • run方法 ->
  • this.compile(onCompiled) ->
  • this.compile()執行有了compilation ->
  • onCompiled執行 const stats = new Stats(compilation)
  • 最後返回 finalCallback(null, stats);

this.compile(onCompiled) 是個高階函式,可以簡單的理解為引數是函式,並且返回一個函式

撒花? ??我要買瓶skii犒賞自己

相關文章