zoukankan      html  css  js  c++  java
  • .18-浅析webpack源码之compile流程-rules参数处理(1)

      Tips:写到这里,需要对当初的规则进行修改。在必要的地方,会在webpack.config.js中设置特殊的参数来跑源码,例如本例会使用module:{rules:[...]}来测试,基本上测试参数均取自于vue脚手架(太复杂的删掉)。

      下面两节的主要流程图如下:

      在进入compile方法后,迎面而来的就是这么一行代码:

    const params = this.newCompilationParams();

      简单看一下方法,源码如下:

    Compiler.prototype.newCompilationParams = () => {
        const params = {
            normalModuleFactory: this.createNormalModuleFactory(),
            contextModuleFactory: this.createContextModuleFactory(),
            compilationDependencies: []
        };
        return params;
    }
    // 工厂方法1
    Compiler.prototype.createNormalModuleFactory = () => {
        const normalModuleFactory = new NormalModuleFactory(this.options.context, this.resolvers, this.options.module || {});
        this.applyPlugins("normal-module-factory", normalModuleFactory);
        return normalModuleFactory;
    }
    // 工厂方法2
    Compiler.prototype.createContextModuleFactory = () => {
        const contextModuleFactory = new ContextModuleFactory(this.resolvers, this.inputFileSystem);
        this.applyPlugins("context-module-factory", contextModuleFactory);
        return contextModuleFactory;
    }

      该方法生成了一个对象参数,而对象中的值又分别调用了各自的工厂方法。

      按顺序,首先来看NormalModuleFactory工厂方法,首先是构造函数:

    class NormalModuleFactory extends Tapable {
        // context默认为命令执行路径
        // resolvers => {...}
        // options => options.module
        constructor(context, resolvers, options) {
            super();
            // 该参数在WebpackOptionsApply方法中被赋值
            this.resolvers = resolvers;
            // 处理module.rules或module.loaders
            // 两者意义一样
            this.ruleSet = new RuleSet(options.rules || options.loaders);
            // 谁会去设置这玩意???默认参数设置那会被置为true
            // 这里会生成一个返回布尔值的函数 可以简单理解为!!
            this.cachePredicate = typeof options.unsafeCache === "function" ? options.unsafeCache : Boolean.bind(null, options.unsafeCache);
            this.context = context || "";
            // 解析缓存
            this.parserCache = {};
            // 这两个方法超级长
            this.plugin("factory", () => (result, callback) => { /**/ });
            this.plugin("resolver", () => (data, callback) => { /**/ })
        }
    }

      也是一个继承了Tapable框架的类,构造函数除去两个事件流注入不看,剩余的内容只有RuleSet那一块,也是本节的主要内容。

      从参数可以看出,这里是对module.rules处理的地方,本节中测试配置添加了rules方便测试:

    const path = require('path');
    // resolve => path.join(__dirname,'..',path)
    module.exports = {
        entry: './input.js',
        output: {
            filename: 'output.js'
        },
        module: {
            rules: [
                {
                    test: /.js$/,
                    loader: 'babel-loader',
                    include: [resolve('src'), resolve('test')]
                },
                {
                    test: /.css/,
                    loader: 'css-loader!style-loader'
                }
            ]
        }
    };

      只针对配置中有或者常用且官网有解释的参数进行。

    RuleSet

      构造函数如下:

    module.exports = class RuleSet {
        constructor(rules) {
            // 空对象
            this.references = Object.create(null);
            // 格式化
            this.rules = RuleSet.normalizeRules(rules, this.references, "ref-");
        }
    }

      生成了一个纯净对象并调用了本地静态方法进行格式化:

    class RuleSet {
        constructor(rules) { /**/ }
            // rules => 传进来的配置数组
            // refs => 纯净对象
            // ident => 'ref-'
        static normalizeRules(rules, refs, ident) {
            // 分数组与非数组
            if (Array.isArray(rules)) {
                return rules.map((rule, idx) => {
                    return RuleSet.normalizeRule(rule, refs, `${ident}-${idx}`);
                });
            } else if (rules) {
                return [RuleSet.normalizeRule(rules, refs, ident)];
            }
            // 未配置rules直接返回空数组 
            else {
                return [];
            }
        }
    }

      这里也区分了数组参数与非数组参数,但是有个小bug

      看代码可以很容易猜到,数组与非数组的情况理论上是这样的:

    // 数组
    module.exports = {
        // ...
        module: {
            rules: [{ test: /.vue$/, loader: 'vue-loader' }, /*...*/ ]
        }
    };
    // 非数组
    module.exports = {
        // ...
        module: {
            rules: { test: /.vue$/, loader: 'vue-loader' }
        }
    };

      因为传非数组,会被包装成一个数组,所以这种情况属于单loader配置。

      但是,这样配置是会报错的,因为过不了validateSchema的验证,测试结果如图:

      这就很尴尬了,提供了非数组形式的处理方式,但是又不通过非数组的校验,所以这基本上是永远不会被执行的代码。

      管他的,估计源码太大,开发者已经顾不上了。

      下面正式进行格式化阶段,源码整理如下:

    class RuleSet {
        constructor(rules) { /**/ };
        // ident => 'ref-${index}'
        static normalizeRule(rule, refs, ident) {
            // 传入字符串
            // 'css-loader' => use:[{loader:'css-loader'}]
            if (typeof rule === "string")
                return {
                    use: [{
                        loader: rule
                    }]
                };
            // 非法参数
            if (!rule)
                throw new Error("Unexcepted null when object was expected as rule");
            if (typeof rule !== "object")
                throw new Error("Unexcepted " + typeof rule + " when object was expected as rule (" + rule + ")");
    
            const newRule = {};
            let useSource;
            let resourceSource;
            let condition;
            // test
            if (rule.test || rule.include || rule.exclude) { /**/ }
            if (rule.resource) { /**/ }
            // 官网doc都懒得解释 估计是几个很弱智的参数
            if (rule.resourceQuery) { /**/ }
            if (rule.compiler) { /**/ }
            if (rule.issuer) { /**/ }
            // loader、loaders只能用一个
            if (rule.loader && rule.loaders)
                throw new Error(RuleSet.buildErrorMessage(rule, new Error("Provided loader and loaders for rule (use only one of them)")));
    
            const loader = rule.loaders || rule.loader;
            // 处理loader
            if (typeof loader === "string" && !rule.options && !rule.query) {
                checkUseSource("loader");
                newRule.use = RuleSet.normalizeUse(loader.split("!"), ident);
            }
            else if (typeof loader === "string" && (rule.options || rule.query)) {
                checkUseSource("loader + options/query");
                newRule.use = RuleSet.normalizeUse({
                    loader: loader,
                    options: rule.options,
                    query: rule.query
                }, ident);
            } else if (loader && (rule.options || rule.query)) {
                throw new Error(RuleSet.buildErrorMessage(rule, new Error("options/query cannot be used with loaders (use options for each array item)")));
            } else if (loader) {
                checkUseSource("loaders");
                newRule.use = RuleSet.normalizeUse(loader, ident);
            } else if (rule.options || rule.query) {
                throw new Error(RuleSet.buildErrorMessage(rule, new Error("options/query provided without loader (use loader + options)")));
            }
            // 处理use
            if (rule.use) {
                checkUseSource("use");
                newRule.use = RuleSet.normalizeUse(rule.use, ident);
            }
            // 递归处理内部rules
            if (rule.rules)
                newRule.rules = RuleSet.normalizeRules(rule.rules, refs, `${ident}-rules`);
            // 不知道是啥
            if (rule.oneOf)
                newRule.oneOf = RuleSet.normalizeRules(rule.oneOf, refs, `${ident}-oneOf`);
            //
            const keys = Object.keys(rule).filter((key) => {
                return ["resource", "resourceQuery", "compiler", "test", "include", "exclude", "issuer", "loader", "options", "query", "loaders", "use", "rules", "oneOf"].indexOf(key) < 0;
            });
            keys.forEach((key) => {
                newRule[key] = rule[key];
            });
    
            function checkUseSource(newSource) { /**/ }
    
            function checkResourceSource(newSource) { /**/ }
            if (Array.isArray(newRule.use)) {
                newRule.use.forEach((item) => {
                    if (item.ident) {
                        refs[item.ident] = item.options;
                    }
                });
            }
    
            return newRule;
        }
    }

      总体来看源码内容如下:

    1、生成newRules对象保存转换后的rules

    2、处理单字符串rule

    3、处理test、include、exclude参数

    4、处理resource、resourseQuery、compiler、issuer参数

    5、处理loader、loaders、options、query参数

    6、处理use参数

    7、递归处理rules参数

    8、处理oneOf参数

      内容比较多,还是分两节讲吧。

  • 相关阅读:
    python爬虫专栏学习
    Scrapy学习笔记
    《The Python Standard Library》——http模块阅读笔记2
    《The Python Standard Library》——http模块阅读笔记1
    《The Python Tutorial》——Errors and Exceptions 阅读笔记
    web服务器架构演化及所其技术知识体系(分布式的由来)
    django学习笔记——搭建博客网站
    《计算机系统要素》学习笔记
    python查看模块版本及所在文件夹
    网络编程---笔记2
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/8109903.html
Copyright © 2011-2022 走看看