StatsFactory.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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. const { concatComparators, keepOriginalOrder } = require("../util/comparators");
  8. const smartGrouping = require("../util/smartGrouping");
  9. /** @typedef {import("../Chunk")} Chunk */
  10. /** @typedef {import("../ChunkGroup").OriginRecord} OriginRecord */
  11. /** @typedef {import("../Compilation")} Compilation */
  12. /** @typedef {import("../Compilation").Asset} Asset */
  13. /** @typedef {import("../Compilation").NormalizedStatsOptions} NormalizedStatsOptions */
  14. /** @typedef {import("../Dependency")} Dependency */
  15. /** @typedef {import("../Module")} Module */
  16. /** @typedef {import("../ModuleGraph").ModuleProfile} ModuleProfile */
  17. /** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */
  18. /** @typedef {import("../WebpackError")} WebpackError */
  19. /** @typedef {import("../util/comparators").Comparator<EXPECTED_ANY>} Comparator */
  20. /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
  21. /**
  22. * Defines the group config type used by this module.
  23. * @template T, R
  24. * @typedef {import("../util/smartGrouping").GroupConfig<T, R>} GroupConfig
  25. */
  26. /** @typedef {import("./DefaultStatsFactoryPlugin").ChunkGroupInfoWithName} ChunkGroupInfoWithName */
  27. /** @typedef {import("./DefaultStatsFactoryPlugin").ModuleIssuerPath} ModuleIssuerPath */
  28. /** @typedef {import("./DefaultStatsFactoryPlugin").ModuleTrace} ModuleTrace */
  29. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsAsset} StatsAsset */
  30. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunk} StatsChunk */
  31. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunkGroup} StatsChunkGroup */
  32. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunkOrigin} StatsChunkOrigin */
  33. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsCompilation} StatsCompilation */
  34. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsError} StatsError */
  35. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsModule} StatsModule */
  36. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleIssuer} StatsModuleIssuer */
  37. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleReason} StatsModuleReason */
  38. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleTraceDependency} StatsModuleTraceDependency */
  39. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleTraceItem} StatsModuleTraceItem */
  40. /** @typedef {import("./DefaultStatsFactoryPlugin").StatsProfile} StatsProfile */
  41. /**
  42. * Defines the known stats factory context type used by this module.
  43. * @typedef {object} KnownStatsFactoryContext
  44. * @property {string} type
  45. * @property {Compilation} compilation
  46. * @property {(path: string) => string} makePathsRelative
  47. * @property {Set<Module>} rootModules
  48. * @property {Map<string, Chunk[]>} compilationFileToChunks
  49. * @property {Map<string, Chunk[]>} compilationAuxiliaryFileToChunks
  50. * @property {RuntimeSpec} runtime
  51. * @property {(compilation: Compilation) => Error[]} cachedGetErrors
  52. * @property {(compilation: Compilation) => Error[]} cachedGetWarnings
  53. */
  54. /** @typedef {KnownStatsFactoryContext & Record<string, EXPECTED_ANY>} StatsFactoryContext */
  55. // StatsLogging StatsLoggingEntry
  56. /**
  57. * Defines the stats object type used by this module.
  58. * @template T
  59. * @template F
  60. * @typedef {T extends Compilation ? StatsCompilation : T extends ChunkGroupInfoWithName ? StatsChunkGroup : T extends Chunk ? StatsChunk : T extends OriginRecord ? StatsChunkOrigin : T extends Module ? StatsModule : T extends ModuleGraphConnection ? StatsModuleReason : T extends Asset ? StatsAsset : T extends ModuleTrace ? StatsModuleTraceItem : T extends Dependency ? StatsModuleTraceDependency : T extends Error ? StatsError : T extends ModuleProfile ? StatsProfile : F} StatsObject
  61. */
  62. /**
  63. * Defines the created object type used by this module.
  64. * @template T
  65. * @template F
  66. * @typedef {T extends ChunkGroupInfoWithName[] ? Record<string, StatsObject<ChunkGroupInfoWithName, F>> : T extends (infer V)[] ? StatsObject<V, F>[] : StatsObject<T, F>} CreatedObject
  67. */
  68. /** @typedef {EXPECTED_ANY} ObjectForExtract */
  69. /** @typedef {EXPECTED_ANY} FactoryData */
  70. /** @typedef {EXPECTED_ANY} FactoryDataItem */
  71. /** @typedef {EXPECTED_ANY} Result */
  72. /**
  73. * Defines the stats factory hooks type used by this module.
  74. * @typedef {object} StatsFactoryHooks
  75. * @property {HookMap<SyncBailHook<[ObjectForExtract, FactoryData, StatsFactoryContext], void>>} extract
  76. * @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext, number, number], boolean | void>>} filter
  77. * @property {HookMap<SyncBailHook<[Comparator[], StatsFactoryContext], void>>} sort
  78. * @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext, number, number], boolean | void>>} filterSorted
  79. * @property {HookMap<SyncBailHook<[GroupConfig<EXPECTED_ANY, EXPECTED_ANY>[], StatsFactoryContext], void>>} groupResults
  80. * @property {HookMap<SyncBailHook<[Comparator[], StatsFactoryContext], void>>} sortResults
  81. * @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext, number, number], boolean | void>>} filterResults
  82. * @property {HookMap<SyncBailHook<[FactoryDataItem[], StatsFactoryContext], Result | void>>} merge
  83. * @property {HookMap<SyncBailHook<[Result, StatsFactoryContext], Result>>} result
  84. * @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext], string | void>>} getItemName
  85. * @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext], StatsFactory | void>>} getItemFactory
  86. */
  87. /**
  88. * Represents the stats factory runtime component.
  89. * @template T
  90. * @typedef {Map<string, T[]>} Caches
  91. */
  92. class StatsFactory {
  93. constructor() {
  94. /** @type {StatsFactoryHooks} */
  95. this.hooks = Object.freeze({
  96. extract: new HookMap(
  97. () => new SyncBailHook(["object", "data", "context"])
  98. ),
  99. filter: new HookMap(
  100. () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
  101. ),
  102. sort: new HookMap(() => new SyncBailHook(["comparators", "context"])),
  103. filterSorted: new HookMap(
  104. () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
  105. ),
  106. groupResults: new HookMap(
  107. () => new SyncBailHook(["groupConfigs", "context"])
  108. ),
  109. sortResults: new HookMap(
  110. () => new SyncBailHook(["comparators", "context"])
  111. ),
  112. filterResults: new HookMap(
  113. () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
  114. ),
  115. merge: new HookMap(() => new SyncBailHook(["items", "context"])),
  116. result: new HookMap(() => new SyncWaterfallHook(["result", "context"])),
  117. getItemName: new HookMap(() => new SyncBailHook(["item", "context"])),
  118. getItemFactory: new HookMap(() => new SyncBailHook(["item", "context"]))
  119. });
  120. const hooks = this.hooks;
  121. this._caches =
  122. /** @type {{ [Key in keyof StatsFactoryHooks]: Map<string, SyncBailHook<EXPECTED_ANY, EXPECTED_ANY>[]> }} */ ({});
  123. for (const key of Object.keys(hooks)) {
  124. this._caches[/** @type {keyof StatsFactoryHooks} */ (key)] = new Map();
  125. }
  126. this._inCreate = false;
  127. }
  128. /**
  129. * Get all level hooks.
  130. * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM
  131. * @template {HM extends HookMap<infer H> ? H : never} H
  132. * @param {HM} hookMap hook map
  133. * @param {Caches<H>} cache cache
  134. * @param {string} type type
  135. * @returns {H[]} hooks
  136. * @private
  137. */
  138. _getAllLevelHooks(hookMap, cache, type) {
  139. const cacheEntry = cache.get(type);
  140. if (cacheEntry !== undefined) {
  141. return cacheEntry;
  142. }
  143. const hooks = /** @type {H[]} */ ([]);
  144. const typeParts = type.split(".");
  145. for (let i = 0; i < typeParts.length; i++) {
  146. const hook = /** @type {H} */ (hookMap.get(typeParts.slice(i).join(".")));
  147. if (hook) {
  148. hooks.push(hook);
  149. }
  150. }
  151. cache.set(type, hooks);
  152. return hooks;
  153. }
  154. /**
  155. * Returns hook.
  156. * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM
  157. * @template {HM extends HookMap<infer H> ? H : never} H
  158. * @template {H extends import("tapable").Hook<EXPECTED_ANY, infer R> ? R : never} R
  159. * @param {HM} hookMap hook map
  160. * @param {Caches<H>} cache cache
  161. * @param {string} type type
  162. * @param {(hook: H) => R | void} fn fn
  163. * @returns {R | void} hook
  164. * @private
  165. */
  166. _forEachLevel(hookMap, cache, type, fn) {
  167. for (const hook of this._getAllLevelHooks(hookMap, cache, type)) {
  168. const result = fn(/** @type {H} */ (hook));
  169. if (result !== undefined) return result;
  170. }
  171. }
  172. /**
  173. * For each level waterfall.
  174. * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM
  175. * @template {HM extends HookMap<infer H> ? H : never} H
  176. * @param {HM} hookMap hook map
  177. * @param {Caches<H>} cache cache
  178. * @param {string} type type
  179. * @param {FactoryData} data data
  180. * @param {(hook: H, factoryData: FactoryData) => FactoryData} fn fn
  181. * @returns {FactoryData} data
  182. * @private
  183. */
  184. _forEachLevelWaterfall(hookMap, cache, type, data, fn) {
  185. for (const hook of this._getAllLevelHooks(hookMap, cache, type)) {
  186. data = fn(/** @type {H} */ (hook), data);
  187. }
  188. return data;
  189. }
  190. /**
  191. * For each level filter.
  192. * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} T
  193. * @template {T extends HookMap<infer H> ? H : never} H
  194. * @template {H extends import("tapable").Hook<EXPECTED_ANY, infer R> ? R : never} R
  195. * @param {T} hookMap hook map
  196. * @param {Caches<H>} cache cache
  197. * @param {string} type type
  198. * @param {FactoryData[]} items items
  199. * @param {(hook: H, item: R, idx: number, i: number) => R | undefined} fn fn
  200. * @param {boolean} forceClone force clone
  201. * @returns {R[]} result for each level
  202. * @private
  203. */
  204. _forEachLevelFilter(hookMap, cache, type, items, fn, forceClone) {
  205. const hooks = this._getAllLevelHooks(hookMap, cache, type);
  206. if (hooks.length === 0) return forceClone ? [...items] : items;
  207. let i = 0;
  208. return items.filter((item, idx) => {
  209. for (const hook of hooks) {
  210. const r = fn(/** @type {H} */ (hook), item, idx, i);
  211. if (r !== undefined) {
  212. if (r) i++;
  213. return r;
  214. }
  215. }
  216. i++;
  217. return true;
  218. });
  219. }
  220. /**
  221. * Returns created object.
  222. * @template FactoryData
  223. * @template FallbackCreatedObject
  224. * @param {string} type type
  225. * @param {FactoryData} data factory data
  226. * @param {Omit<StatsFactoryContext, "type">} baseContext context used as base
  227. * @returns {CreatedObject<FactoryData, FallbackCreatedObject>} created object
  228. */
  229. create(type, data, baseContext) {
  230. if (this._inCreate) {
  231. return this._create(type, data, baseContext);
  232. }
  233. try {
  234. this._inCreate = true;
  235. return this._create(type, data, baseContext);
  236. } finally {
  237. for (const key of Object.keys(this._caches)) {
  238. this._caches[/** @type {keyof StatsFactoryHooks} */ (key)].clear();
  239. }
  240. this._inCreate = false;
  241. }
  242. }
  243. /**
  244. * Returns created object.
  245. * @private
  246. * @template FactoryData
  247. * @template FallbackCreatedObject
  248. * @param {string} type type
  249. * @param {FactoryData} data factory data
  250. * @param {Omit<StatsFactoryContext, "type">} baseContext context used as base
  251. * @returns {CreatedObject<FactoryData, FallbackCreatedObject>} created object
  252. */
  253. _create(type, data, baseContext) {
  254. const context = /** @type {StatsFactoryContext} */ ({
  255. ...baseContext,
  256. type,
  257. [type]: data
  258. });
  259. if (Array.isArray(data)) {
  260. // run filter on unsorted items
  261. const items = this._forEachLevelFilter(
  262. this.hooks.filter,
  263. this._caches.filter,
  264. type,
  265. data,
  266. (h, r, idx, i) => h.call(r, context, idx, i),
  267. true
  268. );
  269. // sort items
  270. /** @type {Comparator[]} */
  271. const comparators = [];
  272. this._forEachLevel(this.hooks.sort, this._caches.sort, type, (h) =>
  273. h.call(comparators, context)
  274. );
  275. if (comparators.length > 0) {
  276. items.sort(
  277. // @ts-expect-error number of arguments is correct
  278. concatComparators(...comparators, keepOriginalOrder(items))
  279. );
  280. }
  281. // run filter on sorted items
  282. const items2 = this._forEachLevelFilter(
  283. this.hooks.filterSorted,
  284. this._caches.filterSorted,
  285. type,
  286. items,
  287. (h, r, idx, i) => h.call(r, context, idx, i),
  288. false
  289. );
  290. // for each item
  291. let resultItems = items2.map((item, i) => {
  292. /** @type {StatsFactoryContext} */
  293. const itemContext = {
  294. ...context,
  295. _index: i
  296. };
  297. // run getItemName
  298. const itemName = this._forEachLevel(
  299. this.hooks.getItemName,
  300. this._caches.getItemName,
  301. `${type}[]`,
  302. (h) => h.call(item, itemContext)
  303. );
  304. if (itemName) itemContext[itemName] = item;
  305. const innerType = itemName ? `${type}[].${itemName}` : `${type}[]`;
  306. // run getItemFactory
  307. const itemFactory =
  308. this._forEachLevel(
  309. this.hooks.getItemFactory,
  310. this._caches.getItemFactory,
  311. innerType,
  312. (h) => h.call(item, itemContext)
  313. ) || this;
  314. // run item factory
  315. return itemFactory.create(innerType, item, itemContext);
  316. });
  317. // sort result items
  318. /** @type {Comparator[]} */
  319. const comparators2 = [];
  320. this._forEachLevel(
  321. this.hooks.sortResults,
  322. this._caches.sortResults,
  323. type,
  324. (h) => h.call(comparators2, context)
  325. );
  326. if (comparators2.length > 0) {
  327. resultItems.sort(
  328. // @ts-expect-error number of arguments is correct
  329. concatComparators(...comparators2, keepOriginalOrder(resultItems))
  330. );
  331. }
  332. // group result items
  333. /** @type {GroupConfig<EXPECTED_ANY, EXPECTED_ANY>[]} */
  334. const groupConfigs = [];
  335. this._forEachLevel(
  336. this.hooks.groupResults,
  337. this._caches.groupResults,
  338. type,
  339. (h) => h.call(groupConfigs, context)
  340. );
  341. if (groupConfigs.length > 0) {
  342. resultItems = smartGrouping(resultItems, groupConfigs);
  343. }
  344. // run filter on sorted result items
  345. const finalResultItems = this._forEachLevelFilter(
  346. this.hooks.filterResults,
  347. this._caches.filterResults,
  348. type,
  349. resultItems,
  350. (h, r, idx, i) => h.call(r, context, idx, i),
  351. false
  352. );
  353. // run merge on mapped items
  354. let result = this._forEachLevel(
  355. this.hooks.merge,
  356. this._caches.merge,
  357. type,
  358. (h) => h.call(finalResultItems, context)
  359. );
  360. if (result === undefined) result = finalResultItems;
  361. // run result on merged items
  362. return this._forEachLevelWaterfall(
  363. this.hooks.result,
  364. this._caches.result,
  365. type,
  366. result,
  367. (h, r) => h.call(r, context)
  368. );
  369. }
  370. /** @type {ObjectForExtract} */
  371. const object = {};
  372. // run extract on value
  373. this._forEachLevel(this.hooks.extract, this._caches.extract, type, (h) =>
  374. h.call(object, data, context)
  375. );
  376. // run result on extracted object
  377. return this._forEachLevelWaterfall(
  378. this.hooks.result,
  379. this._caches.result,
  380. type,
  381. object,
  382. (h, r) => h.call(r, context)
  383. );
  384. }
  385. }
  386. module.exports = StatsFactory;