”做技术分享?苟都不做“做,做的就是 module.rules 加工过程

一、前文总结

上文讲述了 webpack 内部与 loader 相关的重要概念 Rule 以及 rule 的具体生效基础 ------ nmf.ruleSet,具体内容如下:

  1. nmf.ruleSet 的实例化过程,ruleSet 是 ruleSetCompiler.compile 方法的返回值;
  2. 介绍了 options.defaultRules 的出处,他的初始化实在 webpack 创建 compiler 实例的第一个步骤------格式化 options 选项对象时;
  3. 介绍了 options.rules,这个是 webpack.config.js 中声明的 loader 匹配条件和应用 loader;
  4. 介绍了 ruleSetCompier 常量,它是一个 RuleSetCompiler 实例,实例化过程中传入了一大堆插件实例;
  5. 最后介绍了 webpack 构建体系与 Rule 体系插件的异同点,这也是一个重点关注点!

关于这几个插件和 RuleSetCompiler 的具体工作原理是我们今天这一篇要研究的重点!

二、rulePlugins

这个主题我们看看 ruleSetCompiler 实例化用到的 4 类插件具体工作原理!

2.1 BasicMatcherRulePlugin

基础匹配器插件,从注册的属性看,这个插件用的是最多的,我们看看它都注册了哪些 rule:

  1. new BasicMatcherRulePlugin("test", "resource"):Rule.resouce:匹配资源路径,webpack 文档:Rule.resource
  2. new BasicMatcherRulePlugin("scheme"):Rule.scheme,匹配声明的 scheme,webpack 文档 Rule.scheme
  3. new BasicMatcherRulePlugin("mimetype"):Rule.mimetype,匹配 data 对应 uri 的 mimetype Rule.mimetype
  4. new BasicMatcherRulePlugin("dependency"):Rule.dependency,这个文档暂时没有声明作用;
  5. new BasicMatcherRulePlugin("include", "resource"),Rule.include 置顶必须包含的 resource, Rule.include
  6. new BasicMatcherRulePlugin("exclude", "resource", true),Reclude.exclude Rule.exclude 指定必不包含的 resource,文档传送门
  7. new BasicMatcherRulePlugin("resource"):同上 resource
  8. new BasicMatcherRulePlugin("resourceQuery"):匹配指定的资源路径查询字符串,Rule.resourceQuery 文档传送门
  9. new BasicMatcherRulePlugin("resourceFragment"):文档未声明,据之前讲 resolver 的经验看,fragment 指的是 resource 后的 #哈希,比如 '/a/b/c.js?some=test#thisFragment',这么多年没见过😂
  10. new BasicMatcherRulePlugin("realResource"):realResource 一般与 resource 相等,但是当被 !=! matchResource 语法覆写后才不相等;
  11. new BasicMatcherRulePlugin("issuer"):Rule.issuer,匹配 request 的发起者;Rule.issuer 文档
  12. new BasicMatcherRulePlugin("compiler"):暂未查询到文档;
  13. new BasicMatcherRulePlugin("issuerLayer"):允许按 issuer 的 layer 进行过滤/匹配,Rule.issuerLayer 文档

2.1.1 构造函数

先来看看构造函数的参数:

  1. ruleProperty, 规则属性,用于构造 condition.fn,匹配函数的基准属性;
  2. dataProperty, 数据属性,用于构造 condition.property,不传时等于 ruleProperty;
  3. invert, 取反标识符,是否对结果进行取反
js 复制代码
class BasicMatcherRulePlugin {
    constructor(ruleProperty, dataProperty, invert) {
        this.ruleProperty = ruleProperty;
        this.dataProperty = dataProperty || ruleProperty;
        this.invert = invert || false;
    }


    apply(ruleSetCompiler) {
       
    }
}

2.1.2 apply 方法

js 复制代码
class BasicMatcherRulePlugin {
    constructor(ruleProperty, dataProperty, invert) {}

    apply(ruleSetCompiler) {
        // 1.
        ruleSetCompiler.hooks.rule.tap(
            "BasicMatcherRulePlugin",
            (path, rule, unhandledProperties, result) => {
                // 2.
                if (unhandledProperties.has(this.ruleProperty)) {
                
                    // 3.
                    unhandledProperties.delete(this.ruleProperty);
                    
                    // 4.
                    const value = rule[this.ruleProperty];
                    
                    // 5.
                    const condition = ruleSetCompiler.compileCondition(
                        `${path}.${this.ruleProperty}`,
                        value
                    );
                    
                    // 6.
                    const fn = condition.fn;
                    result.conditions.push({
                        property: this.dataProperty,
                        matchWhenEmpty: this.invert
                                ? !condition.matchWhenEmpty
                                : condition.matchWhenEmpty,
                        fn: this.invert ? v => !fn(v) : fn
                    });
                }
            }
        );
    }
}
  1. 订阅 ruleSetCompiler.hooks.rule 钩子,以下为定于的具体逻辑;
  2. 判断当前 ruleProperty 是否已经处理过(unhandledRuleProperties.has(xxx)),如果未处理再执行后续逻辑;
  3. 走到这里说明 ruleProperty 未经处理,此时先从 unhandledRuleProperties 中剔除当前 ruleProperty;
  4. 从 rule 中取用当前 ruleProperty 中取出对应的值;
  5. 调用 ruleSetCompiler.compieCondition 把 ruleProperty,path,value 编译成一个带有匹配函数的 fn 的对象 condition;
  6. 向 result 结果集合中加入当前 ruleProperty 构造的标准 condition 对象;

这里来一个例子,以 mimetype 为例,看下 condition.fn:

2.2 ObjectMatcherRulePlugin

该插件用于处理对象类型的 rule 匹配,和上面的基础规则类似,唯一不同的就是需要将传入的对象进行展开;

2.2.1 构造函数

js 复制代码
class ObjectMatcherRulePlugin {
    constructor(ruleProperty, dataProperty) {
        this.ruleProperty = ruleProperty;
        this.dataProperty = dataProperty || ruleProperty;
    }


    apply(ruleSetCompiler) { }
}

构造函数功能很简单,保存 ruleProperty 和 dataProperty,当并传入 dataProperty 时,用 ruleProperty 兜底;

2.2.2 apply 方法

apply 方法是实现的核心,我们看看它内部实现的能力:

js 复制代码
class ObjectMatcherRulePlugin {
    constructor(ruleProperty, dataProperty) {}

    apply(ruleSetCompiler) {
        // 1.
        const { ruleProperty, dataProperty } = this;
        
        // 2.
        ruleSetCompiler.hooks.rule.tap(
            "ObjectMatcherRulePlugin",
            (path, rule, unhandledProperties, result) => {
                // 3.
                if (unhandledProperties.has(ruleProperty)) {
                    // 4.
                    unhandledProperties.delete(ruleProperty);
                    
                    // 5.
                    const value = rule[ruleProperty];
                    for (const property of Object.keys(value)) {
                        // 6.
                        const nestedDataProperties = property.split(".");
                        
                        // 7.
                        const condition = ruleSetCompiler.compileCondition(
                                `${path}.${ruleProperty}.${property}`,
                                value[property]
                        );
                        
                        // 8.
                        result.conditions.push({
                                property: [dataProperty, ...nestedDataProperties],
                                matchWhenEmpty: condition.matchWhenEmpty,
                                fn: condition.fn
                        });
                    }
                }
            }
        );
    }
}
  1. 获取构造函数执行时保存的 uleProperty, dataProperty 属性;
  2. 订阅 ruleSetCompiler.hooks.rule 钩子,以下为订阅函数的处理逻辑;
  3. 判断 unhandledProperties 是否包含 当前 ruleProperty,若不包含再继续后续逻辑;
  4. 从 unhandledProperties 中移除当前 ruleProperty;
  5. 从 rule 中获取 ruleProperty 对应的值 value 并遍历该 value 对象(for of Object.keys(value));
  6. 将嵌套属性进行分拆;
  7. 调用 ruleSetCompiler.compileCondition 为当前的 property 构造 condition.fn 函数;
  8. 构造标准的 condition 对象推入最终结果集合 result 中;

对比 basic 的插件,这个算是一个加强版,他会把这个对象展开,为每个属性都构造一个 condition,相当于是一个拍平的动作。

2.3 BasicEffectRulePlugin

我把这个插件翻译成 基础效果规则插件,这个 effect 真的不知道咋翻译。名字而已不是重点,我们看看它和前面的 BasicMathcerRulePlugin 有啥区别吧!

js 复制代码
class BasicEffectRulePlugin {
    constructor(ruleProperty, effectType) {
        this.ruleProperty = ruleProperty;
        this.effectType = effectType || ruleProperty;
    }

    apply(ruleSetCompiler) {
        ruleSetCompiler.hooks.rule.tap(
            "BasicEffectRulePlugin",
            (path, rule, unhandledProperties, result, references) => {
                if (unhandledProperties.has(this.ruleProperty)) {
                    unhandledProperties.delete(this.ruleProperty);

                    const value = rule[this.ruleProperty];

                    result.effects.push({
                            type: this.effectType,
                            value
                    });
                }
            }
        );
    }
}

2.3.1 构造函数

构造函数的作用十分简洁,保存 ruleProperty, effectType 两个数据。

js 复制代码
class BasicEffectRulePlugin {
    constructor(ruleProperty, effectType) {
        this.ruleProperty = ruleProperty;
        this.effectType = effectType || ruleProperty;
    }

    apply(ruleSetCompiler) { /* .... */ }
}

2.3.2 apply 方法

我们看这个插件都做了哪些事情,按照顺序分为 5 个步骤:

js 复制代码
class BasicEffectRulePlugin {
    constructor(ruleProperty, effectType) { /* .... */ }

    apply(ruleSetCompiler) {
        // 1.
        ruleSetCompiler.hooks.rule.tap(
            "BasicEffectRulePlugin",
            (path, rule, unhandledProperties, result, references) => {
                // 2.
                if (unhandledProperties.has(this.ruleProperty)) {
                    // 3.
                    unhandledProperties.delete(this.ruleProperty);

                    // 4.
                    const value = rule[this.ruleProperty];
                    
                    // 5.
                    result.effects.push({
                        type: this.effectType,
                        value
                    });
                }
            }
        );
    }
}
  1. 向 ruleSetCompiler.hooks.rule 钩子订阅函数,以下为函数中的细节;
  2. 判断 unhandledProperties 是否存在 当前 ruleProperty,若存在则处理;
  3. 从 unhandledProperties 中移除 ruleProperty 属性;
  4. 从 rule 中取出 ruleProperty 对应的 value;
  5. 将 ruleProperty 对应的 value 构造成标准 effect 对象加入到 result.effects 数组中;

2.3.3 Effect 与前面 matcher 插件的区别

这个构造应该和 ruleSetCompiler 最终构造的 result 有关,仅从插件 apply 方法看,直观的差异是:

  1. matcherRulePlugin 的结果加入到 result.conditions 数组,而 effectPlugin 是把结果加入到 result.effects 中;
  2. 除了目标归属不同,最终的构造结果也不同,matcherRule 插件是把 ruleProperty 经过 ruleSetCompiler 编译成函数的,而 effectPlugin 则不需要编译,直接把结果放到 effects;

大胆推测,condition 的应该是动态调用取结果的,而 effect 的就是静态只读的值;具体是什么我们后面解密 ruleSetCompiler 的相关方法!

三、总结

本文介绍了和 rule 相关的前三个插件------ BasicMatcherRulePlugin、ObjectMatcherRulePlugin、BasicEffectRulePlugin,下面我们看看他们三个的作用和生效的原理:

  1. BasicMatcherRulePlugin:注册包括 scheme、mimetype 在内的 basic rule,其内部是被 ruleSetCompiler.compileCondition 方法编译成 condition.fn 函数并加入到 result.conditions 中;
  2. ObjectMatcherRulePlugin:处理对象 rule,其内部是遍历这个对象,把每个属性都编译成一个 conditon.fn 函数加入到 result.conditions;
  3. BasicEffectRulePlugin:处理 effect rule,这个插件和前两个有所不同,它是直接把 ruleProperty 对应的的 value 加入到 result.effects 中;

webpack 中一共包含了 4 个插件,这里我们讨论了三个,下一篇我们讨论最后一个 UseEffectPlugin!

相关推荐
像风一样自由20208 小时前
HTML与JavaScript:构建动态交互式Web页面的基石
前端·javascript·html
浪裡遊9 小时前
React Hooks全面解析:从基础到高级的实用指南
开发语言·前端·javascript·react.js·node.js·ecmascript·php
Liudef0610 小时前
2048小游戏实现
javascript·css·css3
独行soc11 小时前
#渗透测试#批量漏洞挖掘#HSC Mailinspector 任意文件读取漏洞(CVE-2024-34470)
linux·科技·安全·网络安全·面试·渗透测试
独立开阀者_FwtCoder12 小时前
【Augment】 Augment技巧之 Rewrite Prompt(重写提示) 有神奇的魔法
前端·javascript·github
我想说一句12 小时前
事件机制与委托:从冒泡捕获到高效编程的奇妙之旅
前端·javascript
汤姆Tom13 小时前
JavaScript reduce()函数详解
javascript
小飞悟13 小时前
你以为 React 的事件很简单?错了,它暗藏玄机!
前端·javascript·面试
中微子13 小时前
JavaScript 事件机制:捕获、冒泡与事件委托详解
前端·javascript
蓝翔认证10级掘手13 小时前
🤯 家人们谁懂啊!我的摸鱼脚本它...它成精了!🚀
javascript