原文首发于 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
。