webpack5 创建一个 模块需要几步?

一、前文回顾

本文回到了 NMF 中创建模块的重要流程 ------ hooks.resolve 的流程中,核心点还是关于 loader 的组织,期间讨论了以下重点内容:

  1. nmf.ruleSet.exec 方法的工作原理,重点解析了其参数、返回值、以及其中的 loader 分类的过程;
  2. 接着讨论了解析 loader 的过程,按照 loader 的类型即 use/use-post/use-pre 进行分开解析;
  3. 在解析完成后会执行 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 主要做两件事:

  1. 就是解析当前 request 对应的模块的绝对路径;
  2. 计算 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 过程详细讨论了以下内容:

  1. 复盘了 nmf.hooks.resolve 和 nmf.hooks.factorize 间的关系:nmf.hooks.resolve 是 nmf.hooks.factorize 的一个分支流程。创建模块需要获知当前 request 对应的模块真实的路径已经被应用的 loader 及其路径,而这个工作就是由 nmf.hooks.resolve 完成;

  2. 在拿到路径和 loader 之后,nmf.hooks.factorize 继续后面的模块的创建过程,一共包含了以下步骤:

      1. 验证 resolve 成果:该过程主要是针对 resolve 得到的模块的路径、需要应用的 loader 及其路径进行简单校验;
      1. 触发 afterResolve 钩子,这个钩子可以针对 resolve 的结果进行改动;
      1. 经历 afterResolve 后,需要重新对 resolve 结果进行校验;
      1. 开始进入到了 createModule 阶段,该阶段主要用于创建 normal moduel 的实例对象,期间讲述了 nmf.hooks.createModule 钩子,该钩子可以接管 webpack 创建模块的过程;
      1. 验证 createModule 钩子的结果,看看是否有人接管了创建模块的过程;
      1. 此时已经获得了模块实例对象,无论是由 webpack 自身还是通过 nmf.hooks.createModule 钩子创建所得的模块对象,此时 nmf.hooks.module 钩子,传入创建模块的基础数据和解析数据以及模块对象;最后则是调用 callback 把模块给到外界;

回忆一下 callback 是什么呢?这个过程还需要向前回溯,思考一下模块的创建是由谁触发的呢?这里先做一个铺垫,下文我们将讨论这个主题!

相关推荐
百锦再6 小时前
每天两小时学习three.js
开发语言·javascript·学习·3d·three·2d·gbl
小桥风满袖6 小时前
极简三分钟ES6 - const声明
前端·javascript
yinke小琪6 小时前
如何决定使用HashMap还是TreeMap
java·后端·面试
南北是北北6 小时前
Flow 的 emit 与 tryEmit :它们出现在哪些类型、背压/缓存语义、何时用谁、常见坑
前端·面试
flyliu6 小时前
继承,继承,继承,哪里有家产可以继承
前端·javascript
机构师6 小时前
<uniapp><日期组件>基于uniapp,编写一个自定义的日期组件
前端·javascript
Dream it possible!6 小时前
LeetCode 面试经典 150_矩阵_有效的数独(34_36_C++_中等)(额外数组)
leetcode·面试·矩阵
fury_1236 小时前
vue3:el-date-picker三十天改成第二十九天的23:59:59
前端·javascript·vue.js
小周同学@6 小时前
DOM常见的操作有哪些?
前端·javascript