feat: refactoring project

This commit is contained in:
Carlos
2024-11-23 14:56:07 -05:00
parent f0c2a50c18
commit 1c6db5818d
2351 changed files with 39323 additions and 60326 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -11,15 +11,24 @@ const RequestShortener = require("../RequestShortener");
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compilation").CreateStatsOptionsContext} CreateStatsOptionsContext */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("./DefaultStatsFactoryPlugin").StatsError} StatsError */
/**
* @param {StatsOptions} options options
* @param {StatsOptions} defaults default options
*/
const applyDefaults = (options, defaults) => {
for (const key of Object.keys(defaults)) {
for (const _k of Object.keys(defaults)) {
const key = /** @type {keyof StatsOptions} */ (_k);
if (typeof options[key] === "undefined") {
options[key] = defaults[key];
/** @type {TODO} */
(options)[key] = defaults[key];
}
}
};
/** @typedef {Record<string, StatsOptions>} NamedPresets */
/** @type {NamedPresets} */
const NAMED_PRESETS = {
verbose: {
hash: true,
@@ -126,12 +135,35 @@ const NAMED_PRESETS = {
}
};
/**
* @param {StatsOptions} all stats option
* @returns {boolean} true when enabled, otherwise false
*/
const NORMAL_ON = ({ all }) => all !== false;
/**
* @param {StatsOptions} all stats option
* @returns {boolean} true when enabled, otherwise false
*/
const NORMAL_OFF = ({ all }) => all === true;
/**
* @param {StatsOptions} all stats option
* @param {CreateStatsOptionsContext} forToString stats options context
* @returns {boolean} true when enabled, otherwise false
*/
const ON_FOR_TO_STRING = ({ all }, { forToString }) =>
forToString ? all !== false : all === true;
/**
* @param {StatsOptions} all stats option
* @param {CreateStatsOptionsContext} forToString stats options context
* @returns {boolean} true when enabled, otherwise false
*/
const OFF_FOR_TO_STRING = ({ all }, { forToString }) =>
forToString ? all === true : all !== false;
/**
* @param {StatsOptions} all stats option
* @param {CreateStatsOptionsContext} forToString stats options context
* @returns {boolean | "auto"} true when enabled, otherwise false
*/
const AUTO_FOR_TO_STRING = ({ all }, { forToString }) => {
if (all === false) return false;
if (all === true) return true;
@@ -139,13 +171,19 @@ const AUTO_FOR_TO_STRING = ({ all }, { forToString }) => {
return true;
};
/** @type {Record<string, (options: StatsOptions, context: CreateStatsOptionsContext, compilation: Compilation) => any>} */
/** @typedef {Record<string, (options: StatsOptions, context: CreateStatsOptionsContext, compilation: Compilation) => StatsOptions[keyof StatsOptions] | RequestShortener>} Defaults */
/** @type {Defaults} */
const DEFAULTS = {
context: (options, context, compilation) => compilation.compiler.context,
requestShortener: (options, context, compilation) =>
compilation.compiler.context === options.context
? compilation.requestShortener
: new RequestShortener(options.context, compilation.compiler.root),
: new RequestShortener(
/** @type {string} */
(options.context),
compilation.compiler.root
),
performance: NORMAL_ON,
hash: OFF_FOR_TO_STRING,
env: NORMAL_OFF,
@@ -235,6 +273,10 @@ const DEFAULTS = {
colors: () => false
};
/**
* @param {string | ({ test: function(string): boolean }) | (function(string): boolean) | boolean} item item to normalize
* @returns {(function(string): boolean) | undefined} normalize fn
*/
const normalizeFilter = item => {
if (typeof item === "string") {
const regExp = new RegExp(
@@ -253,6 +295,7 @@ const normalizeFilter = item => {
}
};
/** @type {Record<string, function(any): any[]>} */
const NORMALIZER = {
excludeModules: value => {
if (!Array.isArray(value)) {
@@ -270,20 +313,32 @@ const NORMALIZER = {
if (!Array.isArray(value)) {
value = value ? [value] : [];
}
return value.map(filter => {
if (typeof filter === "string") {
return (warning, warningString) => warningString.includes(filter);
/**
* @callback WarningFilterFn
* @param {StatsError} warning warning
* @param {string} warningString warning string
* @returns {boolean} result
*/
return value.map(
/**
* @param {StatsOptions["warningsFilter"]} filter a warning filter
* @returns {WarningFilterFn} result
*/
filter => {
if (typeof filter === "string") {
return (warning, warningString) => warningString.includes(filter);
}
if (filter instanceof RegExp) {
return (warning, warningString) => filter.test(warningString);
}
if (typeof filter === "function") {
return filter;
}
throw new Error(
`Can only filter warnings with Strings or RegExps. (Given: ${filter})`
);
}
if (filter instanceof RegExp) {
return (warning, warningString) => filter.test(warningString);
}
if (typeof filter === "function") {
return filter;
}
throw new Error(
`Can only filter warnings with Strings or RegExps. (Given: ${filter})`
);
});
);
},
logging: value => {
if (value === true) value = "log";
@@ -306,7 +361,7 @@ class DefaultStatsPresetPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("DefaultStatsPresetPlugin", compilation => {
for (const key of Object.keys(NAMED_PRESETS)) {
const defaults = NAMED_PRESETS[key];
const defaults = NAMED_PRESETS[/** @type {keyof NamedPresets} */ (key)];
compilation.hooks.statsPreset
.for(key)
.tap("DefaultStatsPresetPlugin", (options, context) => {

View File

@@ -6,7 +6,10 @@
"use strict";
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("./DefaultStatsFactoryPlugin").KnownStatsChunkGroup} KnownStatsChunkGroup */
/** @typedef {import("./StatsPrinter")} StatsPrinter */
/** @typedef {import("./StatsPrinter").KnownStatsPrinterColorFn} KnownStatsPrinterColorFn */
/** @typedef {import("./StatsPrinter").KnownStatsPrinterFormaters} KnownStatsPrinterFormaters */
/** @typedef {import("./StatsPrinter").StatsPrinterContext} StatsPrinterContext */
const DATA_URI_CONTENT_LENGTH = 16;
@@ -22,9 +25,8 @@ const plural = (n, singular, plural) => (n === 1 ? singular : plural);
/**
* @param {Record<string, number>} sizes sizes by source type
* @param {object} options options
* @param {(number) => string=} options.formatSize size formatter
* @returns {string} text
* @param {StatsPrinterContext} options options
* @returns {string | undefined} text
*/
const printSizes = (sizes, { formatSize = n => `${n}` }) => {
const keys = Object.keys(sizes);
@@ -56,7 +58,9 @@ const getResourceName = resource => {
* @returns {[string,string]} prefix and module name
*/
const getModuleName = name => {
const [, prefix, resource] = /^(.*!)?([^!]*)$/.exec(name);
const [, prefix, resource] =
/** @type {[any, string, string]} */
(/** @type {unknown} */ (/^(.*!)?([^!]*)$/.exec(name)));
if (resource.length > MAX_MODULE_IDENTIFIER_LENGTH) {
const truncatedResource = `${resource.slice(
@@ -86,22 +90,29 @@ const mapLines = (str, fn) => str.split("\n").map(fn).join("\n");
*/
const twoDigit = n => (n >= 10 ? `${n}` : `0${n}`);
const isValidId = id => {
return typeof id === "number" || id;
};
/**
* @param {string | number} id an id
* @returns {boolean | string} is i
*/
const isValidId = id => typeof id === "number" || id;
/**
* @template T
* @param {Array<T>} list of items
* @param {Array<T> | undefined} list of items
* @param {number} count number of items to show
* @returns {string} string representation of list
*/
const moreCount = (list, count) => {
return list && list.length > 0 ? `+ ${count}` : `${count}`;
};
const moreCount = (list, count) =>
list && list.length > 0 ? `+ ${count}` : `${count}`;
/** @type {Record<string, (thing: any, context: StatsPrinterContext, printer: StatsPrinter) => string | void>} */
const SIMPLE_PRINTERS = {
/**
* @template T
* @template {keyof T} K
* @typedef {{ [P in K]-?: T[P] }} WithRequired
*/
/** @type {Record<string, (thing: any, context: Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation">, printer: StatsPrinter) => string | undefined>} */
const COMPILATION_SIMPLE_PRINTERS = {
"compilation.summary!": (
_,
{
@@ -125,14 +136,16 @@ const SIMPLE_PRINTERS = {
) => {
const root = type === "compilation.summary!";
const warningsMessage =
warningsCount > 0
/** @type {number} */ (warningsCount) > 0
? yellow(
`${warningsCount} ${plural(warningsCount, "warning", "warnings")}`
`${warningsCount} ${plural(/** @type {number} */ (warningsCount), "warning", "warnings")}`
)
: "";
const errorsMessage =
errorsCount > 0
? red(`${errorsCount} ${plural(errorsCount, "error", "errors")}`)
/** @type {number} */ (errorsCount) > 0
? red(
`${errorsCount} ${plural(/** @type {number} */ (errorsCount), "error", "errors")}`
)
: "";
const timeMessage = root && time ? ` in ${formatTime(time)}` : "";
const hashMessage = hash ? ` (${hash})` : "";
@@ -161,7 +174,7 @@ const SIMPLE_PRINTERS = {
} else if (errorsCount === 0 && warningsCount === 0) {
statusMessage = `compiled ${green("successfully")}`;
} else {
statusMessage = `compiled`;
statusMessage = "compiled";
}
if (
builtAtMessage ||
@@ -258,11 +271,12 @@ const SIMPLE_PRINTERS = {
"compilation.warningsInChildren!": (_, { yellow, compilation }) => {
if (
!compilation.children &&
compilation.warningsCount > 0 &&
/** @type {number} */ (compilation.warningsCount) > 0 &&
compilation.warnings
) {
const childWarnings =
compilation.warningsCount - compilation.warnings.length;
/** @type {number} */ (compilation.warningsCount) -
compilation.warnings.length;
if (childWarnings > 0) {
return yellow(
`${childWarnings} ${plural(
@@ -281,10 +295,12 @@ const SIMPLE_PRINTERS = {
"compilation.errorsInChildren!": (_, { red, compilation }) => {
if (
!compilation.children &&
compilation.errorsCount > 0 &&
/** @type {number} */ (compilation.errorsCount) > 0 &&
compilation.errors
) {
const childErrors = compilation.errorsCount - compilation.errors.length;
const childErrors =
/** @type {number} */ (compilation.errorsCount) -
compilation.errors.length;
if (childErrors > 0) {
return red(
`${childErrors} ${plural(
@@ -299,15 +315,16 @@ const SIMPLE_PRINTERS = {
);
}
}
},
}
};
/** @type {Record<string, (thing: any, context: Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "asset">, printer: StatsPrinter) => string | undefined>} */
const ASSET_SIMPLE_PRINTERS = {
"asset.type": type => type,
"asset.name": (name, { formatFilename, asset: { isOverSizeLimit } }) =>
formatFilename(name, isOverSizeLimit),
"asset.size": (
size,
{ asset: { isOverSizeLimit }, yellow, green, formatSize }
) => (isOverSizeLimit ? yellow(formatSize(size)) : formatSize(size)),
"asset.size": (size, { asset: { isOverSizeLimit }, yellow, formatSize }) =>
isOverSizeLimit ? yellow(formatSize(size)) : formatSize(size),
"asset.emitted": (emitted, { green, formatFlag }) =>
emitted ? green(formatFlag("emitted")) : undefined,
"asset.comparedForEmit": (comparedForEmit, { yellow, formatFlag }) =>
@@ -356,8 +373,11 @@ const SIMPLE_PRINTERS = {
assetChunk: (id, { formatChunkId }) => formatChunkId(id),
assetChunkName: name => name,
assetChunkIdHint: name => name,
assetChunkIdHint: name => name
};
/** @type {Record<string, (thing: any, context: Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "module">, printer: StatsPrinter) => string | undefined>} */
const MODULE_SIMPLE_PRINTERS = {
"module.type": type => (type !== "module" ? type : undefined),
"module.id": (id, { formatModuleId }) =>
isValidId(id) ? formatModuleId(id) : undefined,
@@ -433,11 +453,11 @@ const SIMPLE_PRINTERS = {
providedExportsCount === usedExports.length
) {
return cyan(formatFlag("all exports used"));
} else {
return cyan(
formatFlag(`only some exports used: ${usedExports.join(", ")}`)
);
}
return cyan(
formatFlag(`only some exports used: ${usedExports.join(", ")}`)
);
}
}
},
@@ -470,11 +490,17 @@ const SIMPLE_PRINTERS = {
"modules"
)}`
: undefined,
"module.separator!": () => "\n",
"module.separator!": () => "\n"
};
/** @type {Record<string, (thing: any, context: Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "moduleIssuer">, printer: StatsPrinter) => string | undefined>} */
const MODULE_ISSUER_PRINTERS = {
"moduleIssuer.id": (id, { formatModuleId }) => formatModuleId(id),
"moduleIssuer.profile.total": (value, { formatTime }) => formatTime(value),
"moduleIssuer.profile.total": (value, { formatTime }) => formatTime(value)
};
/** @type {Record<string, (thing: any, context: Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "moduleReason">, printer: StatsPrinter) => string | undefined>} */
const MODULE_REASON_PRINTERS = {
"moduleReason.type": type => type,
"moduleReason.userRequest": (userRequest, { cyan }) =>
cyan(getResourceName(userRequest)),
@@ -496,8 +522,11 @@ const SIMPLE_PRINTERS = {
"reason",
"reasons"
)}`
: undefined,
: undefined
};
/** @type {Record<string, (thing: any, context: Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "profile">, printer: StatsPrinter) => string | undefined>} */
const MODULE_PROFILE_PRINTERS = {
"module.profile.total": (value, { formatTime }) => formatTime(value),
"module.profile.resolving": (value, { formatTime }) =>
`resolving: ${formatTime(value)}`,
@@ -512,8 +541,11 @@ const SIMPLE_PRINTERS = {
"module.profile.additionalResolving": (value, { formatTime }) =>
value ? `additional resolving: ${formatTime(value)}` : undefined,
"module.profile.additionalIntegration": (value, { formatTime }) =>
value ? `additional integration: ${formatTime(value)}` : undefined,
value ? `additional integration: ${formatTime(value)}` : undefined
};
/** @type {Record<string, (thing: any, context: Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "chunkGroupKind" | "chunkGroup">, printer: StatsPrinter) => string | undefined>} */
const CHUNK_GROUP_PRINTERS = {
"chunkGroup.kind!": (_, { chunkGroupKind }) => chunkGroupKind,
"chunkGroup.separator!": () => "\n",
"chunkGroup.name": (name, { bold }) => bold(name),
@@ -541,10 +573,11 @@ const SIMPLE_PRINTERS = {
"chunkGroup.is!": () => "=",
"chunkGroupAsset.name": (asset, { green }) => green(asset),
"chunkGroupAsset.size": (size, { formatSize, chunkGroup }) =>
chunkGroup.assets.length > 1 ||
chunkGroup.assets &&
(chunkGroup.assets.length > 1 ||
(chunkGroup.auxiliaryAssets && chunkGroup.auxiliaryAssets.length > 0)
? formatSize(size)
: undefined,
: undefined),
"chunkGroup.children": (children, context, printer) =>
Array.isArray(children)
? undefined
@@ -560,8 +593,11 @@ const SIMPLE_PRINTERS = {
"chunkGroupChild.assets[]": (file, { formatFilename }) =>
formatFilename(file),
"chunkGroupChild.chunks[]": (id, { formatChunkId }) => formatChunkId(id),
"chunkGroupChild.name": name => (name ? `(name: ${name})` : undefined),
"chunkGroupChild.name": name => (name ? `(name: ${name})` : undefined)
};
/** @type {Record<string, (thing: any, context: Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "chunk">, printer: StatsPrinter) => string | undefined>} */
const CHUNK_PRINTERS = {
"chunk.id": (id, { formatChunkId }) => formatChunkId(id),
"chunk.files[]": (file, { formatFilename }) => formatFilename(file),
"chunk.names[]": name => name,
@@ -611,8 +647,11 @@ const SIMPLE_PRINTERS = {
"chunkOrigin.moduleId": (moduleId, { formatModuleId }) =>
isValidId(moduleId) ? formatModuleId(moduleId) : undefined,
"chunkOrigin.moduleName": (moduleName, { bold }) => bold(moduleName),
"chunkOrigin.loc": loc => loc,
"chunkOrigin.loc": loc => loc
};
/** @type {Record<string, (thing: any, context: Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "error">, printer: StatsPrinter) => string | undefined>} */
const ERROR_PRINTERS = {
"error.compilerPath": (compilerPath, { bold }) =>
compilerPath ? bold(`(${compilerPath})`) : undefined,
"error.chunkId": (chunkId, { formatChunkId }) =>
@@ -622,21 +661,23 @@ const SIMPLE_PRINTERS = {
"error.chunkInitial": (chunkInitial, { formatFlag }) =>
chunkInitial ? formatFlag("initial") : undefined,
"error.file": (file, { bold }) => bold(file),
"error.moduleName": (moduleName, { bold }) => {
return moduleName.includes("!")
"error.moduleName": (moduleName, { bold }) =>
moduleName.includes("!")
? `${bold(moduleName.replace(/^(\s|\S)*!/, ""))} (${moduleName})`
: `${bold(moduleName)}`;
},
: `${bold(moduleName)}`,
"error.loc": (loc, { green }) => green(loc),
"error.message": (message, { bold, formatError }) =>
message.includes("\u001b[") ? message : bold(formatError(message)),
message.includes("\u001B[") ? message : bold(formatError(message)),
"error.details": (details, { formatError }) => formatError(details),
"error.filteredDetails": filteredDetails =>
filteredDetails ? `+ ${filteredDetails} hidden lines` : undefined,
"error.stack": stack => stack,
"error.moduleTrace": moduleTrace => undefined,
"error.separator!": () => "\n",
"error.separator!": () => "\n"
};
/** @type {Record<string, (thing: any, context: Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "logging">, printer: StatsPrinter) => string | undefined>} */
const LOG_ENTRY_PRINTERS = {
"loggingEntry(error).loggingEntry.message": (message, { red }) =>
mapLines(message, x => `<e> ${red(x)}`),
"loggingEntry(warn).loggingEntry.message": (message, { yellow }) =>
@@ -666,20 +707,26 @@ const SIMPLE_PRINTERS = {
"loggingEntry.trace[]": trace =>
trace ? mapLines(trace, x => `| ${x}`) : undefined,
"moduleTraceItem.originName": originName => originName,
loggingGroup: loggingGroup =>
loggingGroup.entries.length === 0 ? "" : undefined,
"loggingGroup.debug": (flag, { red }) => (flag ? red("DEBUG") : undefined),
"loggingGroup.name": (name, { bold }) => bold(`LOG from ${name}`),
"loggingGroup.separator!": () => "\n",
"loggingGroup.filteredEntries": filteredEntries =>
filteredEntries > 0 ? `+ ${filteredEntries} hidden lines` : undefined,
filteredEntries > 0 ? `+ ${filteredEntries} hidden lines` : undefined
};
/** @type {Record<string, (thing: any, context: Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "moduleTraceItem">, printer: StatsPrinter) => string | undefined>} */
const MODULE_TRACE_ITEM_PRINTERS = {
"moduleTraceItem.originName": originName => originName
};
/** @type {Record<string, (thing: any, context: Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "moduleTraceDependency">, printer: StatsPrinter) => string | undefined>} */
const MODULE_TRACE_DEPENDENCY_PRINTERS = {
"moduleTraceDependency.loc": loc => loc
};
/** @type {Record<string, string | Function>} */
/** @type {Record<string, string | function(any): string>} */
const ITEM_NAMES = {
"compilation.assets[]": "asset",
"compilation.modules[]": "module",
@@ -908,19 +955,27 @@ const PREFERRED_ORDERS = {
loggingEntry: ["message", "trace", "children"]
};
/** @typedef {(items: string[]) => string | undefined} SimpleItemsJoiner */
/** @type {SimpleItemsJoiner} */
const itemsJoinOneLine = items => items.filter(Boolean).join(" ");
/** @type {SimpleItemsJoiner} */
const itemsJoinOneLineBrackets = items =>
items.length > 0 ? `(${items.filter(Boolean).join(" ")})` : undefined;
/** @type {SimpleItemsJoiner} */
const itemsJoinMoreSpacing = items => items.filter(Boolean).join("\n\n");
/** @type {SimpleItemsJoiner} */
const itemsJoinComma = items => items.filter(Boolean).join(", ");
/** @type {SimpleItemsJoiner} */
const itemsJoinCommaBrackets = items =>
items.length > 0 ? `(${items.filter(Boolean).join(", ")})` : undefined;
/** @type {function(string): SimpleItemsJoiner} */
const itemsJoinCommaBracketsWithName = name => items =>
items.length > 0
? `(${name}: ${items.filter(Boolean).join(", ")})`
: undefined;
/** @type {Record<string, (items: string[]) => string>} */
/** @type {Record<string, SimpleItemsJoiner>} */
const SIMPLE_ITEMS_JOINER = {
"chunk.parents": itemsJoinOneLine,
"chunk.siblings": itemsJoinOneLine,
@@ -952,18 +1007,27 @@ const SIMPLE_ITEMS_JOINER = {
"compilation.errors": itemsJoinMoreSpacing,
"compilation.warnings": itemsJoinMoreSpacing,
"compilation.logging": itemsJoinMoreSpacing,
"compilation.children": items => indent(itemsJoinMoreSpacing(items), " "),
"compilation.children": items =>
indent(/** @type {string} */ (itemsJoinMoreSpacing(items)), " "),
"moduleTraceItem.dependencies": itemsJoinOneLine,
"loggingEntry.children": items =>
indent(items.filter(Boolean).join("\n"), " ", false)
};
/**
* @param {Item[]} items items
* @returns {string} result
*/
const joinOneLine = items =>
items
.map(item => item.content)
.filter(Boolean)
.join(" ");
/**
* @param {Item[]} items items
* @returns {string} result
*/
const joinInBrackets = items => {
const res = [];
let mode = 0;
@@ -1006,13 +1070,24 @@ const joinInBrackets = items => {
return res.join("");
};
/**
* @param {string} str a string
* @param {string} prefix prefix
* @param {boolean=} noPrefixInFirstLine need prefix in the first line?
* @returns {string} result
*/
const indent = (str, prefix, noPrefixInFirstLine) => {
const rem = str.replace(/\n([^\n])/g, "\n" + prefix + "$1");
const rem = str.replace(/\n([^\n])/g, `\n${prefix}$1`);
if (noPrefixInFirstLine) return rem;
const ind = str[0] === "\n" ? "" : prefix;
return ind + rem;
};
/**
* @param {(false | Item)[]} items items
* @param {string} indenter indenter
* @returns {string} result
*/
const joinExplicitNewLine = (items, indenter) => {
let firstInLine = true;
let first = true;
@@ -1027,22 +1102,34 @@ const joinExplicitNewLine = (items, indenter) => {
first = false;
const noJoiner = firstInLine || content.startsWith("\n");
firstInLine = content.endsWith("\n");
return noJoiner ? content : " " + content;
return noJoiner ? content : ` ${content}`;
})
.filter(Boolean)
.join("")
.trim();
};
/**
* @param {boolean} error is an error
* @returns {SimpleElementJoiner} joiner
*/
const joinError =
error =>
/**
* @param {Item[]} items items
* @param {Required<StatsPrinterContext>} ctx context
* @returns {string} result
*/
(items, { red, yellow }) =>
`${error ? red("ERROR") : yellow("WARNING")} in ${joinExplicitNewLine(
items,
""
)}`;
/** @type {Record<string, (items: ({ element: string, content: string })[], context: StatsPrinterContext) => string>} */
/** @typedef {{ element: string, content: string }} Item */
/** @typedef {(items: Item[], context: Required<StatsPrinterContext>) => string} SimpleElementJoiner */
/** @type {Record<string, SimpleElementJoiner>} */
const SIMPLE_ELEMENT_JOINERS = {
compilation: items => {
const result = [];
@@ -1119,23 +1206,20 @@ const SIMPLE_ELEMENT_JOINERS = {
},
chunk: items => {
let hasEntry = false;
return (
"chunk " +
joinExplicitNewLine(
items.filter(item => {
switch (item.element) {
case "entry":
if (item.content) hasEntry = true;
break;
case "initial":
if (hasEntry) return false;
break;
}
return true;
}),
" "
)
);
return `chunk ${joinExplicitNewLine(
items.filter(item => {
switch (item.element) {
case "entry":
if (item.content) hasEntry = true;
break;
case "initial":
if (hasEntry) return false;
break;
}
return true;
}),
" "
)}`;
},
"chunk.childrenByOrder[]": items => `(${joinOneLine(items)})`,
chunkGroup: items => joinExplicitNewLine(items, " "),
@@ -1196,23 +1280,27 @@ const SIMPLE_ELEMENT_JOINERS = {
},
"module.profile": joinInBrackets,
moduleIssuer: joinOneLine,
chunkOrigin: items => "> " + joinOneLine(items),
chunkOrigin: items => `> ${joinOneLine(items)}`,
"errors[].error": joinError(true),
"warnings[].error": joinError(false),
loggingGroup: items => joinExplicitNewLine(items, "").trimEnd(),
moduleTraceItem: items => " @ " + joinOneLine(items),
moduleTraceItem: items => ` @ ${joinOneLine(items)}`,
moduleTraceDependency: joinOneLine
};
/** @typedef {"bold" | "yellow" | "red" | "green" | "cyan" | "magenta"} ColorNames */
/** @type {Record<ColorNames, string>} */
const AVAILABLE_COLORS = {
bold: "\u001b[1m",
yellow: "\u001b[1m\u001b[33m",
red: "\u001b[1m\u001b[31m",
green: "\u001b[1m\u001b[32m",
cyan: "\u001b[1m\u001b[36m",
magenta: "\u001b[1m\u001b[35m"
bold: "\u001B[1m",
yellow: "\u001B[1m\u001B[33m",
red: "\u001B[1m\u001B[31m",
green: "\u001B[1m\u001B[32m",
cyan: "\u001B[1m\u001B[36m",
magenta: "\u001B[1m\u001B[35m"
};
/** @type {Record<string, function(any, Required<KnownStatsPrinterColorFn> & StatsPrinterContext, ...any): string>} */
const AVAILABLE_FORMATS = {
formatChunkId: (id, { yellow }, direction) => {
switch (direction) {
@@ -1256,13 +1344,12 @@ const AVAILABLE_FORMATS = {
else if (time < times[2]) return bold(`${time}${unit}`);
else if (time < times[1]) return green(`${time}${unit}`);
else if (time < times[0]) return yellow(`${time}${unit}`);
else return red(`${time}${unit}`);
} else {
return `${boldQuantity ? bold(time) : time}${unit}`;
return red(`${time}${unit}`);
}
return `${boldQuantity ? bold(time) : time}${unit}`;
},
formatError: (message, { green, yellow, red }) => {
if (message.includes("\u001b[")) return message;
if (message.includes("\u001B[")) return message;
const highlights = [
{ regExp: /(Did you mean .+)/g, format: green },
{
@@ -1290,23 +1377,36 @@ const AVAILABLE_FORMATS = {
}
];
for (const { regExp, format } of highlights) {
message = message.replace(regExp, (match, content) => {
return match.replace(content, format(content));
});
message = message.replace(
regExp,
/**
* @param {string} match match
* @param {string} content content
* @returns {string} result
*/
(match, content) => match.replace(content, format(content))
);
}
return message;
}
};
/** @typedef {function(string): string} ResultModifierFn */
/** @type {Record<string, ResultModifierFn>} */
const RESULT_MODIFIER = {
"module.modules": result => {
return indent(result, "| ");
}
"module.modules": result => indent(result, "| ")
};
/**
* @param {string[]} array array
* @param {string[]} preferredOrder preferred order
* @returns {string[]} result
*/
const createOrder = (array, preferredOrder) => {
const originalArray = array.slice();
/** @type {Set<string>} */
const set = new Set(array);
/** @type {Set<string>} */
const usedSet = new Set();
array.length = 0;
for (const element of preferredOrder) {
@@ -1333,49 +1433,218 @@ class DefaultStatsPrinterPlugin {
compiler.hooks.compilation.tap("DefaultStatsPrinterPlugin", compilation => {
compilation.hooks.statsPrinter.tap(
"DefaultStatsPrinterPlugin",
(stats, options, context) => {
(stats, options) => {
// Put colors into context
stats.hooks.print
.for("compilation")
.tap("DefaultStatsPrinterPlugin", (compilation, context) => {
for (const color of Object.keys(AVAILABLE_COLORS)) {
const name = /** @type {ColorNames} */ (color);
/** @type {string | undefined} */
let start;
if (options.colors) {
if (
typeof options.colors === "object" &&
typeof options.colors[color] === "string"
typeof options.colors[name] === "string"
) {
start = options.colors[color];
start = options.colors[name];
} else {
start = AVAILABLE_COLORS[color];
start = AVAILABLE_COLORS[name];
}
}
if (start) {
/**
* @param {string} str string
* @returns {string} string with color
*/
context[color] = str =>
`${start}${
typeof str === "string"
? str.replace(
/((\u001b\[39m|\u001b\[22m|\u001b\[0m)+)/g,
/((\u001B\[39m|\u001B\[22m|\u001B\[0m)+)/g,
`$1${start}`
)
: str
}\u001b[39m\u001b[22m`;
}\u001B[39m\u001B[22m`;
} else {
/**
* @param {string} str string
* @returns {string} str string
*/
context[color] = str => str;
}
}
for (const format of Object.keys(AVAILABLE_FORMATS)) {
context[format] = (content, ...args) =>
AVAILABLE_FORMATS[format](content, context, ...args);
context[format] =
/**
* @param {string | number} content content
* @param {...TODO} args args
* @returns {string} result
*/
(content, ...args) =>
AVAILABLE_FORMATS[format](
content,
/** @type {Required<KnownStatsPrinterColorFn> & StatsPrinterContext} */
(context),
...args
);
}
context.timeReference = compilation.time;
});
for (const key of Object.keys(SIMPLE_PRINTERS)) {
for (const key of Object.keys(COMPILATION_SIMPLE_PRINTERS)) {
stats.hooks.print
.for(key)
.tap("DefaultStatsPrinterPlugin", (obj, ctx) =>
SIMPLE_PRINTERS[key](obj, ctx, stats)
COMPILATION_SIMPLE_PRINTERS[key](
obj,
/** @type {Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation">} */
(ctx),
stats
)
);
}
for (const key of Object.keys(ASSET_SIMPLE_PRINTERS)) {
stats.hooks.print
.for(key)
.tap("DefaultStatsPrinterPlugin", (obj, ctx) =>
ASSET_SIMPLE_PRINTERS[key](
obj,
/** @type {Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "asset">} */
(ctx),
stats
)
);
}
for (const key of Object.keys(MODULE_SIMPLE_PRINTERS)) {
stats.hooks.print
.for(key)
.tap("DefaultStatsPrinterPlugin", (obj, ctx) =>
MODULE_SIMPLE_PRINTERS[key](
obj,
/** @type {Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "module">} */
(ctx),
stats
)
);
}
for (const key of Object.keys(MODULE_ISSUER_PRINTERS)) {
stats.hooks.print
.for(key)
.tap("DefaultStatsPrinterPlugin", (obj, ctx) =>
MODULE_ISSUER_PRINTERS[key](
obj,
/** @type {Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "moduleIssuer">} */
(ctx),
stats
)
);
}
for (const key of Object.keys(MODULE_REASON_PRINTERS)) {
stats.hooks.print
.for(key)
.tap("DefaultStatsPrinterPlugin", (obj, ctx) =>
MODULE_REASON_PRINTERS[key](
obj,
/** @type {Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "moduleReason">} */
(ctx),
stats
)
);
}
for (const key of Object.keys(MODULE_PROFILE_PRINTERS)) {
stats.hooks.print
.for(key)
.tap("DefaultStatsPrinterPlugin", (obj, ctx) =>
MODULE_PROFILE_PRINTERS[key](
obj,
/** @type {Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "profile">} */
(ctx),
stats
)
);
}
for (const key of Object.keys(CHUNK_GROUP_PRINTERS)) {
stats.hooks.print
.for(key)
.tap("DefaultStatsPrinterPlugin", (obj, ctx) =>
CHUNK_GROUP_PRINTERS[key](
obj,
/** @type {Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "chunkGroupKind" | "chunkGroup">} */
(ctx),
stats
)
);
}
for (const key of Object.keys(CHUNK_PRINTERS)) {
stats.hooks.print
.for(key)
.tap("DefaultStatsPrinterPlugin", (obj, ctx) =>
CHUNK_PRINTERS[key](
obj,
/** @type {Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "chunk">} */
(ctx),
stats
)
);
}
for (const key of Object.keys(ERROR_PRINTERS)) {
stats.hooks.print
.for(key)
.tap("DefaultStatsPrinterPlugin", (obj, ctx) =>
ERROR_PRINTERS[key](
obj,
/** @type {Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "error">} */
(ctx),
stats
)
);
}
for (const key of Object.keys(LOG_ENTRY_PRINTERS)) {
stats.hooks.print
.for(key)
.tap("DefaultStatsPrinterPlugin", (obj, ctx) =>
LOG_ENTRY_PRINTERS[key](
obj,
/** @type {Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "logging">} */
(ctx),
stats
)
);
}
for (const key of Object.keys(MODULE_TRACE_DEPENDENCY_PRINTERS)) {
stats.hooks.print
.for(key)
.tap("DefaultStatsPrinterPlugin", (obj, ctx) =>
MODULE_TRACE_DEPENDENCY_PRINTERS[key](
obj,
/** @type {Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "moduleTraceDependency">} */
(ctx),
stats
)
);
}
for (const key of Object.keys(MODULE_TRACE_ITEM_PRINTERS)) {
stats.hooks.print
.for(key)
.tap("DefaultStatsPrinterPlugin", (obj, ctx) =>
MODULE_TRACE_ITEM_PRINTERS[key](
obj,
/** @type {Required<KnownStatsPrinterColorFn> & Required<KnownStatsPrinterFormaters> & WithRequired<StatsPrinterContext, "type" | "compilation" | "moduleTraceItem">} */
(ctx),
stats
)
);
}
@@ -1409,7 +1678,7 @@ class DefaultStatsPrinterPlugin {
const joiner = SIMPLE_ELEMENT_JOINERS[key];
stats.hooks.printElements
.for(key)
.tap("DefaultStatsPrinterPlugin", joiner);
.tap("DefaultStatsPrinterPlugin", /** @type {TODO} */ (joiner));
}
for (const key of Object.keys(RESULT_MODIFIER)) {

View File

@@ -11,83 +11,108 @@ const smartGrouping = require("../util/smartGrouping");
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compilation").NormalizedStatsOptions} NormalizedStatsOptions */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../WebpackError")} WebpackError */
/** @typedef {import("../util/comparators").Comparator<any>} Comparator */
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
/** @typedef {import("../util/smartGrouping").GroupConfig<any, object>} GroupConfig */
/**
* @typedef {object} KnownStatsFactoryContext
* @property {string} type
* @property {function(string): string=} makePathsRelative
* @property {Compilation=} compilation
* @property {Set<Module>=} rootModules
* @property {Map<string,Chunk[]>=} compilationFileToChunks
* @property {Map<string,Chunk[]>=} compilationAuxiliaryFileToChunks
* @property {RuntimeSpec=} runtime
* @property {function(Compilation): WebpackError[]=} cachedGetErrors
* @property {function(Compilation): WebpackError[]=} cachedGetWarnings
* @property {function(string): string} makePathsRelative
* @property {Compilation} compilation
* @property {Set<Module>} rootModules
* @property {Map<string,Chunk[]>} compilationFileToChunks
* @property {Map<string,Chunk[]>} compilationAuxiliaryFileToChunks
* @property {RuntimeSpec} runtime
* @property {function(Compilation): WebpackError[]} cachedGetErrors
* @property {function(Compilation): WebpackError[]} cachedGetWarnings
*/
/** @typedef {KnownStatsFactoryContext & Record<string, any>} StatsFactoryContext */
/** @typedef {Record<string, any> & KnownStatsFactoryContext} StatsFactoryContext */
/** @typedef {any} CreatedObject */
/** @typedef {any} FactoryData */
/** @typedef {any} FactoryDataItem */
/** @typedef {any} Result */
/** @typedef {Record<string, any>} ObjectForExtract */
/**
* @typedef {object} StatsFactoryHooks
* @property {HookMap<SyncBailHook<[ObjectForExtract, FactoryData, StatsFactoryContext], void>>} extract
* @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext, number, number], boolean | void>>} filter
* @property {HookMap<SyncBailHook<[Comparator[], StatsFactoryContext], void>>} sort
* @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext, number, number], boolean | void>>} filterSorted
* @property {HookMap<SyncBailHook<[GroupConfig[], StatsFactoryContext], void>>} groupResults
* @property {HookMap<SyncBailHook<[Comparator[], StatsFactoryContext], void>>} sortResults
* @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext, number, number], boolean | void>>} filterResults
* @property {HookMap<SyncBailHook<[FactoryDataItem[], StatsFactoryContext], Result | void>>} merge
* @property {HookMap<SyncBailHook<[Result, StatsFactoryContext], Result>>} result
* @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext], string | void>>} getItemName
* @property {HookMap<SyncBailHook<[FactoryDataItem, StatsFactoryContext], StatsFactory | void>>} getItemFactory
*/
/**
* @template T
* @typedef {Map<string, T[]>} Caches
*/
class StatsFactory {
constructor() {
/** @type {StatsFactoryHooks} */
this.hooks = Object.freeze({
/** @type {HookMap<SyncBailHook<[object, any, StatsFactoryContext]>>} */
extract: new HookMap(
() => new SyncBailHook(["object", "data", "context"])
),
/** @type {HookMap<SyncBailHook<[any, StatsFactoryContext, number, number]>>} */
filter: new HookMap(
() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
),
/** @type {HookMap<SyncBailHook<[(function(any, any): number)[], StatsFactoryContext]>>} */
sort: new HookMap(() => new SyncBailHook(["comparators", "context"])),
/** @type {HookMap<SyncBailHook<[any, StatsFactoryContext, number, number]>>} */
filterSorted: new HookMap(
() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
),
/** @type {HookMap<SyncBailHook<[GroupConfig[], StatsFactoryContext]>>} */
groupResults: new HookMap(
() => new SyncBailHook(["groupConfigs", "context"])
),
/** @type {HookMap<SyncBailHook<[(function(any, any): number)[], StatsFactoryContext]>>} */
sortResults: new HookMap(
() => new SyncBailHook(["comparators", "context"])
),
/** @type {HookMap<SyncBailHook<[any, StatsFactoryContext, number, number]>>} */
filterResults: new HookMap(
() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
),
/** @type {HookMap<SyncBailHook<[any[], StatsFactoryContext]>>} */
merge: new HookMap(() => new SyncBailHook(["items", "context"])),
/** @type {HookMap<SyncBailHook<[any[], StatsFactoryContext]>>} */
result: new HookMap(() => new SyncWaterfallHook(["result", "context"])),
/** @type {HookMap<SyncBailHook<[any, StatsFactoryContext]>>} */
getItemName: new HookMap(() => new SyncBailHook(["item", "context"])),
/** @type {HookMap<SyncBailHook<[any, StatsFactoryContext]>>} */
getItemFactory: new HookMap(() => new SyncBailHook(["item", "context"]))
});
const hooks = this.hooks;
this._caches =
/** @type {Record<keyof typeof hooks, Map<string, SyncBailHook<[any[], StatsFactoryContext]>[]>>} */ ({});
this._caches = /** @type {TODO} */ ({});
for (const key of Object.keys(hooks)) {
this._caches[key] = new Map();
this._caches[/** @type {keyof StatsFactoryHooks} */ (key)] = new Map();
}
this._inCreate = false;
}
/**
* @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM
* @template {HM extends HookMap<infer H> ? H : never} H
* @param {HM} hookMap hook map
* @param {Caches<H>} cache cache
* @param {string} type type
* @returns {H[]} hooks
* @private
*/
_getAllLevelHooks(hookMap, cache, type) {
const cacheEntry = cache.get(type);
if (cacheEntry !== undefined) {
return cacheEntry;
}
const hooks = [];
const hooks = /** @type {H[]} */ ([]);
const typeParts = type.split(".");
for (let i = 0; i < typeParts.length; i++) {
const hook = hookMap.get(typeParts.slice(i).join("."));
const hook = /** @type {H} */ (hookMap.get(typeParts.slice(i).join(".")));
if (hook) {
hooks.push(hook);
}
@@ -96,27 +121,62 @@ class StatsFactory {
return hooks;
}
/**
* @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM
* @template {HM extends HookMap<infer H> ? H : never} H
* @template {H extends import("tapable").Hook<any, infer R> ? R : never} R
* @param {HM} hookMap hook map
* @param {Caches<H>} cache cache
* @param {string} type type
* @param {function(H): R | void} fn fn
* @returns {R | void} hook
* @private
*/
_forEachLevel(hookMap, cache, type, fn) {
for (const hook of this._getAllLevelHooks(hookMap, cache, type)) {
const result = fn(hook);
const result = fn(/** @type {H} */ (hook));
if (result !== undefined) return result;
}
}
/**
* @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM
* @template {HM extends HookMap<infer H> ? H : never} H
* @param {HM} hookMap hook map
* @param {Caches<H>} cache cache
* @param {string} type type
* @param {FactoryData} data data
* @param {function(H, FactoryData): FactoryData} fn fn
* @returns {FactoryData} data
* @private
*/
_forEachLevelWaterfall(hookMap, cache, type, data, fn) {
for (const hook of this._getAllLevelHooks(hookMap, cache, type)) {
data = fn(hook, data);
data = fn(/** @type {H} */ (hook), data);
}
return data;
}
/**
* @template {StatsFactoryHooks[keyof StatsFactoryHooks]} T
* @template {T extends HookMap<infer H> ? H : never} H
* @template {H extends import("tapable").Hook<any, infer R> ? R : never} R
* @param {T} hookMap hook map
* @param {Caches<H>} cache cache
* @param {string} type type
* @param {Array<FactoryData>} items items
* @param {function(H, R, number, number): R | undefined} fn fn
* @param {boolean} forceClone force clone
* @returns {R[]} result for each level
* @private
*/
_forEachLevelFilter(hookMap, cache, type, items, fn, forceClone) {
const hooks = this._getAllLevelHooks(hookMap, cache, type);
if (hooks.length === 0) return forceClone ? items.slice() : items;
let i = 0;
return items.filter((item, idx) => {
for (const hook of hooks) {
const r = fn(hook, item, idx, i);
const r = fn(/** @type {H} */ (hook), item, idx, i);
if (r !== undefined) {
if (r) i++;
return r;
@@ -129,30 +189,37 @@ class StatsFactory {
/**
* @param {string} type type
* @param {any} data factory data
* @param {FactoryData} data factory data
* @param {Omit<StatsFactoryContext, "type">} baseContext context used as base
* @returns {any} created object
* @returns {CreatedObject} created object
*/
create(type, data, baseContext) {
if (this._inCreate) {
return this._create(type, data, baseContext);
} else {
try {
this._inCreate = true;
return this._create(type, data, baseContext);
} finally {
for (const key of Object.keys(this._caches)) this._caches[key].clear();
this._inCreate = false;
}
}
try {
this._inCreate = true;
return this._create(type, data, baseContext);
} finally {
for (const key of Object.keys(this._caches))
this._caches[/** @type {keyof StatsFactoryHooks} */ (key)].clear();
this._inCreate = false;
}
}
/**
* @param {string} type type
* @param {FactoryData} data factory data
* @param {Omit<StatsFactoryContext, "type">} baseContext context used as base
* @returns {CreatedObject} created object
* @private
*/
_create(type, data, baseContext) {
const context = {
const context = /** @type {StatsFactoryContext} */ ({
...baseContext,
type,
[type]: data
};
});
if (Array.isArray(data)) {
// run filter on unsorted items
const items = this._forEachLevelFilter(
@@ -165,6 +232,7 @@ class StatsFactory {
);
// sort items
/** @type {Comparator[]} */
const comparators = [];
this._forEachLevel(this.hooks.sort, this._caches.sort, type, h =>
h.call(comparators, context)
@@ -188,6 +256,7 @@ class StatsFactory {
// for each item
let resultItems = items2.map((item, i) => {
/** @type {StatsFactoryContext} */
const itemContext = {
...context,
_index: i
@@ -217,6 +286,7 @@ class StatsFactory {
});
// sort result items
/** @type {Comparator[]} */
const comparators2 = [];
this._forEachLevel(
this.hooks.sortResults,
@@ -232,6 +302,7 @@ class StatsFactory {
}
// group result items
/** @type {GroupConfig[]} */
const groupConfigs = [];
this._forEachLevel(
this.hooks.groupResults,
@@ -270,23 +341,23 @@ class StatsFactory {
result,
(h, r) => h.call(r, context)
);
} else {
const object = {};
// run extract on value
this._forEachLevel(this.hooks.extract, this._caches.extract, type, h =>
h.call(object, data, context)
);
// run result on extracted object
return this._forEachLevelWaterfall(
this.hooks.result,
this._caches.result,
type,
object,
(h, r) => h.call(r, context)
);
}
/** @type {ObjectForExtract} */
const object = {};
// run extract on value
this._forEachLevel(this.hooks.extract, this._caches.extract, type, h =>
h.call(object, data, context)
);
// run result on extracted object
return this._forEachLevelWaterfall(
this.hooks.result,
this._caches.result,
type,
object,
(h, r) => h.call(r, context)
);
}
}
module.exports = StatsFactory;

View File

@@ -7,14 +7,18 @@
const { HookMap, SyncWaterfallHook, SyncBailHook } = require("tapable");
/** @template T @typedef {import("tapable").AsArray<T>} AsArray<T> */
/** @typedef {import("tapable").Hook} Hook */
/** @typedef {import("./DefaultStatsFactoryPlugin").StatsAsset} StatsAsset */
/** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunk} StatsChunk */
/** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunkGroup} StatsChunkGroup */
/** @typedef {import("./DefaultStatsFactoryPlugin").StatsCompilation} StatsCompilation */
/** @typedef {import("./DefaultStatsFactoryPlugin").StatsError} StatsError */
/** @typedef {import("./DefaultStatsFactoryPlugin").StatsLogging} StatsLogging */
/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModule} StatsModule */
/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleIssuer} StatsModuleIssuer */
/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleReason} StatsModuleReason */
/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleTraceDependency} StatsModuleTraceDependency */
/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleTraceItem} StatsModuleTraceItem */
/** @typedef {import("./DefaultStatsFactoryPlugin").StatsProfile} StatsProfile */
/**
* @typedef {object} PrintedElement
@@ -27,53 +31,78 @@ const { HookMap, SyncWaterfallHook, SyncBailHook } = require("tapable");
* @property {string=} type
* @property {StatsCompilation=} compilation
* @property {StatsChunkGroup=} chunkGroup
* @property {string=} chunkGroupKind
* @property {StatsAsset=} asset
* @property {StatsModule=} module
* @property {StatsChunk=} chunk
* @property {StatsModuleReason=} moduleReason
* @property {StatsModuleIssuer=} moduleIssuer
* @property {StatsError=} error
* @property {StatsProfile=} profile
* @property {StatsLogging=} logging
* @property {StatsModuleTraceItem=} moduleTraceItem
* @property {StatsModuleTraceDependency=} moduleTraceDependency
*/
/**
* @typedef {object} KnownStatsPrinterColorFn
* @property {(str: string) => string=} bold
* @property {(str: string) => string=} yellow
* @property {(str: string) => string=} red
* @property {(str: string) => string=} green
* @property {(str: string) => string=} magenta
* @property {(str: string) => string=} cyan
*/
/**
* @typedef {object} KnownStatsPrinterFormaters
* @property {(file: string, oversize?: boolean) => string=} formatFilename
* @property {(id: string) => string=} formatModuleId
* @property {(id: string, direction?: "parent"|"child"|"sibling") => string=} formatChunkId
* @property {(size: number) => string=} formatSize
* @property {(size: string) => string=} formatLayer
* @property {(dateTime: number) => string=} formatDateTime
* @property {(flag: string) => string=} formatFlag
* @property {(time: number, boldQuantity?: boolean) => string=} formatTime
* @property {string=} chunkGroupKind
* @property {(message: string) => string=} formatError
*/
/** @typedef {KnownStatsPrinterContext & Record<string, any>} StatsPrinterContext */
/** @typedef {Record<string, EXPECTED_ANY> & KnownStatsPrinterColorFn & KnownStatsPrinterFormaters & KnownStatsPrinterContext} StatsPrinterContext */
/** @typedef {any} PrintObject */
/**
* @typedef {object} StatsPrintHooks
* @property {HookMap<SyncBailHook<[string[], StatsPrinterContext], void>>} sortElements
* @property {HookMap<SyncBailHook<[PrintedElement[], StatsPrinterContext], string | void>>} printElements
* @property {HookMap<SyncBailHook<[PrintObject[], StatsPrinterContext], boolean | void>>} sortItems
* @property {HookMap<SyncBailHook<[PrintObject, StatsPrinterContext], string | void>>} getItemName
* @property {HookMap<SyncBailHook<[string[], StatsPrinterContext], string | void>>} printItems
* @property {HookMap<SyncBailHook<[PrintObject, StatsPrinterContext], string | void>>} print
* @property {HookMap<SyncWaterfallHook<[string, StatsPrinterContext]>>} result
*/
class StatsPrinter {
constructor() {
/** @type {StatsPrintHooks} */
this.hooks = Object.freeze({
/** @type {HookMap<SyncBailHook<[string[], StatsPrinterContext], true>>} */
sortElements: new HookMap(
() => new SyncBailHook(["elements", "context"])
),
/** @type {HookMap<SyncBailHook<[PrintedElement[], StatsPrinterContext], string>>} */
printElements: new HookMap(
() => new SyncBailHook(["printedElements", "context"])
),
/** @type {HookMap<SyncBailHook<[any[], StatsPrinterContext], true>>} */
sortItems: new HookMap(() => new SyncBailHook(["items", "context"])),
/** @type {HookMap<SyncBailHook<[any, StatsPrinterContext], string>>} */
getItemName: new HookMap(() => new SyncBailHook(["item", "context"])),
/** @type {HookMap<SyncBailHook<[string[], StatsPrinterContext], string>>} */
printItems: new HookMap(
() => new SyncBailHook(["printedItems", "context"])
),
/** @type {HookMap<SyncBailHook<[{}, StatsPrinterContext], string>>} */
print: new HookMap(() => new SyncBailHook(["object", "context"])),
/** @type {HookMap<SyncWaterfallHook<[string, StatsPrinterContext]>>} */
result: new HookMap(() => new SyncWaterfallHook(["result", "context"]))
});
/** @type {Map<HookMap<Hook>, Map<string, Hook[]>>} */
/**
* @type {TODO}
*/
this._levelHookCache = new Map();
this._inPrint = false;
}
@@ -81,15 +110,14 @@ class StatsPrinter {
/**
* get all level hooks
* @private
* @template {Hook} T
* @param {HookMap<T>} hookMap HookMap
* @template {StatsPrintHooks[keyof StatsPrintHooks]} HM
* @template {HM extends HookMap<infer H> ? H : never} H
* @param {HM} hookMap hook map
* @param {string} type type
* @returns {T[]} hooks
* @returns {H[]} hooks
*/
_getAllLevelHooks(hookMap, type) {
let cache = /** @type {Map<string, T[]>} */ (
this._levelHookCache.get(hookMap)
);
let cache = this._levelHookCache.get(hookMap);
if (cache === undefined) {
cache = new Map();
this._levelHookCache.set(hookMap, cache);
@@ -98,11 +126,11 @@ class StatsPrinter {
if (cacheEntry !== undefined) {
return cacheEntry;
}
/** @type {T[]} */
/** @type {H[]} */
const hooks = [];
const typeParts = type.split(".");
for (let i = 0; i < typeParts.length; i++) {
const hook = hookMap.get(typeParts.slice(i).join("."));
const hook = /** @type {H} */ (hookMap.get(typeParts.slice(i).join(".")));
if (hook) {
hooks.push(hook);
}
@@ -114,16 +142,17 @@ class StatsPrinter {
/**
* Run `fn` for each level
* @private
* @template T
* @template R
* @param {HookMap<SyncBailHook<T, R>>} hookMap HookMap
* @template {StatsPrintHooks[keyof StatsPrintHooks]} HM
* @template {HM extends HookMap<infer H> ? H : never} H
* @template {H extends import("tapable").Hook<any, infer R> ? R : never} R
* @param {HM} hookMap hook map
* @param {string} type type
* @param {(hook: SyncBailHook<T, R>) => R} fn function
* @returns {R} result of `fn`
* @param {function(H): R | void} fn fn
* @returns {R | void} hook
*/
_forEachLevel(hookMap, type, fn) {
for (const hook of this._getAllLevelHooks(hookMap, type)) {
const result = fn(hook);
const result = fn(/** @type {H} */ (hook));
if (result !== undefined) return result;
}
}
@@ -131,48 +160,49 @@ class StatsPrinter {
/**
* Run `fn` for each level
* @private
* @template T
* @param {HookMap<SyncWaterfallHook<T>>} hookMap HookMap
* @template {StatsPrintHooks[keyof StatsPrintHooks]} HM
* @template {HM extends HookMap<infer H> ? H : never} H
* @param {HM} hookMap hook map
* @param {string} type type
* @param {AsArray<T>[0]} data data
* @param {(hook: SyncWaterfallHook<T>, data: AsArray<T>[0]) => AsArray<T>[0]} fn function
* @returns {AsArray<T>[0]} result of `fn`
* @param {string} data data
* @param {function(H, string): string} fn fn
* @returns {string} result of `fn`
*/
_forEachLevelWaterfall(hookMap, type, data, fn) {
for (const hook of this._getAllLevelHooks(hookMap, type)) {
data = fn(hook, data);
data = fn(/** @type {H} */ (hook), data);
}
return data;
}
/**
* @param {string} type The type
* @param {object} object Object to print
* @param {object=} baseContext The base context
* @param {PrintObject} object Object to print
* @param {StatsPrinterContext=} baseContext The base context
* @returns {string} printed result
*/
print(type, object, baseContext) {
if (this._inPrint) {
return this._print(type, object, baseContext);
} else {
try {
this._inPrint = true;
return this._print(type, object, baseContext);
} finally {
this._levelHookCache.clear();
this._inPrint = false;
}
}
try {
this._inPrint = true;
return this._print(type, object, baseContext);
} finally {
this._levelHookCache.clear();
this._inPrint = false;
}
}
/**
* @private
* @param {string} type type
* @param {object} object object
* @param {object=} baseContext context
* @param {PrintObject} object object
* @param {StatsPrinterContext=} baseContext context
* @returns {string} printed result
*/
_print(type, object, baseContext) {
/** @type {StatsPrinterContext} */
const context = {
...baseContext,
type,
@@ -189,6 +219,7 @@ class StatsPrinter {
h.call(sortedItems, context)
);
const printedItems = sortedItems.map((item, i) => {
/** @type {StatsPrinterContext} */
const itemContext = {
...context,
_index: i
@@ -241,7 +272,7 @@ class StatsPrinter {
return this._forEachLevelWaterfall(
this.hooks.result,
type,
printResult,
/** @type {string} */ (printResult),
(h, r) => h.call(r, context)
);
}