原文首發於 blog.flqin.com。若有錯誤,請聯繫筆者。分析碼字不易,轉載請代表出處,謝謝!node
接上文,在 resolver
函數回調裏,觸發 normalModuleFactory.hooks
:afterResolve
以後,回調裏執行:webpack
let createdModule = this.hooks.createModule.call(result); // result 即爲 resolver 返回的組合對象 data
if (!createdModule) {
if (!result.request) {
return callback(new Error('Empty dependency (no request)'));
}
createdModule = new NormalModule(result);
}
createdModule = this.hooks.module.call(createdModule, result);
return callback(null, createdModule);
複製代碼
這裏觸發 normalModuleFactory.hooks
:createModule
,若是鉤子裏沒有項目配置的自定義 module
,則使用 webpack
生成的 module
。web
獲得 module
實例,接着觸發 normalModuleFactory.hooks
:module
以後,跳出 factory
函數,執行 factory
函數回調進行依賴緩存後,跳出 create
函數執行 moduleFactory.create
的回調。回調裏執行:api
const addModuleResult = this.addModule(module); // 將這個 `module` 保存到全局的 `Compilation.modules` 數組中和 `_modules` 對象中,判斷`_modules`是否有該 module 來設置是否已加載的標識
module = addModuleResult.module;
onModule(module); // 若是是入口文件還會將 modules 保存到 `Compilation.entries`
dependency.module = module;
module.addReason(null, dependency); // 添加該 `module` 被哪些模塊依賴
複製代碼
而後調用 this.buildModule
進入 build
階段。該方法作了回調緩存後,觸發 compilation.hooks
:buildModule
,而後執行 module.build()
。數組
在 /node_modules/webpack/lib/NormalModule.js
文件裏執行 module.build
, 設置一些屬性後,直接調用了 this.doBuild
。緩存
該方法裏先執行了 this.createLoaderContext
獲得loaderContext
,爲全部的 loader
提供上下文環境並共享,而後調用了 runLoaders
:app
runLoaders(
{
resource: this.resource,
loaders: this.loaders,
context: loaderContext,
readResource: fs.readFile.bind(fs)
},
(err, result) => {
//...
}
);
複製代碼
該方法來自 loader-runner
,經過各類 loader
處理源碼後,獲得一個處理後的 string
或 buffer
(可能還有個 sourcemap
)。異步
還能夠解析自定義 loader
編寫一個 loader。async
主要流程爲:函數
runLoaders
-> iteratePitchingLoaders(按正序 require 每一個 loader)
-> loadLoader(對應的 loader 導出的函數賦值到 loaderContext.loader[].normal、pitch函數賦值到loaderContext.loader[].pitch,而後執行pitch函數(若是有的話))
-> processResource(轉換 buffer 和設置 loaderIndex)
-> iterateNormalLoaders(倒序執行全部 loader)
-> runSyncOrAsync(同步或者異步執行 loader)
每一個 loader
能夠掛載一個 pitch
函數,該函數主要是用於利用 module
的 request
,來提早作一些攔截處理的工做,並不實際處理 module
內容文檔。
正序 require
loader
並執行其 pitch
方法( loadLoader
裏),在執行後的回調裏,若是有除了 err
的參數還有其餘參數,則執行 iterateNormalLoaders
越過剩下的未 require
的 loader
直接進入到執行 loader
的步驟。若是想沒有其餘參數,則執行 iteratePitchingLoaders
進行下個 loader
的 require
。如代碼所示:
if (args.length > 0) {
loaderContext.loaderIndex--;
iterateNormalLoaders(options, loaderContext, args, callback);
} else {
iteratePitchingLoaders(options, loaderContext, callback);
}
複製代碼
倒序執行每一個 loader
的 normal
方法 。
// 該方法按正序 require 每一個 loader
function iteratePitchingLoaders(options, loaderContext, callback) {
// abort after last loader 讀取全部 loader 後,執行 processResource 方法
if (loaderContext.loaderIndex >= loaderContext.loaders.length) return processResource(options, loaderContext, callback);
var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; //選擇第一個 loader
// iterate 增序後遞歸讀取下一個 loader
if (currentLoaderObject.pitchExecuted) {
loaderContext.loaderIndex++;
return iteratePitchingLoaders(options, loaderContext, callback);
}
// load loader module 加載該 loader 模塊
// 對應的 loader 導出的函數賦值到 loaderContext.loader[].normal
loadLoader(currentLoaderObject, function(err) {
if (err) {
loaderContext.cacheable(false);
return callback(err);
}
var fn = currentLoaderObject.pitch; //loadLoader 裏會把 module 賦值到 loader.normal, pitch 賦值到 loader.pitch
currentLoaderObject.pitchExecuted = true;
if (!fn) return iteratePitchingLoaders(options, loaderContext, callback);
// 若是有的話,開始執行 pitch 函數,根據參數狀況決定是否繼續讀取剩下的loader
runSyncOrAsync(fn, loaderContext, [loaderContext.remainingRequest, loaderContext.previousRequest, (currentLoaderObject.data = {})], function(err) {
if (err) return callback(err);
var args = Array.prototype.slice.call(arguments, 1);
if (args.length > 0) {
loaderContext.loaderIndex--;
iterateNormalLoaders(options, loaderContext, args, callback);
} else {
iteratePitchingLoaders(options, loaderContext, callback);
}
});
});
}
// 轉換 buffer 和設置 loaderIndex
function processResource(options, loaderContext, callback) {
// set loader index to last loader 獲取最後一個 loader 的 index
loaderContext.loaderIndex = loaderContext.loaders.length - 1;
var resourcePath = loaderContext.resourcePath;
if (resourcePath) {
loaderContext.addDependency(resourcePath);
// 轉換爲 buffer
options.readResource(resourcePath, function(err, buffer) {
if (err) return callback(err);
options.resourceBuffer = buffer; //獲得buffer
iterateNormalLoaders(options, loaderContext, [buffer], callback);
});
} else {
iterateNormalLoaders(options, loaderContext, [null], callback);
}
}
//倒序執行全部 loader
function iterateNormalLoaders(options, loaderContext, args, callback) {
if (loaderContext.loaderIndex < 0) return callback(null, args); //執行完全部 loader 後退出,去執行 iteratePitchingLoaders 回調即 runLoaders 的回調
var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; //獲取對應 loader
// iterate 減序後遞歸執行下一個 loader
if (currentLoaderObject.normalExecuted) {
loaderContext.loaderIndex--;
return iterateNormalLoaders(options, loaderContext, args, callback);
}
var fn = currentLoaderObject.normal;
currentLoaderObject.normalExecuted = true;
if (!fn) {
return iterateNormalLoaders(options, loaderContext, args, callback);
}
convertArgs(args, currentLoaderObject.raw);
//執行 loader 函數
runSyncOrAsync(fn, loaderContext, args, function(err) {
//loader 執行結果的回調
if (err) return callback(err);
var args = Array.prototype.slice.call(arguments, 1); // arg:[] 爲 loader 轉換結果(字符串或者buffer+可能有的sourcemap)
iterateNormalLoaders(options, loaderContext, args, callback); //遞歸,並將轉換結果一併傳入
});
}
//同步或者異步執行 loader 函數
function runSyncOrAsync(fn, context, args, callback) {
var isSync = true;
var isDone = false;
var isError = false; // internal error
var reportedError = false;
//異步處理
context.async = function async() {
if (isDone) {
if (reportedError) return; // ignore
throw new Error('async(): The callback was already called.');
}
isSync = false;
return innerCallback;
};
// 異步後會執行此方法,loader 的結果會做爲參數傳導出來
var innerCallback = (context.callback = function() {
if (isDone) {
if (reportedError) return; // ignore
throw new Error('callback(): The callback was already called.');
}
isDone = true;
isSync = false;
try {
callback.apply(null, arguments); // arguments 爲 loader 結果,第一個值爲 null 第二個爲字符串或者 buffer,第三個爲 SourceMap
} catch (e) {
//...
}
});
try {
var result = (function LOADER_EXECUTION() {
return fn.apply(context, args); //執行 loader 函數,參數傳遞前一個 loader 的執行結果
})();
if (isSync) {
isDone = true;
if (result === undefined) return callback();
if (result && typeof result === 'object' && typeof result.then === 'function') {
return result.then(function(r) {
callback(null, r);
}, callback);
}
return callback(null, result);
}
} catch (e) {
//...
}
}
複製代碼
NormalModule
獲得初始化的 module
,而後在 build
過程當中先 run loader
處理源碼,獲得一個編譯後的字符串或 buffer
。run loader
的過程當中,先正序執行了每一個 loader
的 pitch
,而後倒序執行了每一個 loader
的 normal
。