UNPKG

7.86 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, SyncWaterfallHook, SyncBailHook } = require("tapable");
9
10/** @template T @typedef {import("tapable").AsArray<T>} AsArray<T> */
11/** @typedef {import("tapable").Hook} Hook */
12/** @typedef {import("./DefaultStatsFactoryPlugin").StatsAsset} StatsAsset */
13/** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunk} StatsChunk */
14/** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunkGroup} StatsChunkGroup */
15/** @typedef {import("./DefaultStatsFactoryPlugin").StatsCompilation} StatsCompilation */
16/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModule} StatsModule */
17/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleReason} StatsModuleReason */
18
19/**
20 * @typedef {Object} PrintedElement
21 * @property {string} element
22 * @property {string} content
23 */
24
25/**
26 * @typedef {Object} KnownStatsPrinterContext
27 * @property {string=} type
28 * @property {StatsCompilation=} compilation
29 * @property {StatsChunkGroup=} chunkGroup
30 * @property {StatsAsset=} asset
31 * @property {StatsModule=} module
32 * @property {StatsChunk=} chunk
33 * @property {StatsModuleReason=} moduleReason
34 * @property {(str: string) => string=} bold
35 * @property {(str: string) => string=} yellow
36 * @property {(str: string) => string=} red
37 * @property {(str: string) => string=} green
38 * @property {(str: string) => string=} magenta
39 * @property {(str: string) => string=} cyan
40 * @property {(file: string, oversize?: boolean) => string=} formatFilename
41 * @property {(id: string) => string=} formatModuleId
42 * @property {(id: string, direction?: "parent"|"child"|"sibling") => string=} formatChunkId
43 * @property {(size: number) => string=} formatSize
44 * @property {(dateTime: number) => string=} formatDateTime
45 * @property {(flag: string) => string=} formatFlag
46 * @property {(time: number, boldQuantity?: boolean) => string=} formatTime
47 * @property {string=} chunkGroupKind
48 */
49
50/** @typedef {KnownStatsPrinterContext & Record<string, any>} StatsPrinterContext */
51
52class StatsPrinter {
53 constructor() {
54 this.hooks = Object.freeze({
55 /** @type {HookMap<SyncBailHook<[string[], StatsPrinterContext], true>>} */
56 sortElements: new HookMap(
57 () => new SyncBailHook(["elements", "context"])
58 ),
59 /** @type {HookMap<SyncBailHook<[PrintedElement[], StatsPrinterContext], string>>} */
60 printElements: new HookMap(
61 () => new SyncBailHook(["printedElements", "context"])
62 ),
63 /** @type {HookMap<SyncBailHook<[any[], StatsPrinterContext], true>>} */
64 sortItems: new HookMap(() => new SyncBailHook(["items", "context"])),
65 /** @type {HookMap<SyncBailHook<[any, StatsPrinterContext], string>>} */
66 getItemName: new HookMap(() => new SyncBailHook(["item", "context"])),
67 /** @type {HookMap<SyncBailHook<[string[], StatsPrinterContext], string>>} */
68 printItems: new HookMap(
69 () => new SyncBailHook(["printedItems", "context"])
70 ),
71 /** @type {HookMap<SyncBailHook<[{}, StatsPrinterContext], string>>} */
72 print: new HookMap(() => new SyncBailHook(["object", "context"])),
73 /** @type {HookMap<SyncWaterfallHook<[string, StatsPrinterContext]>>} */
74 result: new HookMap(() => new SyncWaterfallHook(["result", "context"]))
75 });
76 /** @type {Map<HookMap<Hook>, Map<string, Hook[]>>} */
77 this._levelHookCache = new Map();
78 this._inPrint = false;
79 }
80
81 /**
82 * get all level hooks
83 * @private
84 * @template {Hook} T
85 * @param {HookMap<T>} hookMap HookMap
86 * @param {string} type type
87 * @returns {T[]} hooks
88 */
89 _getAllLevelHooks(hookMap, type) {
90 let cache = /** @type {Map<string, T[]>} */ (
91 this._levelHookCache.get(hookMap)
92 );
93 if (cache === undefined) {
94 cache = new Map();
95 this._levelHookCache.set(hookMap, cache);
96 }
97 const cacheEntry = cache.get(type);
98 if (cacheEntry !== undefined) {
99 return cacheEntry;
100 }
101 /** @type {T[]} */
102 const hooks = [];
103 const typeParts = type.split(".");
104 for (let i = 0; i < typeParts.length; i++) {
105 const hook = 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 /**
115 * Run `fn` for each level
116 * @private
117 * @template T
118 * @template R
119 * @param {HookMap<SyncBailHook<T, R>>} hookMap HookMap
120 * @param {string} type type
121 * @param {(hook: SyncBailHook<T, R>) => R} fn function
122 * @returns {R} result of `fn`
123 */
124 _forEachLevel(hookMap, type, fn) {
125 for (const hook of this._getAllLevelHooks(hookMap, type)) {
126 const result = fn(hook);
127 if (result !== undefined) return result;
128 }
129 }
130
131 /**
132 * Run `fn` for each level
133 * @private
134 * @template T
135 * @param {HookMap<SyncWaterfallHook<T>>} hookMap HookMap
136 * @param {string} type type
137 * @param {AsArray<T>[0]} data data
138 * @param {(hook: SyncWaterfallHook<T>, data: AsArray<T>[0]) => AsArray<T>[0]} fn function
139 * @returns {AsArray<T>[0]} result of `fn`
140 */
141 _forEachLevelWaterfall(hookMap, type, data, fn) {
142 for (const hook of this._getAllLevelHooks(hookMap, type)) {
143 data = fn(hook, data);
144 }
145 return data;
146 }
147
148 /**
149 * @param {string} type The type
150 * @param {Object} object Object to print
151 * @param {Object=} baseContext The base context
152 * @returns {string} printed result
153 */
154 print(type, object, baseContext) {
155 if (this._inPrint) {
156 return this._print(type, object, baseContext);
157 } else {
158 try {
159 this._inPrint = true;
160 return this._print(type, object, baseContext);
161 } finally {
162 this._levelHookCache.clear();
163 this._inPrint = false;
164 }
165 }
166 }
167
168 /**
169 * @private
170 * @param {string} type type
171 * @param {Object} object object
172 * @param {Object=} baseContext context
173 * @returns {string} printed result
174 */
175 _print(type, object, baseContext) {
176 const context = {
177 ...baseContext,
178 type,
179 [type]: object
180 };
181
182 let printResult = this._forEachLevel(this.hooks.print, type, hook =>
183 hook.call(object, context)
184 );
185 if (printResult === undefined) {
186 if (Array.isArray(object)) {
187 const sortedItems = object.slice();
188 this._forEachLevel(this.hooks.sortItems, type, h =>
189 h.call(sortedItems, context)
190 );
191 const printedItems = sortedItems.map((item, i) => {
192 const itemContext = {
193 ...context,
194 _index: i
195 };
196 const itemName = this._forEachLevel(
197 this.hooks.getItemName,
198 `${type}[]`,
199 h => h.call(item, itemContext)
200 );
201 if (itemName) itemContext[itemName] = item;
202 return this.print(
203 itemName ? `${type}[].${itemName}` : `${type}[]`,
204 item,
205 itemContext
206 );
207 });
208 printResult = this._forEachLevel(this.hooks.printItems, type, h =>
209 h.call(printedItems, context)
210 );
211 if (printResult === undefined) {
212 const result = printedItems.filter(Boolean);
213 if (result.length > 0) printResult = result.join("\n");
214 }
215 } else if (object !== null && typeof object === "object") {
216 const elements = Object.keys(object).filter(
217 key => object[key] !== undefined
218 );
219 this._forEachLevel(this.hooks.sortElements, type, h =>
220 h.call(elements, context)
221 );
222 const printedElements = elements.map(element => {
223 const content = this.print(`${type}.${element}`, object[element], {
224 ...context,
225 _parent: object,
226 _element: element,
227 [element]: object[element]
228 });
229 return { element, content };
230 });
231 printResult = this._forEachLevel(this.hooks.printElements, type, h =>
232 h.call(printedElements, context)
233 );
234 if (printResult === undefined) {
235 const result = printedElements.map(e => e.content).filter(Boolean);
236 if (result.length > 0) printResult = result.join("\n");
237 }
238 }
239 }
240
241 return this._forEachLevelWaterfall(
242 this.hooks.result,
243 type,
244 printResult,
245 (h, r) => h.call(r, context)
246 );
247 }
248}
249module.exports = StatsPrinter;