StatsFactory.js 8.4 KB

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