• .27-浅析webpack源码之事件流make(2)


      上一节跑到了NormalModuleFactory模块,调用了原型方法create后,依次触发了before-rsolve、factory、resolver事件流,这节从resolver事件流开始讲。

      源码简化如下:

    this.plugin("resolver", () => (data, callback) => {
        // 来自于create方法
        const contextInfo = data.contextInfo;
        const context = data.context;
        const request = data.request;
        // 检测前后缀
        // 正则比较简单就不解释了
        const noAutoLoaders = /^-?!/.test(request);
        const noPrePostAutoLoaders = /^!!/.test(request);
        const noPostAutoLoaders = /^-!/.test(request);
        let elements = request.replace(/^-?!+/, "").replace(/!!+/g, "!").split("!");
        // 传路径的话这里会是['./input.js']
        let resource = elements.pop();
        // 这里是空数组
        elements = elements.map(identToLoaderRequest);
    
        asyncLib.parallel([ /**/ ], (err, results) => { /**/ });
    });

      第一次进来这个函数传入的是entry,在这里对字符串进行正则校验与切割,一般情况下只会传入口文件路径,官网文档示例也是正常的形式,所以这里暂时无视。

      这里看一下identToLoaderRequest的源码:

    // 根据问号切割值与参数
    /* 
    value?opt => 
    {
        loader: value,
        options: opt
    }
    */
    function identToLoaderRequest(resultString) {
        const idx = resultString.indexOf("?");
        let options;
        if (idx >= 0) {
            options = resultString.substr(idx + 1);
            resultString = resultString.substr(0, idx);
            return {
                loader: resultString,
                options
            };
        } else {
            return {
                loader: resultString
            };
        }
    }

      非常普通的一个参数切割函数。

    async.parallel

      接下来的调用引入了nodejs的async模块里面的parallel方法,该方法官网解释如下:

    /*
        Run the tasks collection of functions in parallel, 
        without waiting until the previous function has completed. 
        If any of the functions pass an error to its callback, 
        the main callback is immediately called with the value of the error. 
        Once the tasks have completed, the results are passed to the final callback as an array.
    */
    asyncLib.parallel([ /*fn1,fn2...*/ ], (err, results) => { /**/ });

      简单来说就是并行调用数组中的方法,将所有方法返回值包装成一个数组(results)传给回调函数,任何一个方法出错会立即执行回调函数并将错误信息作为参数(err)传入。

      这里简要的介绍一下parallel方法源码的核心实现:

    /*
        https://github.com/caolan/async/blob/master/lib/eachOf.js
    */
    /* 
        coll => 方法数组
        iteratee => 迭代器
        callback => 回调函数
    */
    function eachOfArrayLike(coll, iteratee, callback) {
        // 将回调函数包装成只执行一次
        // 具体实现可参考jquery源码的一次性事件绑定
        callback = once(callback || noop);
        var index = 0, // 数组索引
            completed = 0, // 已完成函数数量
            length = coll.length;
        if (length === 0) {
            callback(null);
        }
    
        function iteratorCallback(err, value) {
            // 当其中一个函数执行出错立即执行callback
            if (err) {
                callback(err);
            }
            // breakLoop为空 无视
            // 当所有函数都执行完后调用callback
            else if ((++completed === length) || value === breakLoop) {
                callback(null);
            }
        }
        // 遍历方法数组
        for (; index < length; index++) {
            iteratee(coll[index], index, onlyOnce(iteratorCallback));
        }
    }

      可以看出,callback永远只执行一次,仅当中途执行报错或所有函数都执行完毕。

      如果某一个函数正常执行,则必须调用callback(null,args),其中args就是当前函数的返回值,该值会作为最终回调函数的数组参数之一。

      

      依次直接看数组中的方法:

    asyncLib.parallel([
        // 1
        callback => this.resolveRequestArray(contextInfo, context, elements, this.resolvers.loader, callback),
        // 2
        callback => { /**/ }
    ], /*callback*/ )

      第一个是调用的原型方法,传入了一溜参数,源码如下:

    class NormalModuleFactory extends Tapable {
        /* 
            contextInfo => { issuer: '', compiler:undefined }
            context => process.cwd()
            array => []
            resolver => 来源于WebpackOptionsApply模块中最后compiler.resolvers的设置
            callback => 并行执行完成后的回调函数
        */
        resolveRequestArray(contextInfo, context, array, resolver, callback) {
            // 由于array当前为空数组 所以直接返回
            if (array.length === 0) return callback(null, []);
            asyncLib.map(array, (item, callback) => { /**/ }, callback);
        }
    }

      这里会直接返回,返回值为空数组。

      接下来看第二个方法:

    callback => {
        // resource => './input.js'
        if (resource === "" || resource[0] === "?")
            return callback(null, {
                resource
            });
        this.resolvers.normal.resolve(contextInfo, context, resource, (err, resource, resourceResolveData) => {
            if (err) return callback(err);
            callback(null, {
                resourceResolveData,
                resource
            });
        });
    }

      跳过第一个if判断,继而调用了resolver.normal的resolve方法,传入了3个参数以及一个回调函数。

      下一节分析resolver三个方法的具体实现。

  • 相关阅读:
    SqlLite的使用
    asp.net批量上传图片带进度条显示
    对于GridView控件的RowDataBount事件的错误理解
    关于SQL中时间对比
    关于使用触发器时使用@@identity的问题
    关于Treeview控件如何给每个节点加js脚本的方法
    /etc/init.d/functions详解
    如何解决安装DreamWeaver8 时候提示“无法将数值写入键/SOFTWARE/classes/.shtml”
    [请教]关于超大数据量网站的数据搜索和分页的实现方法
    svchost.exe[900]中发生未处理的win32异常
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/8258727.html
Copyright © 2020-2023  润新知