loaderContext上有些属性通过Object.defineProperty定义,是因为这些是动态属性,每次去取值时都不一样,例如:

    1. Object.defineProperty(loaderContext, 'remainingRequest', {
    2. get() {//request = 所有的loader!要加载的模块
    3. return loaderContext.loaders.slice(loaderContext.loaderIndex + 1).map(loader => loader.path).concat(loaderContext.resource).join('!')
    4. }
    5. });

    源码中,loader的pitch处理完之后,会再次进入iteratePitchLoaders递归执行,然后再判断currentLoaderObject.pitchExecuted,索引增加,即每个loader进两次iteratePitchLoaders方法

    1. function iteratePitchLoaders(options, loaderContext, runLoadersCallback) {
    2. //获取当前的索引对应的loader
    3. let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];
    4. //如果说当前的loader已经执行过了,则执行下一个loader对应的pitch
    5. if (currentLoaderObject.pitchExecuted) {
    6. loaderContext.loaderIndex++;
    7. return iteratePitchLoaders(options, loaderContext, runLoadersCallback);
    8. }
    9. loadLoader(currentLoaderObject, () => {
    10. let fn = currentLoaderObject.pitch;//获取当前的loader的pitch函数
    11. currentLoaderObject.pitchExecuted = true;//表示当前的loader的pitch函数已经执行过了
    12. if (!fn) {//如果当前的loader没有pitch,直接执行下一个loader的pitch方法
    13. return iteratePitchLoaders(options, loaderContext, runLoadersCallback);
    14. }
    15. })
    16. }

    但其实,也可以写成

    1. function iteratePitchLoaders(options, loaderContext, runLoadersCallback) {
    2. //获取当前的索引对应的loader
    3. let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];
    4. loadLoader(currentLoaderObject, () => {
    5. let fn = currentLoaderObject.pitch;//获取当前的loader的pitch函数
    6. if (!fn) {//如果当前的loader没有pitch,直接执行下一个loader的pitch方法
    7. loaderContext.loaderIndex++;
    8. return iteratePitchLoaders(options, loaderContext, runLoadersCallback);
    9. }
    10. })
    11. }

    loader有同步的,有异步的,如果是异步的,loader内部需要调用async方法,async方法的执行会将runSyncOrAsync里的isSync改成false,并直接返回undefined,在异步处理完后再调用loaderContext的callback回调,在callback中,会继续执行下一个loader或最终的处理

    1. /**
    2. * 把loader转成loader对象
    3. * @param {*} loader loader的绝对路径
    4. */
    5. function createLoaderObject(loader) {
    6. let module = require(loader);
    7. let normal = module;
    8. let pitch = module.pitch;
    9. let raw = module.raw;
    10. let loaderObject = {
    11. path: loader, //loader的绝对路径
    12. normal,//loader的normal函数
    13. pitch, //loader的pitch函数
    14. raw,//是否要转成Buffer, raw=true参数就要转成Buffer,raw=false参数就要转成字符串,file-loader和url-loader会用到
    15. data: {},//每个load都有一个自己的data自定义对象,用来可以存放一些自定义的数据
    16. pitchExecuted: false,//此loader的pitch方法是否已经执行过了
    17. normalExecuted: false,//此loader的normal方法是否已经执行过了
    18. }
    19. return loaderObject;
    20. }
    21. function iterateNormalLoaders(options, loaderContext, args, runLoadersCallback) {
    22. if (loaderContext.loaderIndex < 0) {
    23. return runLoadersCallback(null, ...args);
    24. }
    25. let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];//获取当前的索引对应的loader
    26. if (currentLoaderObject.normalExecuted) {//如果说当前的loader已经执行过了,则执行下一个loader对应的pitch
    27. loaderContext.loaderIndex--;
    28. return iterateNormalLoaders(options, loaderContext,args, runLoadersCallback);
    29. }
    30. let fn = currentLoaderObject.normal;//获取当前的loader的normal函数,如果loader是合法的,那么normal方法就一定存在
    31. currentLoaderObject.normalExecuted = true;//表示当前的loader的normal函数已经执行过了
    32. // 如果这个loader有normal方法
    33. //fn可以同步也可以异步
    34. convertArgs(args, currentLoaderObject.raw);
    35. runSyncOrAsync(fn, loaderContext, args, (err, ...args) => {//args可能有值,也可有没有值,可能有一个值,也可能有多个值
    36. if (err) return runLoadersCallback(err);
    37. return iterateNormalLoaders(options, loaderContext, args, runLoadersCallback);
    38. });
    39. }
    40. function convertArgs(args, raw) {
    41. if (raw && !Buffer.isBuffer(args[0])) {
    42. args[0] = Buffer.from(args[0]);//如果这个normal函数想要buffer,但是参数不是Buffer
    43. } else if (!raw && Buffer.isBuffer(args[0])) {//想要字符串,但是是个buffer
    44. args[0] = args[0].toString('utf8');
    45. }
    46. }
    47. function processResource(options, loaderContext, runLoadersCallback) {
    48. options.readResource(loaderContext.resource, (err, buffer) => {
    49. loaderContext.loaderIndex = loaderContext.loaders.length - 1;
    50. options.resourceBuffer = buffer;//要加载的文件的原始文件内容
    51. iterateNormalLoaders(options, loaderContext, [buffer], runLoadersCallback);
    52. });
    53. }
    54. function iteratePitchLoaders(options, loaderContext, runLoadersCallback) {
    55. //如果当前索引已经 大于等loader的数量了,则表示所有的loader pitch执行完了
    56. if (loaderContext.loaderIndex >= loaderContext.loaders.length) {
    57. return processResource(options, loaderContext, runLoadersCallback);
    58. }
    59. let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];//获取当前的索引对应的loader
    60. if (currentLoaderObject.pitchExecuted) {//如果说当前的loader已经执行过了,则执行下一个loader对应的pitch
    61. loaderContext.loaderIndex++;
    62. return iteratePitchLoaders(options, loaderContext, runLoadersCallback);
    63. }
    64. let fn = currentLoaderObject.pitch;//获取当前的loader的pitch函数
    65. currentLoaderObject.pitchExecuted = true;//表示当前的loader的pitch函数已经执行过了
    66. if (!fn) {//如果当前的loader没有pitch,直接执行下一个loader的pitch方法
    67. return iteratePitchLoaders(options, loaderContext, runLoadersCallback);
    68. }
    69. // 如果这个loader有pitch方法
    70. //fn可以同步也可以异步
    71. runSyncOrAsync(fn, loaderContext, [
    72. loaderContext.remainingRequest, loaderContext.previousRequest, loaderContext.data
    73. ], (err, ...args) => {//args可能有值,也可有没有值,可能有一个值,也可能有多个值
    74. if (args.length > 0 && args.some(item => !!item)) {//有任何一个有值就可以
    75. //跳过后续的pitch和读文件,直接掉头执行前一个loader的normal
    76. loaderContext.loaderIndex--;
    77. iterateNormalLoaders(processOptions, loaderContext, returnArgs, pitchingCallback);
    78. } else {
    79. return iteratePitchLoaders(options, loaderContext, runLoadersCallback);
    80. }
    81. });
    82. }
    83. /**
    84. * 以同步或者异步的方式执行fn
    85. * context.fn(args);callback()
    86. * @param {*} fn 要执行的函数
    87. * @param {*} context fn执行的时候的this指针
    88. * @param {*} args 传递给fn的参数
    89. * @param {*} callback fn执行完成后的回调
    90. */
    91. function runSyncOrAsync(fn, context, args, callback) {
    92. let isSync = true;//默认是同步执行
    93. context.callback = (...args) => {
    94. callback(null, ...args);
    95. }
    96. context.async = () => {
    97. isSync = false;//把同步变成异步
    98. return context.callback;
    99. }
    100. var result = fn.apply(context, args);//用指定的参数和this对象执行函数,返回一个结果
    101. // isSync为true表示是同步,同步意味着执行完当前函数后,会直接自动执行callback回调
    102. // isSync为false表示是同步,那意味着扫行当前函数后什么都不做了
    103. if (isSync) {
    104. if (result === undefined) {
    105. return callback();
    106. } else {
    107. return callback(null, result);
    108. }
    109. }
    110. }
    111. function runLoaders(options, finalCallback) {
    112. let resource = options.resource;//加载的文件 src\index.js
    113. let loaders = options.loaders || [];//
    114. let loaderContext = options.context || {};//loader函数执行时的上下文对象
    115. let readResource = options.readResource || fs.readFile;//用来读加载的文件的方法
    116. let loaderObjects = loaders.map(createLoaderObject);
    117. loaderContext.resource = resource;//加载的文件
    118. loaderContext.readResource = readResource;
    119. loaderContext.loaders = loaderObjects;
    120. loaderContext.loaderIndex = 0;//当前正在执行的loader的索引
    121. loaderContext.callback = null;//后面在执行 loader的时候会赋值 返回多个值
    122. loaderContext.async = null;//后面在执行 loader的时候会赋值 把loader的执行从同步变成异步
    123. Object.defineProperty(loaderContext, 'request', {
    124. get() {//request = 所有的loader!要加载的模块
    125. return loaderContext.loaders.map(l = l.path).concat(loaderContext.resource).join('!')
    126. }
    127. });
    128. Object.defineProperty(loaderContext, 'remainingRequest', {
    129. get() {//request = 所有的loader!要加载的模块
    130. return loaderContext.loaders.slice(loaderContext.loaderIndex + 1).map(loader => loader.path).concat(loaderContext.resource).join('!')
    131. }
    132. });
    133. Object.defineProperty(loaderContext, 'currentRequest', {
    134. get() {//request = 所有的loader!要加载的模块 currentRequest 不是仅仅自己,而包括自己和后续的
    135. return loaderContext.loaders.slice(loaderContext.loaderIndex).map(loader => loader.path).concat(loaderContext.resource).join('!')
    136. }
    137. });
    138. Object.defineProperty(loaderContext, 'previousRequest', {
    139. get() {//从第一个loader到当前的loader,不包含当前的loader
    140. return loaderContext.loaders.slice(0, loaderContext.loaderIndex).map(loader => loader.path).concat(loaderContext.resource).join('!')
    141. }
    142. });
    143. Object.defineProperty(loaderContext, 'data', {
    144. get() {//从第一个loader到当前的loader,不包含当前的loader
    145. return loaderContext.loaders[loaderContext.loaderIndex].data;
    146. }
    147. });
    148. let processOptions = {
    149. resourceBuffer: null,//存放原始内容对应的Buffer 用loader转换前的内容Buffer
    150. readResource //fs.readFile
    151. }
    152. //开始从左往后执行每个loader的pitch方法
    153. iteratePitchLoaders(processOptions, loaderContext, (err, result) => {
    154. finalCallback(
    155. err, {
    156. result,
    157. resourceBuffer: processOptions.resourceBuffer
    158. }
    159. );
    160. });
    161. }
    162. exports.runLoaders = runLoaders;
    163. /**
    164. [
    165. 'post1-loader',//loader的绝对路径
    166. 'post2-loader',
    167. 'inline1-loader',
    168. 'inline2-loader',
    169. 'normal1-loader',
    170. 'normal2-loader',
    171. 'pre1-loader',
    172. 'pre2-loader'
    173. ]
    174. */