StatsFactory.js 14 KB

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