.31-淺析webpack源碼之doResolve事件流(3)

  放個流程圖:vue

  這裏也放一下request對象內容,這節完過後以下(把vue-cli的package.json也複製過來了):es6

/*
    { 
        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' 
    }
*/

  上一節看到這:vue-cli

// 調用的是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事件流。json

  來源以下:數組

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

  這個插件就很是簡單了:app

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。工具

 

  剛發現事件流的名字表明着某階段,此時表明描述文件解析完畢。this

  接下來的事件流來源於如下幾個插件:spa

// 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  prototype

  先從第一個開始看,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:\workspace\doc\.\input.js
            // 最後格式化返回d:\workspace\doc\input.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對象有所瞭解,總結以下:

一、該對象能夠處理resolve參數、loader、module等等

二、插件的鏈式調用相似於if/else,好比說若是傳進來的是一個module,插件會流向module事件流;若是是普通的文件,會流向本節所講的方,每一種狀況都有本身的結局。

三、一部分參數處理依賴於package.json的配置對象內容

相關文章
相關標籤/搜索