哈哈,上首頁真難,每次都被秒下,心疼本身1秒~node
這裏補充一個簡要圖,方便理解流程:json
在處理./input.js入口文件時,在類型判斷被分爲普通文件,因此走的文件事件流,最後拼接獲得文件的絕對路徑。數組
可是對應"babel-loader"這個字符串,在以下正則中被斷定爲模塊類型:babel
// Resolver.js var notModuleRegExp = /^\.$|^\.[\\\/]|^\.\.$|^\.\.[\/\\]|^\/|^[A-Z]:[\\\/]/i; Resolver.prototype.isModule = function isModule(path) { return !notModuleRegExp.test(path); };
所以,參考第33節的流程圖,在ModuleKindPlugin插件中,會走新的事件流,以下:app
ModuleKindPlugin.prototype.apply = function(resolver) { var target = this.target; resolver.plugin(this.source, function(request, callback) { // 這裏request.module爲true 繼續走下面的代碼 if (!request.module) return callback(); var obj = Object.assign({}, request); delete obj.module; // target => raw-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)); }); };
進入raw-module事件流!框架
raw-module事件流函數
事件流定義以下:this
// raw-module // 默認爲空數組 moduleExtensions.forEach(function(item) { plugins.push(new ModuleAppendPlugin("raw-module", item, "module")); }); // 垃圾插件 if (!enforceModuleExtension) plugins.push(new TryNextPlugin("raw-module", null, "module"));
這裏沒有任何操做,直接進入module事件流。spa
module事件流 => ModulesInHierachicDirectoriesPluginprototype
定義地點以下:
// module // modules => [ [ 'node_modules' ] ] modules.forEach(function(item) { // 進這個分支 if (Array.isArray(item)) plugins.push(new ModulesInHierachicDirectoriesPlugin("module", item, "resolve")); else plugins.push(new ModulesInRootPlugin("module", item, "resolve")); });
這個modules是默認的一個數組,內容爲node_modules,後來二次包裝變成個二維數組了。
總之無論那麼多,直接看插件內容:
ModulesInHierachicDirectoriesPlugin.prototype.apply = function(resolver) { var directories = this.directories; var target = this.target; resolver.plugin(this.source, function(request, callback) { var fs = this.fileSystem; // 早該這樣了 一堆callback誰分的清啊 var topLevelCallback = callback; // getPaths獲取進程路徑各級目錄 // D:\workspace\doc => ['d:\\workspace\\doc','d:\\workspace','d:'] // 這個map+reduce總的來講就是拼接路徑 /* 最後會生成 [ 'D:\\workspace\\doc\\node_modules', 'D:\\workspace\\node_modules', 'D:\\node_modules' ] */ var addrs = getPaths(request.path).paths.map(function(p) { return directories.map(function(d) { return this.join(p, d); }, this); }, this).reduce(function(array, p) { array.push.apply(array, p); return array; }, []); // 開始讀取每個拼接成的數組 forEachBail(addrs, function(addr, callback) { fs.stat(addr, function(err, stat) { // 當讀取到一個有效路徑時就進入下一個事件流 if (!err && stat && stat.isDirectory()) { var obj = Object.assign({}, request, { path: addr, request: "./" + request.request }); var message = "looking for modules in " + addr; return resolver.doResolve(target, obj, message, createInnerCallback(callback, topLevelCallback)); } if (topLevelCallback.log) topLevelCallback.log(addr + " doesn't exist or is not a directory"); if (topLevelCallback.missing) topLevelCallback.missing.push(addr); return callback(); }); }, callback); }); };
這裏的方法很簡單很暴力,直接拆分當前的目錄,而後跟node_modules進行拼接,而後嘗試讀取每個路徑。
當讀取成功並且該目錄是一個文件夾時,就會把信息加入對象並進入下一個事件流。
這個事件流的名字是resolve……是的,又要進入新的一輪循環,此次會以普通文件的形式讀取,因此以前在獲取模塊類型的第一時間須要刪除module屬性。
再次進入doResolve中,這裏只用文字描述流程,request變成了./babel-loader,path爲node_modules文件夾的路徑。
一、ParsePlugin類型判斷
因爲在上面的代碼中,request被手動添加了./的前綴,因此在模塊判斷正則中被斷定爲非模塊。
二、DescriptionFilePlugin
因爲path修改,因此在讀取package.json時會直接讀取node_modules/babel-loader/package.json文件。
可是在第一次讀取會失敗,由於並不存在node_modules/package.json文件,修正過程看下面。
三、JoinRequestPlugin
在這個插件,配置文件讀取路徑會被修正,代碼以下:
JoinRequestPlugin.prototype.apply = function(resolver) { var target = this.target; resolver.plugin(this.source, function(request, callback) { /* path => D:\workspace\node_modules request => ./babel-loader 拼接後獲得 D:\\workspace\\node_modules\\babel-loader */ var obj = Object.assign({}, request, { path: resolver.join(request.path, request.request), relativePath: request.relativePath && resolver.join(request.relativePath, request.request), request: undefined }); resolver.doResolve(target, obj, null, callback); }); };
四、FileExistsPlugin
因爲上面的修正,這個插件的讀取會有一些問題:
FileExistsPlugin.prototype.apply = function(resolver) { var target = this.target; resolver.plugin(this.source, function(request, callback) { var fs = this.fileSystem; var file = request.path; // file => D:\\workspace\\node_modules\\babel-loader fs.stat(file, function(err, stat) { if (err || !stat) { if (callback.missing) callback.missing.push(file); if (callback.log) callback.log(file + " doesn't exist"); return callback(); } // 因爲這是一個文件夾 因此會進入這個分支 if (!stat.isFile()) { if (callback.missing) callback.missing.push(file); if (callback.log) callback.log(file + " is not a file"); return callback(); } this.doResolve(target, request, "existing file: " + file, callback, true); }.bind(this)); }); };
因爲讀取到這是一個文件夾,因此不會進入最後的事件流,而是直接調用無參回調函數,再次從新嘗試讀取。
文件夾讀取 => existing-drectory
因爲這裏會讀取文件夾類型,因此順便把文件夾的事件流分支過一下。
相關的核心事件流只有existing-directory,以下:
// existing-directory // 這個忽略 plugins.push(new ConcordMainPlugin("existing-directory", {}, "resolve")); // 默認 mainFields => ["browser", "module", "main"] mainFields.forEach(function(item) { plugins.push(new MainFieldPlugin("existing-directory", item, "resolve")); }); // 默認 mainFiles => ["index"] mainFiles.forEach(function(item) { plugins.push(new UseFilePlugin("existing-directory", item, "undescribed-raw-file")); });
除去第一個垃圾插件,後面兩個能夠過一下。
MainFieldPlugin
這個插件主要獲取loader的入口文件相對路徑。
// existing-directory // 這個忽略 plugins.push(new ConcordMainPlugin("existing-directory", {}, "resolve")); /* 默認mainFields => { name: 'loader', forceRelative: true } { name: 'main', forceRelative: true } */ mainFields.forEach(function(item) { plugins.push(new MainFieldPlugin("existing-directory", item, "resolve")); }); MainFieldPlugin.prototype.apply = function(resolver) { var target = this.target; // options => ["browser", "module", "main"] var options = this.options; resolver.plugin(this.source, function mainField(request, callback) { if (request.path !== request.descriptionFileRoot) return callback(); var content = request.descriptionFileData; // path.basename => 獲取path的最後一部分 // D:\workspace\node_modules\babel-loader\package.json => package.json var filename = path.basename(request.descriptionFilePath); var mainModule; // loader、main var field = options.name; 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 === "string") { mainModule = current; } } else { // 獲取配置文件中對應鍵的值 if (typeof content[field] === "string") { mainModule = content[field]; } } if (!mainModule) return callback(); if (options.forceRelative && !/^\.\.?\//.test(mainModule)) mainModule = "./" + mainModule; // 定義request的值 var obj = Object.assign({}, request, { request: mainModule }); return resolver.doResolve(target, obj, "use " + mainModule + " from " + options.name + " in " + filename, callback); }); }
而在babel-loader中,有一個名爲main的鍵,值爲'lib/index.js',與目錄拼接後,恰好是babel-loader這個模塊的入口文件的絕對路徑。
一旦在package.json中找到了對應的值,就會跳過下一個插件,直接進入最終的事件流。
可是爲了完整,仍是看一眼,當package.json中沒有對入口文件的路徑進行定義時,會進入MainFieldPlugin插件,源碼以下:
UseFilePlugin.prototype.apply = function(resolver) { var filename = this.filename; var target = this.target; resolver.plugin(this.source, function(request, callback) { // 默認filename => "index" // 直接對目錄與默認入口文件名進行拼接 var filePath = resolver.join(request.path, filename); // 重定義鍵 var obj = Object.assign({}, request, { path: filePath, relativePath: request.relativePath && resolver.join(request.relativePath, filename) }); resolver.doResolve(target, obj, "using path: " + filePath, callback); }); };
很簡單,直接拼接默認入口文件名與目錄,接下來流程以下:
mainFiles.forEach(function(item) { plugins.push(new UseFilePlugin("existing-directory", item, "undescribed-raw-file")); }); // undescribed-raw-file // 從新走一邊文件類型的流程 plugins.push(new DescriptionFilePlugin("undescribed-raw-file", descriptionFiles, "raw-file")); plugins.push(new NextPlugin("after-undescribed-raw-file", "raw-file"));
能夠看到,拼接完後,將以文件類型的形式嘗試從新讀取新路徑。
至此,模塊的入口文件路徑讀取過程解析完畢,基本上其餘loader、框架、庫等都是按照這個模式讀取到入口文件的。