原文首發於 blog.flqin.com。若有錯誤,請聯繫筆者。分析碼字不易,轉載請代表出處,謝謝!webpack
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)
。正則表達式
onCompiled
在 compile
過程後調用,主要用於輸出構建資源。數組
compile
是真正進行編譯的過程,最終會把全部原始資源編譯爲目標資源。實例化了一個 compilation
,並將 compilation
傳給 make
鉤子上的方法,註冊在這些鉤子上的方法會調用 compilation
上的 addEntry
,執行構建。併發
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
類用於建立一個 normalModule
實例。spa
在實例化 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
數組,數組的每一項包含 loader
和 options
;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
時,該回調返回一個函數。
除了兼容老版本以外的代碼,沒有什麼特別須要注意的。
在這兩個類實例化完成後,分別觸發 compiler.hooks
: normalModuleFactory
,contextModuleFactory
。
this.compile
繼續執行,觸發 compiler.hooks
: beforeCompile
,compile
, 而後在回調中執行:
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
會在 compilation
、normalModuleFactory
對象的 hooks
裏註冊一些新的事件,同時還會往 compilation.dependencyFactories
(工廠類),compilation.dependencyTemplates
(模板類) 增長依賴模塊。爲何這裏須要
thisCompilation,compilation
兩個鉤子?緣由是跟子編譯器有關。在Compiler
的createChildCompiler
方法裏建立子編譯器,其中thisCompilation
鉤子不會被複制,而compilation
會被複制。 子編譯器擁有完整的module
和chunk
生成,經過子編譯器能夠獨立於父編譯器執行一個核心構建流程,額外生成一些須要的module
和chunk
。
this.compile
繼續執行,觸發 compiler.hooks
: make
,執行以前在 SingleEntryPlugin | MultiEntryPlugin
註冊的的 make
事件,執行:
compilation.addEntry(context, dep, name, callback);
複製代碼
來到 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)
,因此 moduleFactory
爲 normalModuleFactory
。
執行:
this.semaphore.acquire(() => {
moduleFactory.create(
{
//...
},
(err, module) => {
//...
}
);
});
複製代碼
this.semaphore
這個類是一個編譯隊列控制,原理很簡單,對執行進行了併發控制,默認併發數爲 100
,超事後存入 semaphore.waiters
,根據狀況再調用 semaphore.release
去執行存入的事件 semaphore.waiters
。
this.semaphore.acquire
裏執行了 moduleFactory.create
。(注:遞歸解析依賴的重複也今後處開始)
compiler.run
開始,觸發了一系列的生命週期鉤子後,執行 compiler.compile
。compilation
所需 params
,實例化 NormalModuleFactory
類(插件會去註冊其鉤子) 及 ContextModuleFactory
類,在實例化 NormalModuleFactory
的過程當中,會實例化 RuleSet
及註冊鉤子 factory
和 resolver
。Compilation
,傳入 params
參數,觸發以前在註冊 plugin
階段所註冊的 NormalModuleFactory
下的 hooks
。make
鉤子執行 compilation.addEntry
,經過編譯隊列控制 semaphore.acquire
執行 moduleFactory.create
開始建立 module
。