loaderContext上有些属性通过Object.defineProperty定义,是因为这些是动态属性,每次去取值时都不一样,例如:
Object.defineProperty(loaderContext, 'remainingRequest', {get() {//request = 所有的loader!要加载的模块return loaderContext.loaders.slice(loaderContext.loaderIndex + 1).map(loader => loader.path).concat(loaderContext.resource).join('!')}});
源码中,loader的pitch处理完之后,会再次进入iteratePitchLoaders递归执行,然后再判断currentLoaderObject.pitchExecuted,索引增加,即每个loader进两次iteratePitchLoaders方法
function iteratePitchLoaders(options, loaderContext, runLoadersCallback) {//获取当前的索引对应的loaderlet currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];//如果说当前的loader已经执行过了,则执行下一个loader对应的pitchif (currentLoaderObject.pitchExecuted) {loaderContext.loaderIndex++;return iteratePitchLoaders(options, loaderContext, runLoadersCallback);}loadLoader(currentLoaderObject, () => {let fn = currentLoaderObject.pitch;//获取当前的loader的pitch函数currentLoaderObject.pitchExecuted = true;//表示当前的loader的pitch函数已经执行过了if (!fn) {//如果当前的loader没有pitch,直接执行下一个loader的pitch方法return iteratePitchLoaders(options, loaderContext, runLoadersCallback);}})}
但其实,也可以写成
function iteratePitchLoaders(options, loaderContext, runLoadersCallback) {//获取当前的索引对应的loaderlet currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];loadLoader(currentLoaderObject, () => {let fn = currentLoaderObject.pitch;//获取当前的loader的pitch函数if (!fn) {//如果当前的loader没有pitch,直接执行下一个loader的pitch方法loaderContext.loaderIndex++;return iteratePitchLoaders(options, loaderContext, runLoadersCallback);}})}
loader有同步的,有异步的,如果是异步的,loader内部需要调用async方法,async方法的执行会将runSyncOrAsync里的isSync改成false,并直接返回undefined,在异步处理完后再调用loaderContext的callback回调,在callback中,会继续执行下一个loader或最终的处理
/*** 把loader转成loader对象* @param {*} loader loader的绝对路径*/function createLoaderObject(loader) {let module = require(loader);let normal = module;let pitch = module.pitch;let raw = module.raw;let loaderObject = {path: loader, //loader的绝对路径normal,//loader的normal函数pitch, //loader的pitch函数raw,//是否要转成Buffer, raw=true参数就要转成Buffer,raw=false参数就要转成字符串,file-loader和url-loader会用到data: {},//每个load都有一个自己的data自定义对象,用来可以存放一些自定义的数据pitchExecuted: false,//此loader的pitch方法是否已经执行过了normalExecuted: false,//此loader的normal方法是否已经执行过了}return loaderObject;}function iterateNormalLoaders(options, loaderContext, args, runLoadersCallback) {if (loaderContext.loaderIndex < 0) {return runLoadersCallback(null, ...args);}let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];//获取当前的索引对应的loaderif (currentLoaderObject.normalExecuted) {//如果说当前的loader已经执行过了,则执行下一个loader对应的pitchloaderContext.loaderIndex--;return iterateNormalLoaders(options, loaderContext,args, runLoadersCallback);}let fn = currentLoaderObject.normal;//获取当前的loader的normal函数,如果loader是合法的,那么normal方法就一定存在currentLoaderObject.normalExecuted = true;//表示当前的loader的normal函数已经执行过了// 如果这个loader有normal方法//fn可以同步也可以异步convertArgs(args, currentLoaderObject.raw);runSyncOrAsync(fn, loaderContext, args, (err, ...args) => {//args可能有值,也可有没有值,可能有一个值,也可能有多个值if (err) return runLoadersCallback(err);return iterateNormalLoaders(options, loaderContext, args, runLoadersCallback);});}function convertArgs(args, raw) {if (raw && !Buffer.isBuffer(args[0])) {args[0] = Buffer.from(args[0]);//如果这个normal函数想要buffer,但是参数不是Buffer} else if (!raw && Buffer.isBuffer(args[0])) {//想要字符串,但是是个bufferargs[0] = args[0].toString('utf8');}}function processResource(options, loaderContext, runLoadersCallback) {options.readResource(loaderContext.resource, (err, buffer) => {loaderContext.loaderIndex = loaderContext.loaders.length - 1;options.resourceBuffer = buffer;//要加载的文件的原始文件内容iterateNormalLoaders(options, loaderContext, [buffer], runLoadersCallback);});}function iteratePitchLoaders(options, loaderContext, runLoadersCallback) {//如果当前索引已经 大于等loader的数量了,则表示所有的loader pitch执行完了if (loaderContext.loaderIndex >= loaderContext.loaders.length) {return processResource(options, loaderContext, runLoadersCallback);}let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];//获取当前的索引对应的loaderif (currentLoaderObject.pitchExecuted) {//如果说当前的loader已经执行过了,则执行下一个loader对应的pitchloaderContext.loaderIndex++;return iteratePitchLoaders(options, loaderContext, runLoadersCallback);}let fn = currentLoaderObject.pitch;//获取当前的loader的pitch函数currentLoaderObject.pitchExecuted = true;//表示当前的loader的pitch函数已经执行过了if (!fn) {//如果当前的loader没有pitch,直接执行下一个loader的pitch方法return iteratePitchLoaders(options, loaderContext, runLoadersCallback);}// 如果这个loader有pitch方法//fn可以同步也可以异步runSyncOrAsync(fn, loaderContext, [loaderContext.remainingRequest, loaderContext.previousRequest, loaderContext.data], (err, ...args) => {//args可能有值,也可有没有值,可能有一个值,也可能有多个值if (args.length > 0 && args.some(item => !!item)) {//有任何一个有值就可以//跳过后续的pitch和读文件,直接掉头执行前一个loader的normalloaderContext.loaderIndex--;iterateNormalLoaders(processOptions, loaderContext, returnArgs, pitchingCallback);} else {return iteratePitchLoaders(options, loaderContext, runLoadersCallback);}});}/*** 以同步或者异步的方式执行fn* context.fn(args);callback()* @param {*} fn 要执行的函数* @param {*} context fn执行的时候的this指针* @param {*} args 传递给fn的参数* @param {*} callback fn执行完成后的回调*/function runSyncOrAsync(fn, context, args, callback) {let isSync = true;//默认是同步执行context.callback = (...args) => {callback(null, ...args);}context.async = () => {isSync = false;//把同步变成异步return context.callback;}var result = fn.apply(context, args);//用指定的参数和this对象执行函数,返回一个结果// isSync为true表示是同步,同步意味着执行完当前函数后,会直接自动执行callback回调// isSync为false表示是同步,那意味着扫行当前函数后什么都不做了if (isSync) {if (result === undefined) {return callback();} else {return callback(null, result);}}}function runLoaders(options, finalCallback) {let resource = options.resource;//加载的文件 src\index.jslet loaders = options.loaders || [];//let loaderContext = options.context || {};//loader函数执行时的上下文对象let readResource = options.readResource || fs.readFile;//用来读加载的文件的方法let loaderObjects = loaders.map(createLoaderObject);loaderContext.resource = resource;//加载的文件loaderContext.readResource = readResource;loaderContext.loaders = loaderObjects;loaderContext.loaderIndex = 0;//当前正在执行的loader的索引loaderContext.callback = null;//后面在执行 loader的时候会赋值 返回多个值loaderContext.async = null;//后面在执行 loader的时候会赋值 把loader的执行从同步变成异步Object.defineProperty(loaderContext, 'request', {get() {//request = 所有的loader!要加载的模块return loaderContext.loaders.map(l = l.path).concat(loaderContext.resource).join('!')}});Object.defineProperty(loaderContext, 'remainingRequest', {get() {//request = 所有的loader!要加载的模块return loaderContext.loaders.slice(loaderContext.loaderIndex + 1).map(loader => loader.path).concat(loaderContext.resource).join('!')}});Object.defineProperty(loaderContext, 'currentRequest', {get() {//request = 所有的loader!要加载的模块 currentRequest 不是仅仅自己,而包括自己和后续的return loaderContext.loaders.slice(loaderContext.loaderIndex).map(loader => loader.path).concat(loaderContext.resource).join('!')}});Object.defineProperty(loaderContext, 'previousRequest', {get() {//从第一个loader到当前的loader,不包含当前的loaderreturn loaderContext.loaders.slice(0, loaderContext.loaderIndex).map(loader => loader.path).concat(loaderContext.resource).join('!')}});Object.defineProperty(loaderContext, 'data', {get() {//从第一个loader到当前的loader,不包含当前的loaderreturn loaderContext.loaders[loaderContext.loaderIndex].data;}});let processOptions = {resourceBuffer: null,//存放原始内容对应的Buffer 用loader转换前的内容BufferreadResource //fs.readFile}//开始从左往后执行每个loader的pitch方法iteratePitchLoaders(processOptions, loaderContext, (err, result) => {finalCallback(err, {result,resourceBuffer: processOptions.resourceBuffer});});}exports.runLoaders = runLoaders;/**['post1-loader',//loader的绝对路径'post2-loader','inline1-loader','inline2-loader','normal1-loader','normal2-loader','pre1-loader','pre2-loader']*/
