StatsPrinter.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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 | 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. /**
  95. * @type {TODO}
  96. */
  97. this._levelHookCache = new Map();
  98. this._inPrint = false;
  99. }
  100. /**
  101. * get all level hooks
  102. * @private
  103. * @template {StatsPrintHooks[keyof StatsPrintHooks]} HM
  104. * @template {HM extends HookMap<infer H> ? H : never} H
  105. * @param {HM} hookMap hook map
  106. * @param {string} type type
  107. * @returns {H[]} hooks
  108. */
  109. _getAllLevelHooks(hookMap, type) {
  110. let cache = this._levelHookCache.get(hookMap);
  111. if (cache === undefined) {
  112. cache = new Map();
  113. this._levelHookCache.set(hookMap, cache);
  114. }
  115. const cacheEntry = cache.get(type);
  116. if (cacheEntry !== undefined) {
  117. return cacheEntry;
  118. }
  119. /** @type {H[]} */
  120. const hooks = [];
  121. const typeParts = type.split(".");
  122. for (let i = 0; i < typeParts.length; i++) {
  123. const hook = /** @type {H} */ (hookMap.get(typeParts.slice(i).join(".")));
  124. if (hook) {
  125. hooks.push(hook);
  126. }
  127. }
  128. cache.set(type, hooks);
  129. return hooks;
  130. }
  131. /**
  132. * Run `fn` for each level
  133. * @private
  134. * @template {StatsPrintHooks[keyof StatsPrintHooks]} HM
  135. * @template {HM extends HookMap<infer H> ? H : never} H
  136. * @template {H extends import("tapable").Hook<any, infer R> ? R : never} R
  137. * @param {HM} hookMap hook map
  138. * @param {string} type type
  139. * @param {(hooK: H) => R | undefined | void} fn fn
  140. * @returns {R | undefined} hook
  141. */
  142. _forEachLevel(hookMap, type, fn) {
  143. for (const hook of this._getAllLevelHooks(hookMap, type)) {
  144. const result = fn(/** @type {H} */ (hook));
  145. if (result !== undefined) return /** @type {R} */ (result);
  146. }
  147. }
  148. /**
  149. * Run `fn` for each level
  150. * @private
  151. * @template {StatsPrintHooks[keyof StatsPrintHooks]} HM
  152. * @template {HM extends HookMap<infer H> ? H : never} H
  153. * @param {HM} hookMap hook map
  154. * @param {string} type type
  155. * @param {string} data data
  156. * @param {(hook: H, data: string) => string} fn fn
  157. * @returns {string | undefined} result of `fn`
  158. */
  159. _forEachLevelWaterfall(hookMap, type, data, fn) {
  160. for (const hook of this._getAllLevelHooks(hookMap, type)) {
  161. data = fn(/** @type {H} */ (hook), data);
  162. }
  163. return data;
  164. }
  165. /**
  166. * @param {string} type The type
  167. * @param {PrintObject} object Object to print
  168. * @param {StatsPrinterContext=} baseContext The base context
  169. * @returns {string | undefined} printed result
  170. */
  171. print(type, object, baseContext) {
  172. if (this._inPrint) {
  173. return this._print(type, object, baseContext);
  174. }
  175. try {
  176. this._inPrint = true;
  177. return this._print(type, object, baseContext);
  178. } finally {
  179. this._levelHookCache.clear();
  180. this._inPrint = false;
  181. }
  182. }
  183. /**
  184. * @private
  185. * @param {string} type type
  186. * @param {PrintObject} object object
  187. * @param {StatsPrinterContext=} baseContext context
  188. * @returns {string | undefined} printed result
  189. */
  190. _print(type, object, baseContext) {
  191. /** @type {StatsPrinterContext} */
  192. const context = {
  193. ...baseContext,
  194. type,
  195. [type]: object
  196. };
  197. /** @type {string | undefined} */
  198. let printResult = this._forEachLevel(this.hooks.print, type, hook =>
  199. hook.call(object, context)
  200. );
  201. if (printResult === undefined) {
  202. if (Array.isArray(object)) {
  203. const sortedItems = object.slice();
  204. this._forEachLevel(this.hooks.sortItems, type, h =>
  205. h.call(
  206. sortedItems,
  207. /** @type {StatsPrinterContextWithExtra} */
  208. (context)
  209. )
  210. );
  211. const printedItems = sortedItems.map((item, i) => {
  212. const itemContext =
  213. /** @type {StatsPrinterContextWithExtra} */
  214. ({
  215. ...context,
  216. _index: i
  217. });
  218. const itemName = this._forEachLevel(
  219. this.hooks.getItemName,
  220. `${type}[]`,
  221. h => h.call(item, itemContext)
  222. );
  223. if (itemName) itemContext[itemName] = item;
  224. return this.print(
  225. itemName ? `${type}[].${itemName}` : `${type}[]`,
  226. item,
  227. itemContext
  228. );
  229. });
  230. printResult = this._forEachLevel(this.hooks.printItems, type, h =>
  231. h.call(
  232. /** @type {string[]} */ (printedItems),
  233. /** @type {StatsPrinterContextWithExtra} */
  234. (context)
  235. )
  236. );
  237. if (printResult === undefined) {
  238. const result = printedItems.filter(Boolean);
  239. if (result.length > 0) printResult = result.join("\n");
  240. }
  241. } else if (object !== null && typeof object === "object") {
  242. const elements = Object.keys(object).filter(
  243. key => object[key] !== undefined
  244. );
  245. this._forEachLevel(this.hooks.sortElements, type, h =>
  246. h.call(
  247. elements,
  248. /** @type {StatsPrinterContextWithExtra} */
  249. (context)
  250. )
  251. );
  252. const printedElements = elements.map(element => {
  253. const content = this.print(`${type}.${element}`, object[element], {
  254. ...context,
  255. _parent: object,
  256. _element: element,
  257. [element]: object[element]
  258. });
  259. return { element, content };
  260. });
  261. printResult = this._forEachLevel(this.hooks.printElements, type, h =>
  262. h.call(
  263. printedElements,
  264. /** @type {StatsPrinterContextWithExtra} */
  265. (context)
  266. )
  267. );
  268. if (printResult === undefined) {
  269. const result = printedElements.map(e => e.content).filter(Boolean);
  270. if (result.length > 0) printResult = result.join("\n");
  271. }
  272. }
  273. }
  274. return this._forEachLevelWaterfall(
  275. this.hooks.result,
  276. type,
  277. /** @type {string} */
  278. (printResult),
  279. (h, r) => h.call(r, /** @type {StatsPrinterContextWithExtra} */ (context))
  280. );
  281. }
  282. }
  283. module.exports = StatsPrinter;