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