.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-endsWithif (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 requestcallback(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 requestcallback(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 => browservar 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 requestcallback(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 alternativescallback(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),// undefinedrelativePath: 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的配置對象內容
轉載于:https://www.cnblogs.com/QH-Jimmy/p/8340639.html
總結
以上是生活随笔為你收集整理的.31-浅析webpack源码之doResolve事件流(3)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WPF快速入门系列(2)——深入解析依赖
- 下一篇: 云通讯短信验证码实例