一、前文回顾
本文回到了 NMF 中创建模块的重要流程 ------ hooks.resolve 的流程中,核心点还是关于 loader 的组织,期间讨论了以下重点内容:
- nmf.ruleSet.exec 方法的工作原理,重点解析了其参数、返回值、以及其中的 loader 分类的过程;
- 接着讨论了解析 loader 的过程,按照 loader 的类型即 use/use-post/use-pre 进行分开解析;
- 在解析完成后会执行 continueCallback:
- 3.1. 这个过程主要是处理 loader 的顺序问题,与顺序相关的因素处理 loader 的类型外,还有一个 matchResource 语法,它主要影响行内 loader 的是在常规 loader 还是之后;
- 3.2 最后则是组织 data.createData 为创建模块和构建模块备用;
- 3.3 调用 callback 把执行权限重新交还到 nmf.hooks.factorize 继续模块的创建工作;
上面这些都是为了获取模块路径以及模块对应的 loader 的路径,下面我们回到模块的创建流程!
二、复盘 nmf.hooks.resolve 和 nmf.hooks.factorize
nmf.hooks.resolve 是 nmf.hooks.factorize 的一个分支,所谓 resolve 主要做两件事:
- 就是解析当前 request 对应的模块的绝对路径;
- 计算 request 需要使用 loaders 并解析这些 loader 的绝对路径;
以下为简化后的代码,可以更直观的看住他们间的关系:
js
// 1.
this.hooks.factorize.tapAsync(
{
name: "NormalModuleFactory",
stage: 100
},
(resolveData, callback) => {
// 3.
this.hooks.resolve.callAsync(resolveData, (err, result) => {
// 5.
})
}
});
// 2.
this.hooks.resolve.tapAsync(
{
name: "NormalModuleFactory",
stage: 100
},
(data, callback) => {
// 4.
}
);
以下为各个注释作用:
- 【1.】 为 nmf.hooks.factorize 添加订阅,可以看到订阅中有一个流程就是触发 nmf.hooks.resolve;
- 【2.】 向 nmf.hooks.resolve 添加订阅,订阅的事件函数就是负责 resolve 的整个过程详见【4.】;
- 【3.】 触发 nmf.hooks.resolve 钩子,其实就相当于执行 【2.】订阅时传入的事件函数【4.】;
- 【4.】 负责具体的解析过程的函数,内部包括了创建 resolver 实例的过程、解析行内 loader、解析 matchResource、创建 ruleSet 并计算 loader及对所有 loader 进行排序、解析 request 对应模块/loader的真实路径;
- 【5.】这个函数是拿到 nmf.hooks.resolve 的成果以后的逻辑;
三、创建模块
准确的来说是继续执行 nmf.hooks.factorize 中的触发 nmf.hooks.resolve 之后的逻辑,以下为部分代码!
js
this.hooks.factorize.tapAsync(
{
name: "NormalModuleFactory",
stage: 100
},
(resolveData, callback) => {
this.hooks.resolve.callAsync(resolveData, (err, result) => {
// 1.
if (err) return callback(err);
// 2.
if (result === false) return callback();
// 3.
if (result instanceof Module) return callback(null, result);
// 4.
if (typeof result === "object")
throw new Error(
deprecationChangedHookMessage("resolve", this.hooks.resolve) +
" Returning a Module object will result in this module used as result."
);
// 5.
this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
// 6.
if (err) return callback(err);
// 7.
if (typeof result === "object")
throw new Error(
deprecationChangedHookMessage(
"afterResolve",
this.hooks.afterResolve
)
);
// 8.
if (result === false) return callback();
// 9.
const createData = resolveData.createData;
// 10.
this.hooks.createModule.callAsync(
createData,
resolveData,
(err, createdModule) => {
// 11.
if (!createdModule) {
if (!resolveData.request) {
return callback(new Error("Empty dependency (no request)"));
}
// 12.
createdModule = new NormalModule(
createData
);
}
// 13.
createdModule = this.hooks.module.call(
createdModule,
createData,
resolveData
);
// 14.
return callback(null, createdModule);
}
);
});
});
}
);
下面我们分步骤看下执行逻辑:
3.1 验证 resolve 成果
1.
判断是否有错误,有错直接调用 callback 终止后续的模块创建流程;
2.
判断 result 是否为 false,如果为 false 说明需要忽略当前 request,不要创建模块;
3.
如果 result 本身就是 Module 实例,此时也不需要重新创建模块,直接调用 callback 并传入 result;
4.
判断 result 是否为对象数据类型,如果不是则说明 resolve 过程中出现异常,抛出报错并终止;
3.2 afterResolve 阶段
5.
触发 nmf.hooks.afterResolve 钩子,传入 resolve 的解析结果,这个钩子中可以再次修改解析数据;
3.3 再次验证 afterResolve 结果
6.
如果 nmf.hooks.afterResolve 返回错误,则放弃后续流程并终止;
7.
判断 result 是否为对象数据类型,如果不是则说明 afterResolve 过程中出现异常,抛出报错并终止;
8.
判断 result 是否为 false,如果为 false 说明需要忽略当前 request,不要创建模块;
3.4 createModule 阶段
9.
获取 resolveData.createData 也就是我们的 resolve 的成果对象,用于创建模块用;
10.
触发 nmf.hooks.createModule 钩子,判断是否有插件接管了模块的创建过程,即别的流程已经创建过
当前模块;
3.5 验证 createModule 并创建模块
11.
如果 nmf.hooks.createModule 没有返回 createdModule 模块同时 resolveData.request 不存在,则报错中断;
12.
此时说明没有人接管 nmf.hooks.createModue 也没有异常,此时创建 NormalModule 的实例,也就是我们心心念念的模块了;
3.6 module 阶段
13.
触发 nmf.hooks.module 钩子,并传入刚刚创建的模块对象和创建该模块的 createData 和 resolveData,此时可以二次修改这个模块对象;
14.
调用 callback 并传入刚刚创建(并且经过hooks.module 修饰)的模块对象;
到这里整个模块的创建过程就全部完成了;
四、总结
本文接上文的 resolve 过程详细讨论了以下内容:
-
复盘了 nmf.hooks.resolve 和 nmf.hooks.factorize 间的关系:nmf.hooks.resolve 是 nmf.hooks.factorize 的一个分支流程。创建模块需要获知当前 request 对应的模块真实的路径已经被应用的 loader 及其路径,而这个工作就是由 nmf.hooks.resolve 完成;
-
在拿到路径和 loader 之后,nmf.hooks.factorize 继续后面的模块的创建过程,一共包含了以下步骤:
-
- 验证 resolve 成果:该过程主要是针对 resolve 得到的模块的路径、需要应用的 loader 及其路径进行简单校验;
-
- 触发 afterResolve 钩子,这个钩子可以针对 resolve 的结果进行改动;
-
- 经历 afterResolve 后,需要重新对 resolve 结果进行校验;
-
- 开始进入到了 createModule 阶段,该阶段主要用于创建 normal moduel 的实例对象,期间讲述了 nmf.hooks.createModule 钩子,该钩子可以接管 webpack 创建模块的过程;
-
- 验证 createModule 钩子的结果,看看是否有人接管了创建模块的过程;
-
- 此时已经获得了模块实例对象,无论是由 webpack 自身还是通过 nmf.hooks.createModule 钩子创建所得的模块对象,此时 nmf.hooks.module 钩子,传入创建模块的基础数据和解析数据以及模块对象;最后则是调用 callback 把模块给到外界;
-
回忆一下 callback 是什么呢?这个过程还需要向前回溯,思考一下模块的创建是由谁触发的呢?这里先做一个铺垫,下文我们将讨论这个主题!