webpack 準(zhǔn)備階段
這個(gè)階段的主要工作,是創(chuàng)建 Compiler 和 Compilation 實(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、