一、 前文回顾
webpack 通过 AsyncQueue 实现异步并发队列,整体核心有以下几点:
- 创建队列实例时通过 parent 构建亲代关系队列;
- 当向任一队列中添加成员时都会触发 _root._ensureProcessing 方法从祖先队列开始并发消耗队列;
- 启用消耗之后,各个队列的 _processor 方法就会处理队列成员数据;
- _processor 处理队列成员数据得到结果后传递给 _handleResult 方法,该方法把结果传给 callback,同时维护并发数量递减及恢复并发消耗队列的工作;
前面几篇有关模块创建的过程都是宏观层面的,今天我们正式进入模块创建具体实现过程。
二、模块工厂(factory)
在 webpack 中模块是由对应的 ModuleFactory 即模块工厂创建所得,在前面创建 Compilation 实例的过程中提到过这个概念,当时有两个 ModuleFactory:
- NormalModuleFactory:常规的 JS 模块工厂
- ContextModuleFactory:上下文模块
这里以我们的示例项目中的代码模块为例,普通的 js 对应的则是 NormalModuleFactory;
2.1 factory.create() 调用
结合前面的文章,我们模块调用逻辑至于 compilation.factorizeQueue 的 processor 函数中:
js
class Compilation {
_factorizeModule({ factory, /* .... */ }, callback) {
// 调用 factory.create 方法
factory.create(
{
contextInfo: {
issuer: originModule ? originModule.nameForCondition() : "",
issuerLayer: originModule ? originModule.layer : null,
compiler: this.compiler.name,
...contextInfo
},
resolveOptions: originModule ? originModule.resolveOptions : undefined,
context: context
? context
: originModule
? originModule.context
: this.compiler.context,
dependencies: dependencies
},
(err, result) => { /* 处理模块创建的结果 */ });
}
}
我们已经知道了上面的 factory 是 NormalModuleFactory 的实例,所以 factory.create 自然就是 NormalModuleFactory.prototype.create 方法
2.2 NormalModuleFactory.prototype.create
该方法位于 NormalModuleFactory.js 模块,该模块用于创建模块工厂实例,注意不是模块实例,是模块工厂
(下称 NMF)!
NMF 的实例是在创建 Compilation 实例之前实例化的,这个过程不在赘述,如果没有印象的话,建议复习前面的章节。
文件位置:webpack/lib/NormalModuleFactory.js
现在我们看看 NMF.prototype.create 方法的大致结构,经过简化后的代码如下:
js
class NormalModuleFactory extends ModuleFactory {
create(data, callback) {
// 1.
const resolveData = {
contextInfo,
resolveOptions,
context,
request,
assertions,
dependencies,
dependencyType,
fileDependencies,
missingDependencies,
contextDependencies,
createData: {},
cacheable: true
};
// 2.
this.hooks.beforeResolve.callAsync(resolveData, (err, result) => {
// 3.
this.hooks.factorize.callAsync(resolveData, (err, module) => {
// 4.
const factoryResult = {
module,
fileDependencies,
missingDependencies,
contextDependencies,
cacheable: resolveData.cacheable
};
// 5.
callback(null, factoryResult);
});
});
}
}
2.2.1 参数
- data:创建模块所需的信息,包括 contextInfo、dependencies及 resolverOptions(创建解析器 Resolver 需要) 等信息,当然这些数据都是前面的环节推入到 factorizeQueue 队列中的;
js
factory.creat({
contextInfo: {
issuer: originModule ? originModule.nameForCondition() : "",
issuerLayer: originModule ? originModule.layer : null,
compiler: this.compiler.name,
...contextInfo
},
resolveOptions: originModule ? originModule.resolveOptions : undefined,
context: context
? context
: originModule
? originModule.context
: this.compiler.context,
dependencies: dependencies
},
- callback:受理模块创建后事宜的回调函数,这里暂时不展开这个部分
2.2.2 逻辑
create 方法主要做了以下工作:
- 组织 resolveData 对象,这其中包含了 context、resolveOptions及待解析的 request 等信息;
- 触发 nmf.hooks.beforeResolve 钩子并传入 resolveData 对象,这个钩子可以用于修改 resolveData 中的信息;
- 在 beforeResolve 的回调中触发 nmf.hooks.factorize 钩子,在创建 nmf 实例的时候该钩子订阅了回调, 该钩子就是创建模块的信号;
- 在 nmf.hooks.factorize 钩子的回调中,组织 factoryResult 对象,其中包含了新创建的 module 及 fileDependencies、 missingDependencies、 contextDependencies 收集到的三种依赖;
- 调用 callback 并传入 factoryResult 对象,结束当前模块的创建流程;
从上面的流程可知,其中重要的环节在 nmf.hooks.factorize 这个钩子,现在我们看看这个钩子从哪里注册的,以及这个钩子中都干了什么;
三、NormalModuleFactory 构造函数
NormalModuleFactory 用于创建模块工厂实例,而模块工厂用于创建模块;以下为精简后的代码,我把重要的环节大致分为 6 个步骤:
js
class NormalModuleFactory extends ModuleFactory {
constructor({
context,
fs,
resolverFactory,
options,
associatedObjectForCache,
layers = false
}) {
super();
// 1.
this.hooks = Object.freeze({
resolve: new AsyncSeriesBailHook(["resolveData"]),
factorize: new AsyncSeriesBailHook(["resolveData"]),
beforeResolve: new AsyncSeriesBailHook(["resolveData"]),
// ....
});
// 2.
this.resolverFactory = resolverFactory;
// 3.
this.ruleSet = ruleSetCompiler.compile([
{
rules: options.defaultRules
},
{
rules: options.rules
}
]);
// 4.
this.hooks.factorize.tapAsync(
{
name: "NormalModuleFactory",
stage: 100
},
(resolveData, callback) => {
}
);
// 6.
this.hooks.resolve.tapAsync(
{
name: "NormalModuleFactory",
stage: 100
},
(data, callback) => {
}
);
}
}
- 声明 nmf 的 hooks 对象,这其中包含了上面我们提到的 beforeResolve、factorize、resolve 等钩子;
- 缓存 resolverFactory,resolverFactory 是创建 resolver (路径解析器)的工厂,后面将会有相当的篇幅介绍 resolver 相关内容;
- 初始化 nmf.ruleSet,ruleSet 是一个新概念,但是他的前身大家都熟悉------
loader 和 应用 loader 的规则,即告知当前模块要使用哪些loader
;ruleSet 是 经由 webpack 格式化之后的标准对象,后面模块构建(module.build)的过程我们再展开; - 订阅 nmf.hook.factorize 钩子,并注册回调,回调的逻辑我们下面单独开一个标题展开讲;
- 订阅 nmf.hooks.resolve 钩子,并注册回调,这个回调我们同样开一个小主题讲;
3.1 nmf.hook.factorize 回调
以下是 nfm 实例化过程中订阅 nmf.hooks.factorize 的回调函数,可以直接说所谓 NormalModuleFactory 的 factory(工厂)就是这个钩子的回调函数了,这个回调函数就是创建模块的工厂;
以下是经过简化的代码:
js
(resolveData, callback) => {
// 1.
this.hooks.resolve.callAsync(resolveData, (err, result) => {
// 2.
this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
// 3.
const createData = resolveData.createData;
this.hooks.createModule.callAsync(
createData,
resolveData,
(err, createdModule) => {
if (!createdModule) {
// 4.
createdModule = new NormalModule(createData));
}
// 5.
createdModule = this.hooks.module.call(
createdModule,
createData,
resolveData
);
// 6.
return callback(null, createdModule);
}
);
});
});
}
我们将这个回到分成 6 个步骤:
- 触发 nmf.hooks.resolve 钩子,传入 resolveData 作为参数,该参数用于解析 request 获取 request 对应的路径相关信息;
- 触发 nmf.hooks.afterResolve 钩子,传入 resolveData;
- 触发 nmf.hooks.createModule 钩子,传入 createData 和 resolveData;
- 创建 NormalModule 实例得到模块对象,这个就是我们心心念念的
模块(module)
对象; - 触发 nmf.hooks.module 钩子,传入刚刚创建的模块对象;
- 调用 callback,将新建模块传递给模块的受理函数继续后续流程;
3.2 nmf.hooks.resolve 回调
注册在 nmf.hooks.resolve 的回调主要用于解析当前 request 对应的模块以及该模块需要应用的 loader 的路径。
以下是经过简化后的代码:
js
(data, callback) => {
// 1.
const loaderResolver = this.getResolver("loader");
const resolveContext = {
fileDependencies,
missingDependencies,
contextDependencies
};
// 2.
this.resolveRequestArray(
contextInfo,
this.context,
useLoadersPost,
loaderResolver,
resolveContext,
(err, result) => {
postLoaders = result;
continueCallback(err);
}
);
// 3.
this.resolveRequestArray(
contextInfo,
this.context,
useLoaders,
loaderResolver,
resolveContext,
(err, result) => {
normalLoaders = result;
continueCallback(err);
}
);
// 4.
this.resolveRequestArray(
contextInfo,
this.context,
useLoadersPre,
loaderResolver,
resolveContext,
(err, result) => {
preLoaders = result;
continueCallback(err);
});
});
// 5.
const defaultResolve = context => {
// 7.
const normalResolver = this.getResolver(
"normal",
dependencyType
? cachedSetProperty(
resolveOptions || EMPTY_RESOLVE_OPTIONS,
"dependencyType",
dependencyType
)
: resolveOptions
);
// 8.
this.resolveResource(
contextInfo,
context,
unresolvedResource,
normalResolver,
resolveContext,
(err, resolvedResource, resolvedResourceResolveData) => {
//
}
);
};
// 6.
defaultResolve(context);
}
我们把这个过程分为了 9 个步骤:
- 调用 nmf.getResolver 获取 loader Resolver;
- 调用 nmf.resolveRequestArray 解析 postLoader;
- 调用 nmf.resolveRequestArray 解析 normalLoader;
- 调用 nmf.resolveRequestArray 解析 preLoader;
- 声明 defaultResolve 方法,该方法用于解析资源路径;
- 调用 defaultResolve 方法,启动 request 对应的资源路径解析
- 调用 nmf.getResolver 方法创建 normal resolver;
- 调用 nmf.resolveResource 解析资源路径;
四、总结
本文主要讨论了 NormalModuleFactory 类型在模块的创建过程中的主要作用,该类型上的 NMF.prototype.create 方法用于创建模块,其核心原理如下:
- 触发 nmf.hooks.beforeResolve 接着触发 nmf.hooks.factorize 钩子;
- 在 nmf 实例创建的过程中会订阅 nmf.hooks.factorize 钩子,主要做了以下工作:
- 2.2 触发 nmf.hooks.resolve 完成 loader、normal 的解析工作;
- 2.2 创建 NormalModule 实例并处理各种 dependencies;
- 2.3 调用 callback 交付新建模块实例;
接着我们还讲述了 nmf.hooks.resolve 的工作流程,主要分为两部分:
- 解析 normal/pre/post 三种 laoder 的路径;
- 解析常规模块资源路径;
- 将解析所得的结果交付到 nmf.hooks.factory 当中的调用;