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

相关推荐
Bruce-li__18 小时前
前端开发利器:nvm、npm与pnpm全面解析与TypeScript/JavaScript选择指南
javascript·typescript·npm
前端缘梦19 小时前
前端模块化详解:CommonJS 与 ES Module 核心原理与面试指南
前端·面试·前端工程化
一点一木19 小时前
告别重复代码!Vue3 中后台下拉框统一加载方案(自动缓存、去重、过滤、适配表单与表格)
前端·javascript·vue.js
Hilaku19 小时前
前端开发,为什么容易被边缘化?
前端·javascript·面试
砺能19 小时前
JavaScript 截取 HTML 生成图片
前端·javascript
解道Jdon19 小时前
IntelliJ IDEA全流程智能支持Java 25新特性
javascript·reactjs
云枫晖19 小时前
JS核心知识-闭包
前端·javascript
前端康师傅20 小时前
Javascript 数组基础用法
前端·javascript
召摇20 小时前
命令-查询分离原则(Command-Query Separation)
前端·javascript·面试
猪哥帅过吴彦祖20 小时前
第 3 篇:让图形动起来 - WebGL 2D 变换
前端·javascript·webgl