StatsPrinter.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { HookMap, SyncWaterfallHook, SyncBailHook } = require("tapable");
  7. /** @template T @typedef {import("tapable").AsArray<T>} AsArray<T> */
  8. /** @typedef {import("tapable").Hook} Hook */
  9. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsAsset} StatsAsset */
  10. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunk} StatsChunk */
  11. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunkGroup} StatsChunkGroup */
  12. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsCompilation} StatsCompilation */
  13. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsModule} StatsModule */
  14. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleReason} StatsModuleReason */
  15. /**
  16. * @typedef {object} PrintedElement
  17. * @property {string} element
  18. * @property {string} content
  19. */
  20. /**
  21. * @typedef {object} KnownStatsPrinterContext
  22. * @property {string=} type
  23. * @property {StatsCompilation=} compilation
  24. * @property {StatsChunkGroup=} chunkGroup
  25. * @property {StatsAsset=} asset
  26. * @property {StatsModule=} module
  27. * @property {StatsChunk=} chunk
  28. * @property {StatsModuleReason=} moduleReason
  29. * @property {(str: string) => string=} bold
  30. * @property {(str: string) => string=} yellow
  31. * @property {(str: string) => string=} red
  32. * @property {(str: string) => string=} green
  33. * @property {(str: string) => string=} magenta
  34. * @property {(str: string) => string=} cyan
  35. * @property {(file: string, oversize?: boolean) => string=} formatFilename
  36. * @property {(id: string) => string=} formatModuleId
  37. * @property {(id: string, direction?: "parent"|"child"|"sibling") => string=} formatChunkId
  38. * @property {(size: number) => string=} formatSize
  39. * @property {(dateTime: number) => string=} formatDateTime
  40. * @property {(flag: string) => string=} formatFlag
  41. * @property {(time: number, boldQuantity?: boolean) => string=} formatTime
  42. * @property {string=} chunkGroupKind
  43. */
  44. /** @typedef {KnownStatsPrinterContext & Record<string, any>} StatsPrinterContext */
  45. class StatsPrinter {
  46. constructor() {
  47. this.hooks = Object.freeze({
  48. /** @type {HookMap<SyncBailHook<[string[], StatsPrinterContext], true>>} */
  49. sortElements: new HookMap(
  50. () => new SyncBailHook(["elements", "context"])
  51. ),
  52. /** @type {HookMap<SyncBailHook<[PrintedElement[], StatsPrinterContext], string>>} */
  53. printElements: new HookMap(
  54. () => new SyncBailHook(["printedElements", "context"])
  55. ),
  56. /** @type {HookMap<SyncBailHook<[any[], StatsPrinterContext], true>>} */
  57. sortItems: new HookMap(() => new SyncBailHook(["items", "context"])),
  58. /** @type {HookMap<SyncBailHook<[any, StatsPrinterContext], string>>} */
  59. getItemName: new HookMap(() => new SyncBailHook(["item", "context"])),
  60. /** @type {HookMap<SyncBailHook<[string[], StatsPrinterContext], string>>} */
  61. printItems: new HookMap(
  62. () => new SyncBailHook(["printedItems", "context"])
  63. ),
  64. /** @type {HookMap<SyncBailHook<[{}, StatsPrinterContext], string>>} */
  65. print: new HookMap(() => new SyncBailHook(["object", "context"])),
  66. /** @type {HookMap<SyncWaterfallHook<[string, StatsPrinterContext]>>} */
  67. result: new HookMap(() => new SyncWaterfallHook(["result", "context"]))
  68. });
  69. /** @type {Map<HookMap<Hook>, Map<string, Hook[]>>} */
  70. this._levelHookCache = new Map();
  71. this._inPrint = false;
  72. }
  73. /**
  74. * get all level hooks
  75. * @private
  76. * @template {Hook} T
  77. * @param {HookMap<T>} hookMap HookMap
  78. * @param {string} type type
  79. * @returns {T[]} hooks
  80. */
  81. _getAllLevelHooks(hookMap, type) {
  82. let cache = /** @type {Map<string, T[]>} */ (
  83. this._levelHookCache.get(hookMap)
  84. );
  85. if (cache === undefined) {
  86. cache = new Map();
  87. this._levelHookCache.set(hookMap, cache);
  88. }
  89. const cacheEntry = cache.get(type);
  90. if (cacheEntry !== undefined) {
  91. return cacheEntry;
  92. }
  93. /** @type {T[]} */
  94. const hooks = [];
  95. const typeParts = type.split(".");
  96. for (let i = 0; i < typeParts.length; i++) {
  97. const hook = hookMap.get(typeParts.slice(i).join("."));
  98. if (hook) {
  99. hooks.push(hook);
  100. }
  101. }
  102. cache.set(type, hooks);
  103. return hooks;
  104. }
  105. /**
  106. * Run `fn` for each level
  107. * @private
  108. * @template T
  109. * @template R
  110. * @param {HookMap<SyncBailHook<T, R>>} hookMap HookMap
  111. * @param {string} type type
  112. * @param {(hook: SyncBailHook<T, R>) => R} fn function
  113. * @returns {R} result of `fn`
  114. */
  115. _forEachLevel(hookMap, type, fn) {
  116. for (const hook of this._getAllLevelHooks(hookMap, type)) {
  117. const result = fn(hook);
  118. if (result !== undefined) return result;
  119. }
  120. }
  121. /**
  122. * Run `fn` for each level
  123. * @private
  124. * @template T
  125. * @param {HookMap<SyncWaterfallHook<T>>} hookMap HookMap
  126. * @param {string} type type
  127. * @param {AsArray<T>[0]} data data
  128. * @param {(hook: SyncWaterfallHook<T>, data: AsArray<T>[0]) => AsArray<T>[0]} fn function
  129. * @returns {AsArray<T>[0]} result of `fn`
  130. */
  131. _forEachLevelWaterfall(hookMap, type, data, fn) {
  132. for (const hook of this._getAllLevelHooks(hookMap, type)) {
  133. data = fn(hook, data);
  134. }
  135. return data;
  136. }
  137. /**
  138. * @param {string} type The type
  139. * @param {object} object Object to print
  140. * @param {object=} baseContext The base context
  141. * @returns {string} printed result
  142. */
  143. print(type, object, baseContext) {
  144. if (this._inPrint) {
  145. return this._print(type, object, baseContext);
  146. } else {
  147. try {
  148. this._inPrint = true;
  149. return this._print(type, object, baseContext);
  150. } finally {
  151. this._levelHookCache.clear();
  152. this._inPrint = false;
  153. }
  154. }
  155. }
  156. /**
  157. * @private
  158. * @param {string} type type
  159. * @param {object} object object
  160. * @param {object=} baseContext context
  161. * @returns {string} printed result
  162. */
  163. _print(type, object, baseContext) {
  164. const context = {
  165. ...baseContext,
  166. type,
  167. [type]: object
  168. };
  169. let printResult = this._forEachLevel(this.hooks.print, type, hook =>
  170. hook.call(object, context)
  171. );
  172. if (printResult === undefined) {
  173. if (Array.isArray(object)) {
  174. const sortedItems = object.slice();
  175. this._forEachLevel(this.hooks.sortItems, type, h =>
  176. h.call(sortedItems, context)
  177. );
  178. const printedItems = sortedItems.map((item, i) => {
  179. const itemContext = {
  180. ...context,
  181. _index: i
  182. };
  183. const itemName = this._forEachLevel(
  184. this.hooks.getItemName,
  185. `${type}[]`,
  186. h => h.call(item, itemContext)
  187. );
  188. if (itemName) itemContext[itemName] = item;
  189. return this.print(
  190. itemName ? `${type}[].${itemName}` : `${type}[]`,
  191. item,
  192. itemContext
  193. );
  194. });
  195. printResult = this._forEachLevel(this.hooks.printItems, type, h =>
  196. h.call(printedItems, context)
  197. );
  198. if (printResult === undefined) {
  199. const result = printedItems.filter(Boolean);
  200. if (result.length > 0) printResult = result.join("\n");
  201. }
  202. } else if (object !== null && typeof object === "object") {
  203. const elements = Object.keys(object).filter(
  204. key => object[key] !== undefined
  205. );
  206. this._forEachLevel(this.hooks.sortElements, type, h =>
  207. h.call(elements, context)
  208. );
  209. const printedElements = elements.map(element => {
  210. const content = this.print(`${type}.${element}`, object[element], {
  211. ...context,
  212. _parent: object,
  213. _element: element,
  214. [element]: object[element]
  215. });
  216. return { element, content };
  217. });
  218. printResult = this._forEachLevel(this.hooks.printElements, type, h =>
  219. h.call(printedElements, context)
  220. );
  221. if (printResult === undefined) {
  222. const result = printedElements.map(e => e.content).filter(Boolean);
  223. if (result.length > 0) printResult = result.join("\n");
  224. }
  225. }
  226. }
  227. return this._forEachLevelWaterfall(
  228. this.hooks.result,
  229. type,
  230. printResult,
  231. (h, r) => h.call(r, context)
  232. );
  233. }
  234. }
  235. module.exports = StatsPrinter;