StatsPrinter.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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. /** @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 | undefined} 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. /** @typedef {(value: string | number) => string} ColorFunction */
  42. /**
  43. * @typedef {object} KnownStatsPrinterColorFunctions
  44. * @property {ColorFunction=} bold
  45. * @property {ColorFunction=} yellow
  46. * @property {ColorFunction=} red
  47. * @property {ColorFunction=} green
  48. * @property {ColorFunction=} magenta
  49. * @property {ColorFunction=} cyan
  50. */
  51. /**
  52. * @typedef {object} KnownStatsPrinterFormatters
  53. * @property {(file: string, oversize?: boolean) => string=} formatFilename
  54. * @property {(id: string | number) => string=} formatModuleId
  55. * @property {(id: string | number, direction?: "parent" | "child" | "sibling") => string=} formatChunkId
  56. * @property {(size: number) => string=} formatSize
  57. * @property {(size: string) => string=} formatLayer
  58. * @property {(dateTime: number) => string=} formatDateTime
  59. * @property {(flag: string) => string=} formatFlag
  60. * @property {(time: number, boldQuantity?: boolean) => string=} formatTime
  61. * @property {(message: string) => string=} formatError
  62. */
  63. /** @typedef {KnownStatsPrinterColorFunctions & KnownStatsPrinterFormatters & KnownStatsPrinterContext & Record<string, EXPECTED_ANY>} StatsPrinterContext */
  64. /** @typedef {StatsPrinterContext & Required<KnownStatsPrinterColorFunctions> & Required<KnownStatsPrinterFormatters> & { type: string }} StatsPrinterContextWithExtra */
  65. /** @typedef {EXPECTED_ANY} PrintObject */
  66. /**
  67. * @typedef {object} StatsPrintHooks
  68. * @property {HookMap<SyncBailHook<[string[], StatsPrinterContext], void>>} sortElements
  69. * @property {HookMap<SyncBailHook<[PrintedElement[], StatsPrinterContext], string | undefined | void>>} printElements
  70. * @property {HookMap<SyncBailHook<[PrintObject[], StatsPrinterContext], boolean | void>>} sortItems
  71. * @property {HookMap<SyncBailHook<[PrintObject, StatsPrinterContext], string | void>>} getItemName
  72. * @property {HookMap<SyncBailHook<[string[], StatsPrinterContext], string | undefined>>} printItems
  73. * @property {HookMap<SyncBailHook<[PrintObject, StatsPrinterContext], string | undefined | void>>} print
  74. * @property {HookMap<SyncWaterfallHook<[string, StatsPrinterContext]>>} result
  75. */
  76. class StatsPrinter {
  77. constructor() {
  78. /** @type {StatsPrintHooks} */
  79. this.hooks = Object.freeze({
  80. sortElements: new HookMap(
  81. () => new SyncBailHook(["elements", "context"])
  82. ),
  83. printElements: new HookMap(
  84. () => new SyncBailHook(["printedElements", "context"])
  85. ),
  86. sortItems: new HookMap(() => new SyncBailHook(["items", "context"])),
  87. getItemName: new HookMap(() => new SyncBailHook(["item", "context"])),
  88. printItems: new HookMap(
  89. () => new SyncBailHook(["printedItems", "context"])
  90. ),
  91. print: new HookMap(() => new SyncBailHook(["object", "context"])),
  92. result: new HookMap(() => new SyncWaterfallHook(["result", "context"]))
  93. });
  94. /** @type {Map<StatsPrintHooks[keyof StatsPrintHooks], Map<string, import("tapable").Hook<EXPECTED_ANY, EXPECTED_ANY>[]>>} */
  95. this._levelHookCache = new Map();
  96. this._inPrint = false;
  97. }
  98. /**
  99. * get all level hooks
  100. * @private
  101. * @template {StatsPrintHooks[keyof StatsPrintHooks]} HM
  102. * @template {HM extends HookMap<infer H> ? H : never} H
  103. * @param {HM} hookMap hook map
  104. * @param {string} type type
  105. * @returns {H[]} hooks
  106. */
  107. _getAllLevelHooks(hookMap, type) {
  108. let cache = this._levelHookCache.get(hookMap);
  109. if (cache === undefined) {
  110. cache = new Map();
  111. this._levelHookCache.set(hookMap, cache);
  112. }
  113. const cacheEntry = cache.get(type);
  114. if (cacheEntry !== undefined) {
  115. return /** @type {H[]} */ (cacheEntry);
  116. }
  117. /** @type {H[]} */
  118. const hooks = [];
  119. const typeParts = type.split(".");
  120. for (let i = 0; i < typeParts.length; i++) {
  121. const hook = /** @type {H} */ (hookMap.get(typeParts.slice(i).join(".")));
  122. if (hook) {
  123. hooks.push(hook);
  124. }
  125. }
  126. cache.set(type, hooks);
  127. return hooks;
  128. }
  129. /**
  130. * Run `fn` for each level
  131. * @private
  132. * @template {StatsPrintHooks[keyof StatsPrintHooks]} HM
  133. * @template {HM extends HookMap<infer H> ? H : never} H
  134. * @template {H extends import("tapable").Hook<EXPECTED_ANY, infer R> ? R : never} R
  135. * @param {HM} hookMap hook map
  136. * @param {string} type type
  137. * @param {(hooK: H) => R | undefined | void} fn fn
  138. * @returns {R | undefined} hook
  139. */
  140. _forEachLevel(hookMap, type, fn) {
  141. for (const hook of this._getAllLevelHooks(hookMap, type)) {
  142. const result = fn(/** @type {H} */ (hook));
  143. if (result !== undefined) return /** @type {R} */ (result);
  144. }
  145. }
  146. /**
  147. * Run `fn` for each level
  148. * @private
  149. * @template {StatsPrintHooks[keyof StatsPrintHooks]} HM
  150. * @template {HM extends HookMap<infer H> ? H : never} H
  151. * @param {HM} hookMap hook map
  152. * @param {string} type type
  153. * @param {string} data data
  154. * @param {(hook: H, data: string) => string} fn fn
  155. * @returns {string | undefined} result of `fn`
  156. */
  157. _forEachLevelWaterfall(hookMap, type, data, fn) {
  158. for (const hook of this._getAllLevelHooks(hookMap, type)) {
  159. data = fn(/** @type {H} */ (hook), data);
  160. }
  161. return data;
  162. }
  163. /**
  164. * @param {string} type The type
  165. * @param {PrintObject} object Object to print
  166. * @param {StatsPrinterContext=} baseContext The base context
  167. * @returns {string | undefined} printed result
  168. */
  169. print(type, object, baseContext) {
  170. if (this._inPrint) {
  171. return this._print(type, object, baseContext);
  172. }
  173. try {
  174. this._inPrint = true;
  175. return this._print(type, object, baseContext);
  176. } finally {
  177. this._levelHookCache.clear();
  178. this._inPrint = false;
  179. }
  180. }
  181. /**
  182. * @private
  183. * @param {string} type type
  184. * @param {PrintObject} object object
  185. * @param {StatsPrinterContext=} baseContext context
  186. * @returns {string | undefined} printed result
  187. */
  188. _print(type, object, baseContext) {
  189. /** @type {StatsPrinterContext} */
  190. const context = {
  191. ...baseContext,
  192. type,
  193. [type]: object
  194. };
  195. /** @type {string | undefined} */
  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];
  202. this._forEachLevel(this.hooks.sortItems, type, h =>
  203. h.call(
  204. sortedItems,
  205. /** @type {StatsPrinterContextWithExtra} */
  206. (context)
  207. )
  208. );
  209. const printedItems = sortedItems.map((item, i) => {
  210. const itemContext =
  211. /** @type {StatsPrinterContextWithExtra} */
  212. ({
  213. ...context,
  214. _index: i
  215. });
  216. const itemName = this._forEachLevel(
  217. this.hooks.getItemName,
  218. `${type}[]`,
  219. h => h.call(item, itemContext)
  220. );
  221. if (itemName) itemContext[itemName] = item;
  222. return this.print(
  223. itemName ? `${type}[].${itemName}` : `${type}[]`,
  224. item,
  225. itemContext
  226. );
  227. });
  228. printResult = this._forEachLevel(this.hooks.printItems, type, h =>
  229. h.call(
  230. /** @type {string[]} */ (printedItems),
  231. /** @type {StatsPrinterContextWithExtra} */
  232. (context)
  233. )
  234. );
  235. if (printResult === undefined) {
  236. const result = printedItems.filter(Boolean);
  237. if (result.length > 0) printResult = result.join("\n");
  238. }
  239. } else if (object !== null && typeof object === "object") {
  240. const elements = Object.keys(object).filter(
  241. key => object[key] !== undefined
  242. );
  243. this._forEachLevel(this.hooks.sortElements, type, h =>
  244. h.call(
  245. elements,
  246. /** @type {StatsPrinterContextWithExtra} */
  247. (context)
  248. )
  249. );
  250. const printedElements = elements.map(element => {
  251. const content = this.print(`${type}.${element}`, object[element], {
  252. ...context,
  253. _parent: object,
  254. _element: element,
  255. [element]: object[element]
  256. });
  257. return { element, content };
  258. });
  259. printResult = this._forEachLevel(this.hooks.printElements, type, h =>
  260. h.call(
  261. printedElements,
  262. /** @type {StatsPrinterContextWithExtra} */
  263. (context)
  264. )
  265. );
  266. if (printResult === undefined) {
  267. const result = printedElements.map(e => e.content).filter(Boolean);
  268. if (result.length > 0) printResult = result.join("\n");
  269. }
  270. }
  271. }
  272. return this._forEachLevelWaterfall(
  273. this.hooks.result,
  274. type,
  275. /** @type {string} */
  276. (printResult),
  277. (h, r) => h.call(r, /** @type {StatsPrinterContextWithExtra} */ (context))
  278. );
  279. }
  280. }
  281. module.exports = StatsPrinter;