webpack(2) -編譯

webpack 準(zhǔn)備階段

這個(gè)階段的主要工作,是創(chuàng)建 CompilerCompilation 實(shí)例。

  • 當(dāng)我們開始運(yùn)行 webpack 的時(shí)候,就會(huì)創(chuàng)建 Compiler 實(shí)例
  • 然后調(diào)用 WebpackOptionsApply并且加載內(nèi)部插件
    • 1、WebpackOptionsApply 中 process 主要處理 options.target 參數(shù)
    • 2、處理options.output, options.devtool 等參數(shù)
    • 3、引用的大量的模塊 把this 指向了compiler
    • 4、處理options.optimization 的moduleIds和chunkIds屬性
    • 5、加載EntryOptionPlugin插件并觸發(fā)entry-option的事件流
    • 6、觸發(fā)after-plugins事件流
    • 7、設(shè)置compiler.resolvers的值
    • 8、觸發(fā)after-resolvers事件流

WebpackOptionsApply 這個(gè)模塊主要是根據(jù)options選項(xiàng)的配置,設(shè)置compile的相應(yīng)的插件,屬性,里面寫了大量的 apply(compiler); 使得模塊的this指向compiler

class WebpackOptionsApply extends OptionsApply{
  constructor() {
     super();
  }
  process(options, compiler){
     compiler.outputPath = options.output.path;
     compiler.recordsInputPath = options.recordsInputPath || null;
     compiler.recordsOutputPath = options.recordsOutputPath || null;
     compiler.name = options.name;
    //...
        new JavascriptModulesPlugin().apply(compiler);
        new JsonModulesPlugin().apply(compiler);
        new AssetModulesPlugin().apply(compiler);
    //...
        new EntryOptionPlugin().apply(compiler);
     // 觸發(fā)事件點(diǎn)entry-options并傳入?yún)?shù) context和entry 
        compiler.hooks.entryOption.call(options.context, options.entry);
 }  
}
  • 和構(gòu)建流程相關(guān)的插件 主要是 EntryOptionPlugin

  • EntryOptionPlugin 的代碼只有寥寥數(shù)行但是非常重要,它會(huì)解析傳給 webpack 的配置中的 entry 屬性,這些插件可能是 EntryPlugin, MultiEntryPlugin 或者 DynamicEntryPlugin。但不管是哪個(gè)插件,內(nèi)部都會(huì)監(jiān)聽 Compiler 實(shí)例對(duì)象的 make 任務(wù)點(diǎn),

class EntryOptionPlugin {
    /**
     * @param {Compiler} compiler the compiler instance one is tapping into
     * @returns {void}
     */
    apply(compiler) {
        compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => {
            EntryOptionPlugin.applyEntryOption(compiler, context, entry);
            return true;
        });
    }

    /**
     * @param {Compiler} compiler the compiler
     * @param {string} context context directory
     * @param {Entry} entry request
     * @returns {void}
     */
    static applyEntryOption(compiler, context, entry) {
        if (typeof entry === "function") {
            const DynamicEntryPlugin = require("./DynamicEntryPlugin");
            new DynamicEntryPlugin(context, entry).apply(compiler);
        } else {
            const EntryPlugin = require("./EntryPlugin");
            for (const name of Object.keys(entry)) {
                const desc = entry[name];
                const options = EntryOptionPlugin.entryDescriptionToOptions(
                    compiler,
                    name,
                    desc
                );
                for (const entry of desc.import) {
                    new EntryPlugin(context, entry, options).apply(compiler);
                }
            }
        }
    }

    /**
     * @param {Compiler} compiler the compiler
     * @param {string} name entry name
     * @param {EntryDescription} desc entry description
     * @returns {EntryOptions} options for the entry
     */
    static entryDescriptionToOptions(compiler, name, desc) {
        /** @type {EntryOptions} */
        const options = {
            name,
            filename: desc.filename,
            runtime: desc.runtime,
            dependOn: desc.dependOn,
            chunkLoading: desc.chunkLoading,
            wasmLoading: desc.wasmLoading,
            library: desc.library
        };
        if (desc.chunkLoading) {
            const EnableChunkLoadingPlugin = require("./javascript/EnableChunkLoadingPlugin");
            EnableChunkLoadingPlugin.checkEnabled(compiler, desc.chunkLoading);
        }
        if (desc.wasmLoading) {
            const EnableWasmLoadingPlugin = require("./wasm/EnableWasmLoadingPlugin");
            EnableWasmLoadingPlugin.checkEnabled(compiler, desc.wasmLoading);
        }
        if (desc.library) {
            const EnableLibraryPlugin = require("./library/EnableLibraryPlugin");
            EnableLibraryPlugin.checkEnabled(compiler, desc.library.type);
        }
        return options;
    }
}

module.exports = EntryOptionPlugin;
//  EntryPlugin 

    compiler.hooks.compilation.tap(
            "EntryPlugin",
            (compilation, { normalModuleFactory }) => {
                compilation.dependencyFactories.set(
                    EntryDependency,
                    normalModuleFactory
                );
            }
        );

compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
            const { entry, options, context } = this;

            const dep = EntryPlugin.createDependency(entry, options);
            compilation.addEntry(context, dep, options, err => {
                callback(err);
            });
        });
    }
  • 當(dāng) Compiler 實(shí)例加載完內(nèi)部插件之后,下一步就會(huì)直接調(diào)用 compiler.run 方法來(lái)啟動(dòng)構(gòu)建,任務(wù)點(diǎn) run 也是在此時(shí)觸發(fā),值得注意的是此時(shí)基本只有 options 屬性是解析完成

另外要注意的一點(diǎn)是,任務(wù)點(diǎn) run 只有在 webpack 以正常模式運(yùn)行的情況下會(huì)觸發(fā),如果我們以監(jiān)聽(watch)的模式運(yùn)行 webpack,那么任務(wù)點(diǎn) run 是不會(huì)觸發(fā)的,但是會(huì)觸發(fā)任務(wù)點(diǎn) watch-run

  • Compiler 對(duì)象會(huì)開始實(shí)例化兩個(gè)核心的工廠對(duì)象,分別是 NormalModuleFactory 和 ContextModuleFactory。工廠對(duì)象顧名思義就是用來(lái)創(chuàng)建實(shí)例的,它們后續(xù)用來(lái)創(chuàng)建 NormalModule 以及 ContextModule 實(shí)例,這兩個(gè)工廠對(duì)象會(huì)在任務(wù)點(diǎn) compile 觸發(fā)時(shí)傳遞過(guò)去,所以任務(wù)點(diǎn) compile 是間接監(jiān)聽這兩個(gè)對(duì)象的任務(wù)點(diǎn)的一個(gè)入口
// Compiler 中 createNormalModuleFactory
createNormalModuleFactory() {
    const normalModuleFactory = new NormalModuleFactory({
           context: this.options.context,
            fs: this.inputFileSystem,
            resolverFactory: this.resolverFactory,
            options: this.options.module || {},
            associatedObjectForCache: this.root
    });
    this.hooks.normalModuleFactory.call(normalModuleFactory);
    return normalModuleFactory;
    }
  • 下一步,Compiler 實(shí)例將會(huì)開始創(chuàng)建 Compilation 對(duì)象,這個(gè)對(duì)象是后續(xù)構(gòu)建流程中最核心最重要的對(duì)象,它包含了一次構(gòu)建過(guò)程中所有的數(shù)據(jù)。也就是說(shuō)一次構(gòu)建過(guò)程對(duì)應(yīng)一個(gè) Compilation 實(shí)例。

  • 當(dāng) Compilation 實(shí)例創(chuàng)建完成之后,webpack 的準(zhǔn)備階段已經(jīng)完成,下一步將開始 modules 和 chunks 的生成階段。

modules 和 chunks 的生成階段

先解析項(xiàng)目依賴的所有 modules,再根據(jù) modules 生成 chunks
module 解析,包含了三個(gè)主要步驟:創(chuàng)建實(shí)例、loaders應(yīng)用以及依賴收集
chunks 生成,主要步驟是找到 chunk 所需要包含的 modules。

  • 下面將以 NormalModule 為例講解一下 module 的解析過(guò)程,ContextModule 等其他模塊實(shí)例的處理是類似的。

  • 1、步驟是創(chuàng)建 NormalModule 實(shí)例。這里需要用到上一個(gè)階段講到的 NormalModuleFactory 實(shí)例, NormalModuleFactory 的 create 方法是創(chuàng)建 NormalModule 實(shí)例的入口,內(nèi)部的主要過(guò)程是解析 module 需要用到的一些屬性,比如需要用到的 loaders, 資源路徑 resource 等等,最終將解析完畢的參數(shù)傳給 NormalModule 構(gòu)建函數(shù)直接實(shí)例化

  • 2、在解析參數(shù)的過(guò)程中,有兩個(gè)比較實(shí)用的任務(wù)點(diǎn) before-resolve 和 after-resolve,分別對(duì)應(yīng)了解析參數(shù)前和解析參數(shù)后的時(shí)間點(diǎn)。舉個(gè)例子,在任務(wù)點(diǎn) before-resolve 可以做到忽略某個(gè) module 的解析,webpack 內(nèi)部插件 IgnorePlugin 就是這么做的。

  • 3、在創(chuàng)建完 NormalModule 實(shí)例之后會(huì)調(diào)用 build 方法繼續(xù)進(jìn)行內(nèi)部的構(gòu)建。我們熟悉的 loaders 將會(huì)在這里開始應(yīng)用,NormalModule 實(shí)例中的 loaders 屬性已經(jīng)記錄了該模塊需要應(yīng)用的 loaders

  • 4、

文件生成階段

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容