zoukankan      html  css  js  c++  java
  • .31-浅析webpack源码之doResolve事件流(3)

      放个流程图:

      这里也放一下request对象内容,这节完事后如下(把vue-cli的package.json也复制过来了):

    /*
        { 
            context: { issuer: '', compiler: undefined },
            path: 'd:\workspace\doc',
            request: './input.js',
            query: '',
            module: false,
            directory: false,
            file: false,
            descriptionFilePath: 'd:\workspace\doc\package.json',
            descriptionFileData: *package.json内容*,
            descriptionFileRoot: 'd:\workspace\doc',
            relativePath: '.',
            __innerRequest_request: './input.js',
            __innerRequest_relativePath: '.',
            __innerRequest: './input.js' 
        }
    */

      上一节看到这:

    // 调用的是callback()
    function innerCallback(err, result) {
        if (arguments.length > 0) {
            if (err) return callback(err);
            if (result) return callback(null, result);
            return callback();
        }
        runAfter();
    }

      这里接下来会调用runAfter方法,之前有讲解过这个,简单讲就是触发after-type的事件流,这里的type为parsed-resolve,即触发after-parsed-resolve事件流。

      来源如下:

    plugins.push(new NextPlugin("after-parsed-resolve", "described-resolve"));

      这个插件就非常简单了:

    NextPlugin.prototype.apply = function(resolver) {
        var target = this.target;
        resolver.plugin(this.source, function(request, callback) {
            resolver.doResolve(target, request, null, callback);
        });
    };

      直接调用doResolve方法触发下一个target的事件流,比起有描述文件的情况,这里的区别就是request少了几个参数,触发下一个事件流时没有message。

      刚发现事件流的名字代表着某阶段,此时代表描述文件解析完毕。

      接下来的事件流来源于以下几个插件:

    // described-resolve
    alias.forEach(function(item) {
        plugins.push(new AliasPlugin("described-resolve", item, "resolve"));
    });
    plugins.push(new ConcordModulesPlugin("described-resolve", {}, "resolve"));
    aliasFields.forEach(function(item) {
        plugins.push(new AliasFieldPlugin("described-resolve", item, "resolve"));
    });
    plugins.push(new ModuleKindPlugin("after-described-resolve", "raw-module"));
    plugins.push(new JoinRequestPlugin("after-described-resolve", "relative"));

    AliasPlugin  

      先从第一个开始看,alias参数引用vue-cli的代码,这里的alias在上面的第二部分进行了转换(具体可参考28节)。

      数组的元素作为参数传入了AliasPlugin插件中,源码如下:

    /*
    源配置
        alias: {
            'vue$': 'vue/dist/vue.esm.js',
            '@': '../src'
        }
    转换后为
        alias:[
            {
                name: 'vue',
                onlyModule: true,
                alias: 'vue/dist/vue.esm.js'
            },
            {
                name: '@',
                onlyModule: false,
                alias: '../src'           
            }
        ]
    */
    AliasPlugin.prototype.apply = function(resolver) {
        var target = this.target;
        var name = this.name;
        var alias = this.alias;
        var onlyModule = this.onlyModule;
        resolver.plugin(this.source, function(request, callback) {
            var innerRequest = request.request;
            if (!innerRequest) return callback();
            // 两个元素传进来并不满足if条件 跳过
            // startsWith可参考ES6的新方法http://es6.ruanyifeng.com/#docs/string#includes-startsWith-endsWith
            if (innerRequest === name || (!onlyModule && startsWith(innerRequest, name + "/"))) {
                if (innerRequest !== alias && !startsWith(innerRequest, alias + "/")) {
                    var newRequestStr = alias + innerRequest.substr(name.length);
                    var obj = Object.assign({}, request, {
                        request: newRequestStr
                    });
                    return resolver.doResolve(target, obj, "aliased with mapping '" + name + "': '" + alias + "' to '" + newRequestStr + "'", createInnerCallback(function(err, result) {
                        if (arguments.length > 0) return callback(err, result);
    
                        // don't allow other aliasing or raw request
                        callback(null, null);
                    }, callback));
                }
            }
            return callback();
        });
    };

      不太懂这里的处理是干什么,反正两个元素传进来都没有满足if条件,跳过。

    ConcordModulesPlugin

      described-resolve事件流还没有完,所以callback执行后只是记数,下一个插件源码如下:

    ConcordModulesPlugin.prototype.apply = function(resolver) {
        var target = this.target;
        resolver.plugin(this.source, function(request, callback) {
            // 获取的还是'./input.js'
            var innerRequest = getInnerRequest(resolver, request);
            if (!innerRequest) return callback();
            // request.descriptionFileData就是配置文件package.json中的内容
            var concordField = DescriptionFileUtils.getField(request.descriptionFileData, "concord");
            // 找不到该属性直接返回
            if (!concordField) return callback();
            // 下面的都不用跑了
            var data = concord.matchModule(request.context, concordField, innerRequest);
            if (data === innerRequest) return callback();
            if (data === undefined) return callback();
            if (data === false) {
                var ignoreObj = Object.assign({}, request, {
                    path: false
    
                });
                return callback(null, ignoreObj);
            }
            var obj = Object.assign({}, request, {
                path: request.descriptionFileRoot,
                request: data
            });
            resolver.doResolve(target, obj, "aliased from description file " + request.descriptionFilePath + " with mapping '" + innerRequest + "' to '" + data + "'", createInnerCallback(function(err, result) {
                if (arguments.length > 0) return callback(err, result);
    
                // Don't allow other aliasing or raw request
                callback(null, null);
            }, callback));
        });
    };

      这里有两个工具方法:getInnerRequest、getFiled,第一个获取request的inner属性,代码如下:

    module.exports = function getInnerRequest(resolver, request) {
        // 第一次进来是没有这些属性的
        if (typeof request.__innerRequest === "string" &&
            request.__innerRequest_request === request.request &&
            request.__innerRequest_relativePath === request.relativePath)
            return request.__innerRequest;
        var innerRequest;
        // './input.js'
        if (request.request) {
            innerRequest = request.request;
            // 尝试获取relativePath属性进行拼接
            if (/^..?//.test(innerRequest) && request.relativePath) {
                innerRequest = resolver.join(request.relativePath, innerRequest);
            }
        } else {
            innerRequest = request.relativePath;
        }
        // 属性添加
        request.__innerRequest_request = request.request;
        request.__innerRequest_relativePath = request.relativePath;
        return request.__innerRequest = innerRequest;
    };

      总的来说就是尝试获取__innerRequest属性,但是初次进来是没有的,所以会在后面进行添加,最后返回的仍然是'./input.js'。

      第二个方法就比较简单了,只是从之前读取的package.json对象查询对应的字段,代码如下:

    // content为package.json配置对象
    function getField(content, field) {
        if (!content) return undefined;
        // 数组及单key模式
        if (Array.isArray(field)) {
            var current = content;
            for (var j = 0; j < field.length; j++) {
                if (current === null || typeof current !== "object") {
                    current = null;
                    break;
                }
                current = current[field[j]];
            }
            if (typeof current === "object") {
                return current;
            }
        } else {
            if (typeof content[field] === "object") {
                return content[field];
            }
        }
    }

      代码非常简单,这里就不讲了。

      常规情况下,没人会去设置concord属性吧,在vue-cli我也没看到,这里先跳过。

    AliasFieldPlugin

      接下来是这个不知道干啥的插件,处理的是resolve.aliasFields参数,默认参数及插件源码如下:

    // "aliasFields": ["browser"],
    AliasFieldPlugin.prototype.apply = function(resolver) {
        var target = this.target;
        var field = this.field;
        resolver.plugin(this.source, function(request, callback) {
            if (!request.descriptionFileData) return callback();
            // 一样的
            var innerRequest = getInnerRequest(resolver, request);
            if (!innerRequest) return callback();
            // filed => browser
            var fieldData = DescriptionFileUtils.getField(request.descriptionFileData, field);
            if (typeof fieldData !== "object") {
                if (callback.log) callback.log("Field '" + field + "' doesn't contain a valid alias configuration");
                return callback();
            }
            var data1 = fieldData[innerRequest];
            var data2 = fieldData[innerRequest.replace(/^.//, "")];
            var data = typeof data1 !== "undefined" ? data1 : data2;
            if (data === innerRequest) return callback();
            if (data === undefined) return callback();
            if (data === false) {
                var ignoreObj = Object.assign({}, request, {
                    path: false
                });
                return callback(null, ignoreObj);
            }
            var obj = Object.assign({}, request, {
                path: request.descriptionFileRoot,
                request: data
            });
            resolver.doResolve(target, obj, "aliased from description file " + request.descriptionFilePath + " with mapping '" + innerRequest + "' to '" + data + "'", createInnerCallback(function(err, result) {
                if (arguments.length > 0) return callback(err, result);
    
                // Don't allow other aliasing or raw request
                callback(null, null);
            }, callback));
        });
    };

      开头跟之前那个是一样的,也是调用getField从package.json中获取对应的配置,但是这个默认的browser我在vue-cli也找不到,暂时跳过。

      正常处理完described-resolve事件流,继续执行runafter触发after-described-resolve事件流,来源如下:

    plugins.push(new ModuleKindPlugin("after-described-resolve", "raw-module"));
    plugins.push(new JoinRequestPlugin("after-described-resolve", "relative"));

    ModuleKindPlugin

    ModuleKindPlugin.prototype.apply = function(resolver) {
        var target = this.target;
        resolver.plugin(this.source, function(request, callback) {
            // 判断module属性
            if (!request.module) return callback();
            var obj = Object.assign({}, request);
            // 删除module属性
            delete obj.module;
            // 直接触发下一个事件流
            resolver.doResolve(target, obj, "resolve as module", createInnerCallback(function(err, result) {
                if (arguments.length > 0) return callback(err, result);
    
                // Don't allow other alternatives
                callback(null, null);
            }, callback));
        });
    };

      这里的处理十分简单,判断request对象是否是module,是则直接触发下一个事件流。

      而在第一次时进来的是入口文件,module属性为false,所以这里会跳过,后面处理module再回来讲。

    JoinRequestPlugin

    JoinRequestPlugin.prototype.apply = function(resolver) {
        var target = this.target;
        resolver.plugin(this.source, function(request, callback) {
            var obj = Object.assign({}, request, {
                // request.path => d:\workspace\doc
                // request.request => ./input.js
                // 在join方法中会被拼接成d:workspacedoc.input.js
                // 最后格式化返回d:workspacedocinput.js
                path: resolver.join(request.path, request.request),
                // undefined
                relativePath: request.relativePath && resolver.join(request.relativePath, request.request),
                request: undefined
            });
            resolver.doResolve(target, obj, null, callback);
        });
    };

      这个地方终于把入口文件的路径拼起来了,接下来调用下一个事件流,这节先到这里。

      写完这节,总算对Resolver对象有所了解,总结如下:

    1、该对象可以处理resolve参数、loader、module等等

    2、插件的链式调用类似于if/else,比如说如果传进来的是一个module,插件会流向module事件流;如果是普通的文件,会流向本节所讲的方,每一种情况都有自己的结局。

    3、一部分参数处理依赖于package.json的配置对象内容

  • 相关阅读:
    20145229吴姗珊《信息安全系统设计基础》第3周学习总结
    20145229《信息安全系统设计基础》第2周学习总结
    20145207 myeclipse测试
    20145207 ms11_050漏洞攻击
    20145207 ms08_067攻击实验
    20145207《Java程序设计》实验一(Java开发环境的熟悉)实验报告
    20145207《Java程序设计》第7周学习总结
    20145207李祉昂《网络对抗技术》恶意代码分析
    20145207《Java程序设计》第6周学习总结
    20145207《网络对抗》免杀原理与实践
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/8340639.html
Copyright © 2011-2022 走看看