${yellow(x)}`),
"loggingEntry(info).loggingEntry.message": (message, { green }) =>
mapLines(message, x => ` ${green(x)}`),
"loggingEntry(log).loggingEntry.message": (message, { bold }) =>
mapLines(message, x => ` ${bold(x)}`),
"loggingEntry(debug).loggingEntry.message": message =>
mapLines(message, x => ` ${x}`),
"loggingEntry(trace).loggingEntry.message": message =>
mapLines(message, x => ` ${x}`),
"loggingEntry(status).loggingEntry.message": (message, { magenta }) =>
mapLines(message, x => ` ${magenta(x)}`),
"loggingEntry(profile).loggingEntry.message": (message, { magenta }) =>
mapLines(message, x => ` ${magenta(x)}`),
"loggingEntry(profileEnd).loggingEntry.message": (message, { magenta }) =>
mapLines(message, x => `
${magenta(x)}`),
"loggingEntry(time).loggingEntry.message": (message, { magenta }) =>
mapLines(message, x => ` ${magenta(x)}`),
"loggingEntry(group).loggingEntry.message": (message, { cyan }) =>
mapLines(message, x => `<-> ${cyan(x)}`),
"loggingEntry(groupCollapsed).loggingEntry.message": (message, { cyan }) =>
mapLines(message, x => `<+> ${cyan(x)}`),
"loggingEntry(clear).loggingEntry": () => " -------",
"loggingEntry(groupCollapsed).loggingEntry.children": () => "",
"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,
"moduleTraceDependency.loc": loc => loc
};
/** @type {Record} */
const ITEM_NAMES = {
"compilation.assets[]": "asset",
"compilation.modules[]": "module",
"compilation.chunks[]": "chunk",
"compilation.entrypoints[]": "chunkGroup",
"compilation.namedChunkGroups[]": "chunkGroup",
"compilation.errors[]": "error",
"compilation.warnings[]": "error",
"compilation.logging[]": "loggingGroup",
"compilation.children[]": "compilation",
"asset.related[]": "asset",
"asset.children[]": "asset",
"asset.chunks[]": "assetChunk",
"asset.auxiliaryChunks[]": "assetChunk",
"asset.chunkNames[]": "assetChunkName",
"asset.chunkIdHints[]": "assetChunkIdHint",
"asset.auxiliaryChunkNames[]": "assetChunkName",
"asset.auxiliaryChunkIdHints[]": "assetChunkIdHint",
"chunkGroup.assets[]": "chunkGroupAsset",
"chunkGroup.auxiliaryAssets[]": "chunkGroupAsset",
"chunkGroupChild.assets[]": "chunkGroupAsset",
"chunkGroupChild.auxiliaryAssets[]": "chunkGroupAsset",
"chunkGroup.children[]": "chunkGroupChildGroup",
"chunkGroupChildGroup.children[]": "chunkGroupChild",
"module.modules[]": "module",
"module.children[]": "module",
"module.reasons[]": "moduleReason",
"moduleReason.children[]": "moduleReason",
"module.issuerPath[]": "moduleIssuer",
"chunk.origins[]": "chunkOrigin",
"chunk.modules[]": "module",
"loggingGroup.entries[]": logEntry =>
`loggingEntry(${logEntry.type}).loggingEntry`,
"loggingEntry.children[]": logEntry =>
`loggingEntry(${logEntry.type}).loggingEntry`,
"error.moduleTrace[]": "moduleTraceItem",
"moduleTraceItem.dependencies[]": "moduleTraceDependency"
};
const ERROR_PREFERRED_ORDER = [
"compilerPath",
"chunkId",
"chunkEntry",
"chunkInitial",
"file",
"separator!",
"moduleName",
"loc",
"separator!",
"message",
"separator!",
"details",
"separator!",
"stack",
"separator!",
"missing",
"separator!",
"moduleTrace"
];
/** @type {Record} */
const PREFERRED_ORDERS = {
compilation: [
"name",
"hash",
"version",
"time",
"builtAt",
"env",
"publicPath",
"assets",
"filteredAssets",
"entrypoints",
"namedChunkGroups",
"chunks",
"modules",
"filteredModules",
"children",
"logging",
"warnings",
"warningsInChildren!",
"filteredWarningDetailsCount",
"errors",
"errorsInChildren!",
"filteredErrorDetailsCount",
"summary!",
"needAdditionalPass"
],
asset: [
"type",
"name",
"size",
"chunks",
"auxiliaryChunks",
"emitted",
"comparedForEmit",
"cached",
"info",
"isOverSizeLimit",
"chunkNames",
"auxiliaryChunkNames",
"chunkIdHints",
"auxiliaryChunkIdHints",
"related",
"filteredRelated",
"children",
"filteredChildren"
],
"asset.info": [
"immutable",
"sourceFilename",
"javascriptModule",
"development",
"hotModuleReplacement"
],
chunkGroup: [
"kind!",
"name",
"isOverSizeLimit",
"assetsSize",
"auxiliaryAssetsSize",
"is!",
"assets",
"filteredAssets",
"auxiliaryAssets",
"filteredAuxiliaryAssets",
"separator!",
"children"
],
chunkGroupAsset: ["name", "size"],
chunkGroupChildGroup: ["type", "children"],
chunkGroupChild: ["assets", "chunks", "name"],
module: [
"type",
"name",
"identifier",
"id",
"layer",
"sizes",
"chunks",
"depth",
"cacheable",
"orphan",
"runtime",
"optional",
"dependent",
"built",
"codeGenerated",
"cached",
"assets",
"failed",
"warnings",
"errors",
"children",
"filteredChildren",
"providedExports",
"usedExports",
"optimizationBailout",
"reasons",
"filteredReasons",
"issuerPath",
"profile",
"modules",
"filteredModules"
],
moduleReason: [
"active",
"type",
"userRequest",
"moduleId",
"module",
"resolvedModule",
"loc",
"explanation",
"children",
"filteredChildren"
],
"module.profile": [
"total",
"separator!",
"resolving",
"restoring",
"integration",
"building",
"storing",
"additionalResolving",
"additionalIntegration"
],
chunk: [
"id",
"runtime",
"files",
"names",
"idHints",
"sizes",
"parents",
"siblings",
"children",
"childrenByOrder",
"entry",
"initial",
"rendered",
"recorded",
"reason",
"separator!",
"origins",
"separator!",
"modules",
"separator!",
"filteredModules"
],
chunkOrigin: ["request", "moduleId", "moduleName", "loc"],
error: ERROR_PREFERRED_ORDER,
warning: ERROR_PREFERRED_ORDER,
"chunk.childrenByOrder[]": ["type", "children"],
loggingGroup: [
"debug",
"name",
"separator!",
"entries",
"separator!",
"filteredEntries"
],
loggingEntry: ["message", "trace", "children"]
};
const itemsJoinOneLine = items => items.filter(Boolean).join(" ");
const itemsJoinOneLineBrackets = items =>
items.length > 0 ? `(${items.filter(Boolean).join(" ")})` : undefined;
const itemsJoinMoreSpacing = items => items.filter(Boolean).join("\n\n");
const itemsJoinComma = items => items.filter(Boolean).join(", ");
const itemsJoinCommaBrackets = items =>
items.length > 0 ? `(${items.filter(Boolean).join(", ")})` : undefined;
const itemsJoinCommaBracketsWithName = name => items =>
items.length > 0
? `(${name}: ${items.filter(Boolean).join(", ")})`
: undefined;
/** @type {Record string>} */
const SIMPLE_ITEMS_JOINER = {
"chunk.parents": itemsJoinOneLine,
"chunk.siblings": itemsJoinOneLine,
"chunk.children": itemsJoinOneLine,
"chunk.names": itemsJoinCommaBrackets,
"chunk.idHints": itemsJoinCommaBracketsWithName("id hint"),
"chunk.runtime": itemsJoinCommaBracketsWithName("runtime"),
"chunk.files": itemsJoinComma,
"chunk.childrenByOrder": itemsJoinOneLine,
"chunk.childrenByOrder[].children": itemsJoinOneLine,
"chunkGroup.assets": itemsJoinOneLine,
"chunkGroup.auxiliaryAssets": itemsJoinOneLineBrackets,
"chunkGroupChildGroup.children": itemsJoinComma,
"chunkGroupChild.assets": itemsJoinOneLine,
"chunkGroupChild.auxiliaryAssets": itemsJoinOneLineBrackets,
"asset.chunks": itemsJoinComma,
"asset.auxiliaryChunks": itemsJoinCommaBrackets,
"asset.chunkNames": itemsJoinCommaBracketsWithName("name"),
"asset.auxiliaryChunkNames": itemsJoinCommaBracketsWithName("auxiliary name"),
"asset.chunkIdHints": itemsJoinCommaBracketsWithName("id hint"),
"asset.auxiliaryChunkIdHints":
itemsJoinCommaBracketsWithName("auxiliary id hint"),
"module.chunks": itemsJoinOneLine,
"module.issuerPath": items =>
items
.filter(Boolean)
.map(item => `${item} ->`)
.join(" "),
"compilation.errors": itemsJoinMoreSpacing,
"compilation.warnings": itemsJoinMoreSpacing,
"compilation.logging": itemsJoinMoreSpacing,
"compilation.children": items => indent(itemsJoinMoreSpacing(items), " "),
"moduleTraceItem.dependencies": itemsJoinOneLine,
"loggingEntry.children": items =>
indent(items.filter(Boolean).join("\n"), " ", false)
};
const joinOneLine = items =>
items
.map(item => item.content)
.filter(Boolean)
.join(" ");
const joinInBrackets = items => {
const res = [];
let mode = 0;
for (const item of items) {
if (item.element === "separator!") {
switch (mode) {
case 0:
case 1:
mode += 2;
break;
case 4:
res.push(")");
mode = 3;
break;
}
}
if (!item.content) continue;
switch (mode) {
case 0:
mode = 1;
break;
case 1:
res.push(" ");
break;
case 2:
res.push("(");
mode = 4;
break;
case 3:
res.push(" (");
mode = 4;
break;
case 4:
res.push(", ");
break;
}
res.push(item.content);
}
if (mode === 4) res.push(")");
return res.join("");
};
const indent = (str, prefix, noPrefixInFirstLine) => {
const rem = str.replace(/\n([^\n])/g, "\n" + prefix + "$1");
if (noPrefixInFirstLine) return rem;
const ind = str[0] === "\n" ? "" : prefix;
return ind + rem;
};
const joinExplicitNewLine = (items, indenter) => {
let firstInLine = true;
let first = true;
return items
.map(item => {
if (!item || !item.content) return;
let content = indent(item.content, first ? "" : indenter, !firstInLine);
if (firstInLine) {
content = content.replace(/^\n+/, "");
}
if (!content) return;
first = false;
const noJoiner = firstInLine || content.startsWith("\n");
firstInLine = content.endsWith("\n");
return noJoiner ? content : " " + content;
})
.filter(Boolean)
.join("")
.trim();
};
const joinError =
error =>
(items, { red, yellow }) =>
`${error ? red("ERROR") : yellow("WARNING")} in ${joinExplicitNewLine(
items,
""
)}`;
/** @type {Record string>} */
const SIMPLE_ELEMENT_JOINERS = {
compilation: items => {
const result = [];
let lastNeedMore = false;
for (const item of items) {
if (!item.content) continue;
const needMoreSpace =
item.element === "warnings" ||
item.element === "filteredWarningDetailsCount" ||
item.element === "errors" ||
item.element === "filteredErrorDetailsCount" ||
item.element === "logging";
if (result.length !== 0) {
result.push(needMoreSpace || lastNeedMore ? "\n\n" : "\n");
}
result.push(item.content);
lastNeedMore = needMoreSpace;
}
if (lastNeedMore) result.push("\n");
return result.join("");
},
asset: items =>
joinExplicitNewLine(
items.map(item => {
if (
(item.element === "related" || item.element === "children") &&
item.content
) {
return {
...item,
content: `\n${item.content}\n`
};
}
return item;
}),
" "
),
"asset.info": joinOneLine,
module: (items, { module }) => {
let hasName = false;
return joinExplicitNewLine(
items.map(item => {
switch (item.element) {
case "id":
if (module.id === module.name) {
if (hasName) return false;
if (item.content) hasName = true;
}
break;
case "name":
if (hasName) return false;
if (item.content) hasName = true;
break;
case "providedExports":
case "usedExports":
case "optimizationBailout":
case "reasons":
case "issuerPath":
case "profile":
case "children":
case "modules":
if (item.content) {
return {
...item,
content: `\n${item.content}\n`
};
}
break;
}
return item;
}),
" "
);
},
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;
}),
" "
)
);
},
"chunk.childrenByOrder[]": items => `(${joinOneLine(items)})`,
chunkGroup: items => joinExplicitNewLine(items, " "),
chunkGroupAsset: joinOneLine,
chunkGroupChildGroup: joinOneLine,
chunkGroupChild: joinOneLine,
// moduleReason: (items, { moduleReason }) => {
// let hasName = false;
// return joinOneLine(
// items.filter(item => {
// switch (item.element) {
// case "moduleId":
// if (moduleReason.moduleId === moduleReason.module && item.content)
// hasName = true;
// break;
// case "module":
// if (hasName) return false;
// break;
// case "resolvedModule":
// return (
// moduleReason.module !== moduleReason.resolvedModule &&
// item.content
// );
// }
// return true;
// })
// );
// },
moduleReason: (items, { moduleReason }) => {
let hasName = false;
return joinExplicitNewLine(
items.map(item => {
switch (item.element) {
case "moduleId":
if (moduleReason.moduleId === moduleReason.module && item.content)
hasName = true;
break;
case "module":
if (hasName) return false;
break;
case "resolvedModule":
if (moduleReason.module === moduleReason.resolvedModule)
return false;
break;
case "children":
if (item.content) {
return {
...item,
content: `\n${item.content}\n`
};
}
break;
}
return item;
}),
" "
);
},
"module.profile": joinInBrackets,
moduleIssuer: joinOneLine,
chunkOrigin: items => "> " + joinOneLine(items),
"errors[].error": joinError(true),
"warnings[].error": joinError(false),
loggingGroup: items => joinExplicitNewLine(items, "").trimEnd(),
moduleTraceItem: items => " @ " + joinOneLine(items),
moduleTraceDependency: joinOneLine
};
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"
};
const AVAILABLE_FORMATS = {
formatChunkId: (id, { yellow }, direction) => {
switch (direction) {
case "parent":
return `<{${yellow(id)}}>`;
case "sibling":
return `={${yellow(id)}}=`;
case "child":
return `>{${yellow(id)}}<`;
default:
return `{${yellow(id)}}`;
}
},
formatModuleId: id => `[${id}]`,
formatFilename: (filename, { green, yellow }, oversize) =>
(oversize ? yellow : green)(filename),
formatFlag: flag => `[${flag}]`,
formatLayer: layer => `(in ${layer})`,
formatSize: require("../SizeFormatHelpers").formatSize,
formatDateTime: (dateTime, { bold }) => {
const d = new Date(dateTime);
const x = twoDigit;
const date = `${d.getFullYear()}-${x(d.getMonth() + 1)}-${x(d.getDate())}`;
const time = `${x(d.getHours())}:${x(d.getMinutes())}:${x(d.getSeconds())}`;
return `${date} ${bold(time)}`;
},
formatTime: (
time,
{ timeReference, bold, green, yellow, red },
boldQuantity
) => {
const unit = " ms";
if (timeReference && time !== timeReference) {
const times = [
timeReference / 2,
timeReference / 4,
timeReference / 8,
timeReference / 16
];
if (time < times[3]) return `${time}${unit}`;
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}`;
}
},
formatError: (message, { green, yellow, red }) => {
if (message.includes("\u001b[")) return message;
const highlights = [
{ regExp: /(Did you mean .+)/g, format: green },
{
regExp: /(Set 'mode' option to 'development' or 'production')/g,
format: green
},
{ regExp: /(\(module has no exports\))/g, format: red },
{ regExp: /\(possible exports: (.+)\)/g, format: green },
{ regExp: /(?:^|\n)(.* doesn't exist)/g, format: red },
{ regExp: /('\w+' option has not been set)/g, format: red },
{
regExp: /(Emitted value instead of an instance of Error)/g,
format: yellow
},
{ regExp: /(Used? .+ instead)/gi, format: yellow },
{ regExp: /\b(deprecated|must|required)\b/g, format: yellow },
{
regExp: /\b(BREAKING CHANGE)\b/gi,
format: red
},
{
regExp:
/\b(error|failed|unexpected|invalid|not found|not supported|not available|not possible|not implemented|doesn't support|conflict|conflicting|not existing|duplicate)\b/gi,
format: red
}
];
for (const { regExp, format } of highlights) {
message = message.replace(regExp, (match, content) => {
return match.replace(content, format(content));
});
}
return message;
}
};
const RESULT_MODIFIER = {
"module.modules": result => {
return indent(result, "| ");
}
};
const createOrder = (array, preferredOrder) => {
const originalArray = array.slice();
const set = new Set(array);
const usedSet = new Set();
array.length = 0;
for (const element of preferredOrder) {
if (element.endsWith("!") || set.has(element)) {
array.push(element);
usedSet.add(element);
}
}
for (const element of originalArray) {
if (!usedSet.has(element)) {
array.push(element);
}
}
return array;
};
class DefaultStatsPrinterPlugin {
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap("DefaultStatsPrinterPlugin", compilation => {
compilation.hooks.statsPrinter.tap(
"DefaultStatsPrinterPlugin",
(stats, options, context) => {
// Put colors into context
stats.hooks.print
.for("compilation")
.tap("DefaultStatsPrinterPlugin", (compilation, context) => {
for (const color of Object.keys(AVAILABLE_COLORS)) {
let start;
if (options.colors) {
if (
typeof options.colors === "object" &&
typeof options.colors[color] === "string"
) {
start = options.colors[color];
} else {
start = AVAILABLE_COLORS[color];
}
}
if (start) {
context[color] = str =>
`${start}${
typeof str === "string"
? str.replace(
/((\u001b\[39m|\u001b\[22m|\u001b\[0m)+)/g,
`$1${start}`
)
: str
}\u001b[39m\u001b[22m`;
} else {
context[color] = str => str;
}
}
for (const format of Object.keys(AVAILABLE_FORMATS)) {
context[format] = (content, ...args) =>
AVAILABLE_FORMATS[format](content, context, ...args);
}
context.timeReference = compilation.time;
});
for (const key of Object.keys(SIMPLE_PRINTERS)) {
stats.hooks.print
.for(key)
.tap("DefaultStatsPrinterPlugin", (obj, ctx) =>
SIMPLE_PRINTERS[key](obj, ctx, stats)
);
}
for (const key of Object.keys(PREFERRED_ORDERS)) {
const preferredOrder = PREFERRED_ORDERS[key];
stats.hooks.sortElements
.for(key)
.tap("DefaultStatsPrinterPlugin", (elements, context) => {
createOrder(elements, preferredOrder);
});
}
for (const key of Object.keys(ITEM_NAMES)) {
const itemName = ITEM_NAMES[key];
stats.hooks.getItemName
.for(key)
.tap(
"DefaultStatsPrinterPlugin",
typeof itemName === "string" ? () => itemName : itemName
);
}
for (const key of Object.keys(SIMPLE_ITEMS_JOINER)) {
const joiner = SIMPLE_ITEMS_JOINER[key];
stats.hooks.printItems
.for(key)
.tap("DefaultStatsPrinterPlugin", joiner);
}
for (const key of Object.keys(SIMPLE_ELEMENT_JOINERS)) {
const joiner = SIMPLE_ELEMENT_JOINERS[key];
stats.hooks.printElements
.for(key)
.tap("DefaultStatsPrinterPlugin", joiner);
}
for (const key of Object.keys(RESULT_MODIFIER)) {
const modifier = RESULT_MODIFIER[key];
stats.hooks.result
.for(key)
.tap("DefaultStatsPrinterPlugin", modifier);
}
}
);
});
}
}
module.exports = DefaultStatsPrinterPlugin;