StatsPrinter.js 9.3 KB

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