前言
webpack初始化完成之後,則會通過傳入的options.watch
來判斷是否要開啟watch,如果開啟watch
則會執行watch
的流程,如果是run
,則會執行run
的流程,本系列只關注主線,所以我們直接從run
開始,watch
感興趣的同學可以自行研究研究
compiler.run()
直接看核心程式碼
const run = () => {
this.hooks.beforeRun.callAsync(this, err => {
if (err) return finalCallback(err);
this.hooks.run.callAsync(this, err => {
if (err) return finalCallback(err);
this.readRecords(err => {
if (err) return finalCallback(err);
this.compile(onCompiled);
});
});
});
};
簡單說就是做了這麼幾件事。
- 觸發
beforeRun
的回撥 - 觸發
run
的回撥 - 然後調
this.readRecords
- 在
readRecords
的回撥裡呼叫this.compile(onCompiled)
開啟編譯
我們一步一步看beforeRun
會觸發之前在NodeEnvironmentPlugin
中註冊的 beforeRun 鉤子,這個plugin會判斷inputFileSystem
是否被配置,如果沒有配置則執行purge
清理方法。
readRecords
會讀取一些統計資訊,由於沒有配置recordsInputPath
,這裡會把this.records
初始為{}
。
建立compilation例項
接下來就執行到compiler.compiler()
方法。compiler.compiler
方法貫穿了整個編譯過程。首先compiler
例項化了一個compilation
。
compile(callback) {
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
if (err) return callback(err);
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
// do something
}
}
獲取引數
newCompilationParams() {
const params = {
normalModuleFactory: this.createNormalModuleFactory(),
contextModuleFactory: this.createContextModuleFactory()
};
return params;
}
引數有兩個,一個是NormalModuleFactory
的例項,一個是ContextModuleFactory
的例項。ContextModuleFactory
這個引數我在compilation裡面沒搜到,暫且略過,後續打個斷點看會不會走進來。這裡主要看下NormalModuleFactory
。
NormalModuleFactory
先看下例項化NormalModuleFactory
的引數
const normalModuleFactory = new NormalModuleFactory({
context: this.options.context,
fs: this.inputFileSystem,
resolverFactory: this.resolverFactory,
options: this.options.module,
associatedObjectForCache: this.root,
layers: this.options.experiments.layers
});
注意這裡的resolverFactory
, 以後會用到。
接下來看下new NormalModuleFactory
的時候發生了啥
constructor({
context,
fs,
resolverFactory,
options,
associatedObjectForCache,
layers = false
}) {
super();
this.hooks = 定義了很多hooks
this.resolverFactory = resolverFactory;
this.ruleSet = ruleSetCompiler.compile([
{
rules: options.defaultRules
},
{
rules: options.rules
}
]);
this.context = context || "";
this.fs = fs;
this._globalParserOptions = options.parser;
this._globalGeneratorOptions = options.generator;
/** @type {Map<string, WeakMap<Object, TODO>>} */
this.parserCache = new Map();
/** @type {Map<string, WeakMap<Object, Generator>>} */
this.generatorCache = new Map();
/** @type {Set<Module>} */
this._restoredUnsafeCacheEntries = new Set();
const cacheParseResource = parseResource.bindCache(
associatedObjectForCache
);
this.hooks.factorize.tapAsync(
// do something
);
this.hooks.resolve.tapAsync(
// dosomething
);
}
可能覺得太長了不看,我直接給大家翻譯一下幹了啥
- 定義了很多內部的hook,比方說最後註冊的兩個 reslover,factorize
- 定義了很多構建module需要的變數,這裡先不細說。
- 同時註冊了兩個
NormalModuleFactory
的內部 hook。會在合適的時機在被compilation物件呼叫
new NormalModuleFactory()
之後,觸發了compiler上的normalModuleFactory
鉤子
this.hooks.normalModuleFactory.call(normalModuleFactory);
繼續觸發鉤子回撥
然後觸發beforeCompile
和compile
鉤子。
開始例項化
newCompilation(params) {
const compilation = this.createCompilation(params); //這裡簡單理解為new 了一下,
compilation.name = this.name;
compilation.records = this.records;
this.hooks.thisCompilation.call(compilation, params);
this.hooks.compilation.call(compilation, params);
return compilation;
}
分類一下,這個函式做了兩件事。
- new Compilation,再賦一點值
註冊兩個鉤子
new Compilation 內部細節
Compilation物件表示了當前模組資源、編譯生成資源、變化的檔案、以及被跟蹤依賴的狀態資訊,代表了一次資源的構建。constructor程式碼太多就不貼在這裡了,大家可以自行去看看。
簡單總結一下,就是
- 在compilation內部註冊了很多內部的鉤子。
- 初始化了一些自身屬性
- 例項化
MainTemplate
,ChunkTemplate
,HotUpdateChunkTemplate
,RuntimeTemplate
,ModuleTemplate
。用於提供編譯模板
例項化後的鉤子呼叫
this.hooks.thisCompilation.call(compilation, params);
this.hooks.compilation.call(compilation, params);
compilation
鉤子的呼叫,會呼叫到之前在entryplugin註冊的方法。
會往dependencyFactories裡增加依賴模組。
compilation.dependencyFactories.set(
EntryDependency,
normalModuleFactory
);
也許你好奇,這裡為什麼有兩個鉤子?原因是跟子編譯器有關。在 Compiler 的 createChildCompiler 方法裡建立子編譯器,其中 thisCompilation 鉤子不會被複制,而 compilation 會被複制。
子編譯器擁有完整的 module 和 chunk 生成,通過子編譯器可以獨立於父編譯器執行一個核心構建流程,額外生成一些需要的 module 和 chunk。
結束
到目前為止,描述構建流程的物件compiler
和描述編譯過程的物件compilation
物件已經建立完成。下一篇文章我們進入構建流程。