原文首发于 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
。