StatsPrinter.js 9.9 KB

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