StatsFactory.js 11 KB

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