UNPKG

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