webpack 4 源碼主流程分析(四):reslove 前的準備

原文首發於 blog.flqin.com。若有錯誤,請聯繫筆者。分析碼字不易,轉載請代表出處,謝謝!webpack

compiler.run

compiler.run是整個編譯過程啓動的入口,執行:web

this.hooks.beforeRun.callAsync(this, err => {
  //...
  this.hooks.run.callAsync(this, err => {
    //...
    // recordsInputPath是webpack配置中指定的讀取上一組records的文件路徑
    this.readRecords(err => {
      //...
      this.compile(onCompiled);
    });
  });
});
複製代碼

在方法中先觸發 compiler.hooks: beforeRun,執行以前註冊的 NodeEnvironmentPlugin(該插件此時判斷 inputFileSystem 是否被配置,如未配置則執行 purge 清理方法),而後在回調裏觸發 compiler.hooks: run,而後回調裏 this.readRecords 是用於讀取以前的 records 的方法,再在它的回調裏執行 this.compile(onCompiled)正則表達式

onCompiledcompile 過程後調用,主要用於輸出構建資源。數組

compiler.compile

compile 是真正進行編譯的過程,最終會把全部原始資源編譯爲目標資源。實例化了一個 compilation,並將 compilation 傳給 make 鉤子上的方法,註冊在這些鉤子上的方法會調用 compilation 上的 addEntry,執行構建。併發

獲取 compilation 所需 params

this.compile 先執行:函數

const params = this.newCompilationParams();
複製代碼

即:ui

newCompilationParams() {
    const params = {
        normalModuleFactory: this.createNormalModuleFactory(),
        contextModuleFactory: this.createContextModuleFactory(),
        compilationDependencies: new Set()
    };
    return params;
}
複製代碼

該方法先實例化了 NormalModuleFactory 類和 ContextModuleFactory 類,兩個類均擴展於 tapable。接下來具體說明這兩個類。this

實例化 NormalModuleFactory

NormalModuleFactory 類用於建立一個 normalModule 實例。spa

實例化 RuleSet

在實例化 NormalModuleFactory 執行 constructor 的過程當中,執行:插件

this.ruleSet = new RuleSet(options.defaultRules.concat(options.rules));
複製代碼
  • options.defaultRules 是在以前文件 WebpackOptionsDefaulter.js 中被初始化,而後與項目配置的 module.rules 合併;
  • 每一個規則能夠分爲三部分 - 條件condition(如 test, include, exclude),結果result(如應用的 loader,parse 選項) 和嵌套規則nested rule(如 rules);
  • 條件可接受 正則表達式,字符串,函數等。
  • new RuleSet 實例化過程當中,會對每一項 rule 進行進行處理,遞歸調用靜態方法 normalizeCondition 處理 condition 相關,最終每個 condition 都處理爲一個 newRule.resource 函數;遞歸調用 normalizeUse 處理 result 相關,最終每個 result 都處理爲一個 use 數組,數組的每一項包含 loaderoptions
  • 調用 ruleSet 的實例 exec 時,傳入目標路徑和相關信息後,在內部 _run 裏,進行遞歸過濾匹配出對應的 loader,最終獲得 result 數組,數組每一項包含 type,value(loader 和 options) 等;
註冊 normalModuleFactory.hooks:factory
this.hooks.factory.tap('NormalModuleFactory', () => (result, callback) => {
  let resolver = this.hooks.resolver.call(null);
  //...
  resolver(result, (err, data) => {
      //...
    });
  });
});
複製代碼

此時註冊了 normalModuleFactory.hooks:factory,當後面觸發該 hooks 時,該回調返回一個函數。函數內的運行須先觸發 normalModuleFactory.hooks:resolver,而後執行其回調結果。

註冊 normalModuleFactory.hooks:resolver
this.hooks.resolver.tap('NormalModuleFactory', () => (data, callback) => {
  //...
});
複製代碼

此時註冊了 normalModuleFactory.hooks:resolver,跟normalModuleFactory.hooks:factory 相同,當後面觸發該 hooks 時,該回調返回一個函數。

實例化 ContextModuleFactory

除了兼容老版本以外的代碼,沒有什麼特別須要注意的。

在這兩個類實例化完成後,分別觸發 compiler.hooks: normalModuleFactorycontextModuleFactory

實例化 compilation

this.compile 繼續執行,觸發 compiler.hooks: beforeCompilecompile, 而後在回調中執行:

const compilation = this.newCompilation(params);
複製代碼

該方法實例化了一個 Compilation,也是擴展於 tapable一個 compilation 對象表現了當前的模塊資源、編譯生成資源、變化的文件、以及被跟蹤依賴的狀態信息,表明了一次資源的構建

在添加了一些屬性後,觸發compiler.hooks:thisCompilation,compilation。回憶在 編譯前的準備 - 註冊plugins階段 - WebpackOptionsApply.js 的文件裏註冊了大量該 hooks 的事件,在此時拿到 compilation 對象後,開始執行這一系列事件。

  • compiler.hooks:thisCompilation 會在 compilation 對象的 hooks 裏註冊一些新的事件;
  • compiler.hooks:compilation 會在 compilationnormalModuleFactory 對象的 hooks 裏註冊一些新的事件,同時還會往 compilation.dependencyFactories(工廠類),compilation.dependencyTemplates(模板類) 增長依賴模塊。

爲何這裏須要 thisCompilation,compilation 兩個鉤子?緣由是跟子編譯器有關。在 CompilercreateChildCompiler 方法裏建立子編譯器,其中 thisCompilation 鉤子不會被複制,而 compilation 會被複制。 子編譯器擁有完整的 modulechunk 生成,經過子編譯器能夠獨立於父編譯器執行一個核心構建流程,額外生成一些須要的 modulechunk

開始構建

this.compile 繼續執行,觸發 compiler.hooks : make ,執行以前在 SingleEntryPlugin | MultiEntryPlugin 註冊的的 make 事件,執行:

compilation.addEntry(context, dep, name, callback);
複製代碼

compilation.addEntry

來到 Compilation.js 文件,addEntry 觸發了 compilation.hooks:addEntry,定義了入口對象 _preparedEntrypoints 以後,直接執行了 this._addModuleChain

在該方法裏,執行:

//...
const Dep = /** @type {DepConstructor} */ (dependency.constructor);
const moduleFactory = this.dependencyFactories.get(Dep);
複製代碼

dependency = SingleEntryPlugin.createDependency(entry, name)new SingleEntryDependency(entry),則 Dep 則爲 SingleEntryDependency 類,而在以前 compiler.hooks:compilation 的註冊事件中添加了依賴: compilation.dependencyFactories.set(SingleEntryDependency, normalModuleFactory),因此 moduleFactorynormalModuleFactory

開始建立 module

編譯隊列控制:semaphore.acquire

執行:

this.semaphore.acquire(() => {
  moduleFactory.create(
    {
      //...
    },
    (err, module) => {
      //...
    }
  );
});
複製代碼

this.semaphore 這個類是一個編譯隊列控制,原理很簡單,對執行進行了併發控制,默認併發數爲 100,超事後存入 semaphore.waiters,根據狀況再調用 semaphore.release 去執行存入的事件 semaphore.waiters

moduleFactory.create

this.semaphore.acquire 裏執行了 moduleFactory.create。(注:遞歸解析依賴的重複也今後處開始

本章小結

  1. 從編譯過程啓動的入口 compiler.run 開始,觸發了一系列的生命週期鉤子後,執行 compiler.compile
  2. 獲取 compilation 所需 params,實例化 NormalModuleFactory 類(插件會去註冊其鉤子) 及 ContextModuleFactory 類,在實例化 NormalModuleFactory 的過程當中,會實例化 RuleSet 及註冊鉤子 factoryresolver
  3. 實例化 Compilation,傳入 params 參數,觸發以前在註冊 plugin 階段所註冊的 NormalModuleFactory 下的 hooks
  4. 觸發 make 鉤子執行 compilation.addEntry,經過編譯隊列控制 semaphore.acquire 執行 moduleFactory.create 開始建立 module
相關文章
相關標籤/搜索