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

      第一步处理rule为字符串,直接返回一个包装类,很简单看注释就好了。

    test/include/exclude

      然后处理test、include、exclude,如下:

    if (rule.test || rule.include || rule.exclude) {
        // 标记使用参数
        checkResourceSource("test + include + exclude");
        // 没有就是undefined
        condition = {
            test: rule.test,
            include: rule.include,
            exclude: rule.exclude
        };
        // 处理常规参数
        try {
            newRule.resource = RuleSet.normalizeCondition(condition);
        } catch (error) {
            throw new Error(RuleSet.buildErrorMessage(condition, error));
        }
    }

      checkResourceSource直接看源码:

    let resourceSource;
    // ...
    function checkResourceSource(newSource) {
        // 第一次直接跳到后面赋值
        if (resourceSource && resourceSource !== newSource)
            throw new Error(RuleSet.buildErrorMessage(rule, new Error("Rule can only have one resource source (provided " + newSource + " and " + resourceSource + ")")));
        resourceSource = newSource;
    }

      这个用于检测配置来源的唯一性,后面会能看到作用,同样作用的还有checkUseSource方法。

      随后将三个参数包装成一个对象传入normalizeCondition方法,该方法对常规参数进行函数包装:

    class RuleSet {
        constructor(rules) { /**/ };
        static normalizeCondition(condition) {
            // 假值报错
            if (!condition) throw new Error("Expected condition but got falsy value");
            // 检测给定字符串是否以这个开头
            if (typeof condition === "string") { return str => str.indexOf(condition) === 0; }
            // 函数直接返回
            if (typeof condition === "function") { return condition; }
            // 正则表达式返回一个正则的test函数
            if (condition instanceof RegExp) { return condition.test.bind(condition); }
            // 数组map递归处理 有一个满足返回true
            if (Array.isArray(condition)) {
                const items = condition.map(c => RuleSet.normalizeCondition(c));
                return orMatcher(items);
            }
            if (typeof condition !== "object") throw Error("Unexcepted " + typeof condition + " when condition was expected (" + condition + ")");
            const matchers = [];
            // 对象会对每个值进行函数包装弹入matchers中
            Object.keys(condition).forEach(key => {
                const value = condition[key];
                switch (key) {
                    case "or":
                    case "include":
                    case "test":
                        if (value)
                            matchers.push(RuleSet.normalizeCondition(value));
                        break;
                    case "and":
                        if (value) {
                            const items = value.map(c => RuleSet.normalizeCondition(c));
                            matchers.push(andMatcher(items));
                        }
                        break;
                    case "not":
                    case "exclude":
                        if (value) {
                            const matcher = RuleSet.normalizeCondition(value);
                            matchers.push(notMatcher(matcher));
                        }
                        break;
                    default:
                        throw new Error("Unexcepted property " + key + " in condition");
                }
            });
            if (matchers.length === 0)
                throw new Error("Excepted condition but got " + condition);
            if (matchers.length === 1)
                return matchers[0];
            return andMatcher(matchers);
        }
    }

      这里用js的rules做案例,看这个方法的返回: 

    class RuleSet {
        constructor(rules) { /**/ };
        /*
        Example:
            {
                test: /.js$/,
                loader: 'babel-loader',
                include: [resolve('src'), resolve('test')]
            }
        */
        /*
        condition:
            {
                test: /.js$/,
                include: ['d:\workspace\src', 'd:\workspace\test'],
                exclude: undefined
            }
        */
        static normalizeCondition(condition) {
            // include返回类似于 [(str) => str.indexOf('d:\workspace\src') === 0,...] 的函数
            if (typeof condition === "string") { return str => str.indexOf(condition) === 0; }
            // test参数返回了 /.js$/.test 函数
            if (condition instanceof RegExp) { return condition.test.bind(condition); }
            // include为数组
            if (Array.isArray(condition)) {
                const items = condition.map(c => RuleSet.normalizeCondition(c));
                return orMatcher(items);
            }
            const matchers = [];
            // 解析出['test','include','exclude']
            Object.keys(condition).forEach(key => {
                const value = condition[key];
                switch (key) {
                    // 此value为一个数组
                    case "include":
                    case "test":
                        if (value)
                            matchers.push(RuleSet.normalizeCondition(value));
                        break;
                    // undefined跳过
                    case "exclude":
                        if (value) { /**/ }
                        break;
                    default:
                        throw new Error("Unexcepted property " + key + " in condition");
                }
            });
            return andMatcher(matchers);
        }
    }

      这里继续看orMatcher、andMatcher函数的处理:

    function orMatcher(items) {
        // 当一个函数被满足条件时返回true
        return function(str) {
            for (let i = 0; i < items.length; i++) {
                if (items[i](str))
                    return true;
            }
            return false;
        };
    }
    
    function andMatcher(items) {
        // 当一个条件不满足时返回false
        return function(str) {
            for (let i = 0; i < items.length; i++) {
                if (!items[i](str))
                    return false;
            }
            return true;
        };
    }

      从字面意思也可以理解函数作用,比如说这里的include被包装成一个orMatcher函数,传入的字符串无论是以数组中任何一个元素开头都会返回true,andMatcher以及未出现的notMatcher同理。

    resource

      下面是对resource参数的处理,源码如下:

    if (rule.resource) {
        // 如果前面检测到了test || include || exclude参数 这里会报错
        checkResourceSource("resource");
        try {
            newRule.resource = RuleSet.normalizeCondition(rule.resource);
        } catch (error) {
            throw new Error(RuleSet.buildErrorMessage(rule.resource, error));
        }
    }

      可以看出这个参数与前面那个是互斥的,应该是老版API,下面两种方式实现是一样的:

    /*
    方式1:
        rules:[
            {
                test: /.js$/,
                loader: 'babel-loader',
                include: [resolve('src'), resolve('test')]
            }
        ]
    */
    /*
    方式2:
        rules:[
            {
                resource:{
                    test: /.js$/,
                    include: [resolve('src'), resolve('test')]
                    exclude: undefined
                }
            }
        ]
    */

      接下来的resourceQuery、compiler、issuer先跳过,脚手架没有,不知道怎么写。

    loader

      下面是另一块主要配置loader(s),这里loader与loaders作用一样的,当初还头疼啥区别:

    const loader = rule.loaders || rule.loader;
    // 单loader情况
    if (typeof loader === "string" && !rule.options && !rule.query) {
        checkUseSource("loader");
        newRule.use = RuleSet.normalizeUse(loader.split("!"), ident);
    }
    // loader配合options或query出现
    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);
    }
    // options与query同时出现报错
    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)")));
    }
    /*
        处理这种愚蠢用法时:
        {
            test: /.css$/,
            loader: [{ loader: 'less-loader' }, { loader: 'css-loader' }]
        }
    */
    else if (loader) {
        checkUseSource("loaders");
        newRule.use = RuleSet.normalizeUse(loader, ident);
    }
    // 单独出现options或者query报错
    else if (rule.options || rule.query) {
        throw new Error(RuleSet.buildErrorMessage(rule, new Error("options/query provided without loader (use loader + options)")));
    }

      之前举例的babel-loader就是第一种单loader配置,这里使用vue-loader嵌套的css配置作为示例。

      首先normalizeUse方法如下:

    static normalizeUse(use, ident) {
        // 单loader字符串
        if (Array.isArray(use)) {
            return use
                // 如果是单loader情况这里会返回[[loader1...],[loader2...]]
                .map((item, idx) => RuleSet.normalizeUse(item, `${ident}-${idx}`))
                // 扁平化后变成[loader1,loader2]
                .reduce((arr, items) => arr.concat(items), []);
        }
        // 对象或字符串
        return [RuleSet.normalizeUseItem(use, ident)];
    };

      先讲解有options或者query的模式,这里会把参数包装一个对象传入normalizeUse方法:

    loader && (options || query)

    // indet => 'ref-'
    static normalizeUseItem(item, ident) {
        if (typeof item === "function")
            return item;
        if (typeof item === "string") {
            return RuleSet.normalizeUseItemString(item);
        }
        const newItem = {};
        if (item.options && item.query) throw new Error("Provided options and query in use");
        if (!item.loader) throw new Error("No loader specified");
        newItem.options = item.options || item.query;
        // 防止options:null的情况
        if (typeof newItem.options === "object" && newItem.options) {
            // 这里只是为了处理ident参数
            if (newItem.options.ident)
                newItem.ident = newItem.options.ident;
            else
                newItem.ident = ident;
        }
        // 取出loader参数
        const keys = Object.keys(item).filter(function(key) {
            return ["options", "query"].indexOf(key) < 0;
        });
        keys.forEach(function(key) {
            newItem[key] = item[key];
        });
        /*
        newItem = 
        {
            loader:'原字符串',
            ident:'ref-', (或自定义)
            options:{...}
        }
        */
        return newItem;
    }

      比起对test的处理,这里就简单的多,简述如下:

    1、尝试取出options(query)中的ident参数,赋值给newItem的ident,没有就赋值为默认的ref-

    2、取出对象中不是options、query的键,赋值给newItem,这里传进来的键只有三个,剩下的就是loader,这个filter是逗我???

    3、返回newItem对象

      总之,不知道为什么防止什么意外情况而写出来的垃圾代码,这段代码其实十分简单。

    单loader

      第二种情况是单字符串,会对'!'进行切割调用map方法处理,再调用reduce方法扁平化为一个数组,直接看normalizeUseItemString方法:

    /*
    Example:
    {
        test: /.css$/,
        loader: 'css-loader?{opt:1}!style-loader'
    }
    返回:
    [{loader:'css-loader',options:{opt:1}},
    {loader:'style-loader'}]
    */
    static normalizeUseItemString(useItemString) {
        // 根据'?'切割获取loader的参数
        const idx = useItemString.indexOf("?");
        if (idx >= 0) {
            return {
                loader: useItemString.substr(0, idx),
                // 后面的作为options返回
                options: useItemString.substr(idx + 1)
            };
        }
        return {
            loader: useItemString
        };
    }

      这种就是串行调用loader的处理方式,代码很简单。

    Tips

      这里有一点要注意,一旦loader使用了串行调用方式,不要传options或者query参数,不然loader不会被切割解析!!!

      这两种方式只是不同的使用方法,最后的结果都是一样的。

    use

      下面是use参数的解析,估计因为这是老版的API,所以格式要求严格,处理比较随便:

    if (rule.use) {
        checkUseSource("use");
        newRule.use = RuleSet.normalizeUse(rule.use, ident);
    }

      如果不用loader(s),可以用use替代,但是需要按照格式写,比如说上述串行简写的loader,在use中就需要这样写:

    /*
        {
            test:/.css$/,
            use:['css-loader','style-loader]
        }
    */

      而对应options或query,需要这样写:

    /*
        {
            test:/.css$/,
            use:{
                loader:'css-loader',
                options:'1'
            }
        }
    */

      因为基本上不用了,所以这里简单看一下。

    rules、oneOf

    if (rule.rules)
        newRule.rules = RuleSet.normalizeRules(rule.rules, refs, `${ident}-rules`);
    if (rule.oneOf)
        newRule.oneOf = RuleSet.normalizeRules(rule.oneOf, refs, `${ident}-oneOf`);

      这两个用得少,也没啥难理解的。

      下一步是过滤出没有处理的参数,添加到newRuls上:

    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];
    });

      基本上用到都是test、loader、options,暂时不知道有啥额外参数。

    ident

    // 防止rules:[]的情况
    if (Array.isArray(newRule.use)) {
        newRule.use.forEach((item) => {
            // ident来源于options/query的ident参数
            if (item.ident) {
                refs[item.ident] = item.options;
            }
        });
    }

      最后这个地方是终于用到了传进来的纯净对象refs。

      如果在options中传了ident参数,会填充这个对象,key为ident值,value为对应的options。

      至此,所有rules的规则已经解析完毕,真是配置简单处理复杂。

  • 相关阅读:
    利用webpack构建vue项目
    关于写毕业设计网页代码写后感
    用canvas属性写一个五角星哦
    css3瀑布流布局
    css3学习笔记,随时帮你记起遗忘的css3
    自己做得一个用于直观观察css3 transform属性中的rotate 3D效果
    第一次讨论——关于块级元素与行内元素的区别,浮动与清除浮动,定位,兼容性问题
    软件工程第一次作业
    自我介绍
    自我介绍
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/8112520.html
Copyright © 2011-2022 走看看