webpack 准备阶段
这个阶段的主要工作,是创建 Compiler 和 Compilation 实例。
- 当我们开始运行 webpack 的时候,就会创建 Compiler 实例
- 然后调用 WebpackOptionsApply并且加载内部插件
- 1、WebpackOptionsApply 中 process 主要处理 options.target 参数
- 2、处理options.output, options.devtool 等参数
- 3、引用的大量的模块 把this 指向了compiler
- 4、处理options.optimization 的moduleIds和chunkIds属性
- 5、加载EntryOptionPlugin插件并触发entry-option的事件流
- 6、触发after-plugins事件流
- 7、设置compiler.resolvers的值
- 8、触发after-resolvers事件流
WebpackOptionsApply 这个模块主要是根据options选项的配置,设置compile的相应的插件,属性,里面写了大量的 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);
// 触发事件点entry-options并传入参数 context和entry
compiler.hooks.entryOption.call(options.context, options.entry);
}
}
和构建流程相关的插件 主要是 EntryOptionPlugin
EntryOptionPlugin 的代码只有寥寥数行但是非常重要,它会解析传给 webpack 的配置中的 entry 属性,这些插件可能是 EntryPlugin, MultiEntryPlugin 或者 DynamicEntryPlugin。但不管是哪个插件,内部都会监听 Compiler 实例对象的 make 任务点,
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);
});
});
}
- 当 Compiler 实例加载完内部插件之后,下一步就会直接调用 compiler.run 方法来启动构建,任务点 run 也是在此时触发,值得注意的是此时基本只有 options 属性是解析完成
另外要注意的一点是,任务点 run 只有在 webpack 以正常模式运行的情况下会触发,如果我们以监听(watch)的模式运行 webpack,那么任务点 run 是不会触发的,但是会触发任务点 watch-run
- Compiler 对象会开始实例化两个核心的工厂对象,分别是 NormalModuleFactory 和 ContextModuleFactory。工厂对象顾名思义就是用来创建实例的,它们后续用来创建 NormalModule 以及 ContextModule 实例,这两个工厂对象会在任务点 compile 触发时传递过去,所以任务点 compile 是间接监听这两个对象的任务点的一个入口
// 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 实例将会开始创建 Compilation 对象,这个对象是后续构建流程中最核心最重要的对象,它包含了一次构建过程中所有的数据。也就是说一次构建过程对应一个 Compilation 实例。
当 Compilation 实例创建完成之后,webpack 的准备阶段已经完成,下一步将开始 modules 和 chunks 的生成阶段。
modules 和 chunks 的生成阶段
先解析项目依赖的所有 modules,再根据 modules 生成 chunks
module 解析,包含了三个主要步骤:创建实例、loaders应用以及依赖收集
chunks 生成,主要步骤是找到 chunk 所需要包含的 modules。
下面将以 NormalModule 为例讲解一下 module 的解析过程,ContextModule 等其他模块实例的处理是类似的。
1、步骤是创建 NormalModule 实例。这里需要用到上一个阶段讲到的 NormalModuleFactory 实例, NormalModuleFactory 的 create 方法是创建 NormalModule 实例的入口,内部的主要过程是解析 module 需要用到的一些属性,比如需要用到的 loaders, 资源路径 resource 等等,最终将解析完毕的参数传给 NormalModule 构建函数直接实例化
2、在解析参数的过程中,有两个比较实用的任务点 before-resolve 和 after-resolve,分别对应了解析参数前和解析参数后的时间点。举个例子,在任务点 before-resolve 可以做到忽略某个 module 的解析,webpack 内部插件 IgnorePlugin 就是这么做的。
3、在创建完 NormalModule 实例之后会调用 build 方法继续进行内部的构建。我们熟悉的 loaders 将会在这里开始应用,NormalModule 实例中的 loaders 属性已经记录了该模块需要应用的 loaders
4、