webpack/lib/stats/DefaultStatsPrinterPlugin.js

1014 lines
29 KiB
JavaScript

/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("./StatsPrinter")} StatsPrinter */
/**
* @typedef {Object} UsualContext
* @property {string} type
* @property {Object} compilation
* @property {Object} asset
* @property {Object} module
* @property {Object} chunk
* @property {Object} moduleReason
* @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
* @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 {(flag: string) => string} formatFlag
* @property {(time: number) => string} formatTime
* @property {number} maxModuleId
* @property {string} chunkGroupKind
*/
const plural = (n, singular, plural) => (n === 1 ? singular : plural);
const printSizes = (sizes, { formatSize }) => {
const keys = Object.keys(sizes);
if (keys.length > 1) {
return keys.map(key => `${formatSize(sizes[key])} (${key})`).join(" ");
} else if (keys.length === 1) {
return formatSize(sizes[keys[0]]);
}
};
const isValidId = id => {
return typeof id === "number" || id;
};
/** @type {Record<string, (thing: any, context: UsualContext, printer: StatsPrinter) => string | void>} */
const SIMPLE_PRINTERS = {
"compilation.hash": (hash, { bold, type }) =>
type === "compilation.hash" ? `Hash: ${bold(hash)}` : undefined,
"compilation.version": (version, { bold, type }) =>
type === "compilation.version"
? `Version: webpack ${bold(version)}`
: undefined,
"compilation.time": (time, { bold }) => `Time: ${bold(time)}ms`,
"compilation.builtAt": (builtAt, { bold }) => {
const builtAtDate = new Date(builtAt);
return `Built at: ${builtAtDate.toLocaleDateString(undefined, {
day: "2-digit",
month: "2-digit",
year: "numeric"
})} ${bold(builtAtDate.toLocaleTimeString())}`;
},
"compilation.env": (env, { bold }) =>
env
? `Environment (--env): ${bold(JSON.stringify(env, null, 2))}`
: undefined,
"compilation.publicPath": (publicPath, { bold }) =>
`PublicPath: ${bold(publicPath || "(none)")}`,
"compilation.entrypoints": (entrypoints, context, printer) =>
Array.isArray(entrypoints)
? undefined
: printer.print(
context.type,
Object.values(entrypoints),
Object.assign({}, context, { chunkGroupKind: "Entrypoint" })
),
"compilation.namedChunkGroups": (namedChunkGroups, context, printer) => {
if (!Array.isArray(namedChunkGroups)) {
const {
compilation: { entrypoints }
} = context;
let chunkGroups = Object.values(namedChunkGroups);
if (entrypoints) {
chunkGroups = chunkGroups.filter(
group =>
!Object.prototype.hasOwnProperty.call(entrypoints, group.name)
);
}
return printer.print(
context.type,
chunkGroups,
Object.assign({}, context, { chunkGroupKind: "Chunk Group" })
);
}
},
"compilation.assetsByChunkName": () => "",
"compilation.modules": (modules, context) => {
let maxModuleId = 0;
for (const module of modules) {
if (typeof module.id === "number") {
if (maxModuleId < module.id) maxModuleId = module.id;
}
}
context.maxModuleId = maxModuleId;
},
"compilation.filteredModules": (
filteredModules,
{ compilation: { modules } }
) =>
filteredModules > 0
? ` ${
modules && modules.length > 0
? ` + ${filteredModules} hidden`
: filteredModules
} ${plural(filteredModules, "module", "modules")}`
: undefined,
"compilation.filteredAssets": (filteredAssets, { compilation: { assets } }) =>
filteredAssets > 0
? `${
assets && assets.length > 0
? ` + ${filteredAssets} hidden`
: filteredAssets
} ${plural(filteredAssets, "asset", "assets")}`
: undefined,
"compilation.children[].compilation.name": name =>
name ? `Child ${name}:` : "Child",
"asset.name": (name, { formatFilename, asset: { isOverSizeLimit } }) =>
formatFilename(name, isOverSizeLimit),
"asset.size": (
size,
{ asset: { isOverSizeLimit }, yellow, green, formatSize }
) => ((isOverSizeLimit ? yellow(formatSize(size)) : formatSize(size))),
"asset.emitted": (emitted, { green, formatFlag }) =>
emitted ? green(formatFlag("emitted")) : undefined,
"asset.isOverSizeLimit": (isOverSizeLimit, { yellow, formatFlag }) =>
isOverSizeLimit ? yellow(formatFlag("big")) : undefined,
assetChunk: (id, { formatChunkId }) => formatChunkId(id),
assetChunkName: name => name,
"module.id": (id, { formatModuleId }) =>
isValidId(id) ? formatModuleId(id) : undefined,
"module.name": (name, { bold }) => bold(name),
"module.identifier": identifier => identifier,
"module.sizes": printSizes,
"module.chunks[]": (id, { formatChunkId }) => formatChunkId(id),
"module.depth": (depth, { formatFlag }) =>
depth !== null ? formatFlag(`depth ${depth}`) : undefined,
"module.cacheable": (cacheable, { formatFlag, red }) =>
cacheable === false ? red(formatFlag("not cacheable")) : undefined,
"module.orphan": (orphan, { formatFlag, yellow }) =>
orphan ? yellow(formatFlag("orphan")) : undefined,
"module.runtime": (runtime, { formatFlag, yellow }) =>
runtime ? yellow(formatFlag("runtime")) : undefined,
"module.optional": (optional, { formatFlag, yellow }) =>
optional ? yellow(formatFlag("optional")) : undefined,
"module.built": (built, { formatFlag, green }) =>
built ? green(formatFlag("built")) : undefined,
"module.assets": (assets, { formatFlag, magenta }) =>
assets && assets.length
? magenta(
formatFlag(
`${assets.length} ${plural(assets.length, "asset", "assets")}`
)
)
: undefined,
"module.failed": (failed, { formatFlag, red }) =>
failed ? red(formatFlag("failed")) : undefined,
"module.warnings": (warnings, { formatFlag, yellow }) =>
warnings
? yellow(
formatFlag(`${warnings} ${plural(warnings, "warning", "warnings")}`)
)
: undefined,
"module.errors": (errors, { formatFlag, red }) =>
errors
? red(formatFlag(`${errors} ${plural(errors, "error", "errors")}`))
: undefined,
"module.providedExports": (providedExports, { formatFlag, cyan }) => {
if (Array.isArray(providedExports)) {
if (providedExports.length === 0) return cyan(formatFlag("no exports"));
return cyan(formatFlag(`exports: ${providedExports.join(", ")}`));
}
},
"module.usedExports": (usedExports, { formatFlag, cyan, module }) => {
if (usedExports !== true) {
if (usedExports === null) return cyan(formatFlag("used exports unknown"));
if (usedExports === false) return cyan(formatFlag("module unused"));
if (Array.isArray(usedExports)) {
if (usedExports.length === 0)
return cyan(formatFlag("no exports used"));
const providedExportsCount = Array.isArray(module.providedExports)
? module.providedExports.length
: null;
if (
providedExportsCount !== null &&
providedExportsCount === module.usedExports.length
) {
return cyan(formatFlag("all exports used"));
} else {
return cyan(
formatFlag(`only some exports used: ${usedExports.join(", ")}`)
);
}
}
}
},
"module.optimizationBailout[]": (optimizationBailout, { yellow }) =>
yellow(optimizationBailout),
"module.issuerPath": (issuerPath, { module }) =>
module.profile ? undefined : "",
"module.profile": profile => undefined,
"module.modules": (modules, context) => {
let maxModuleId = 0;
for (const module of modules) {
if (typeof module.id === "number") {
if (maxModuleId < module.id) maxModuleId = module.id;
}
}
context.maxModuleId = maxModuleId;
},
"module.filteredModules": (filteredModules, { compilation: { modules } }) =>
filteredModules > 0
? ` ${
modules && modules.length > 0
? ` + ${filteredModules} hidden`
: filteredModules
} nested ${plural(filteredModules, "module", "modules")}`
: undefined,
"module.separator!": () => "\n",
"moduleIssuer.id": (id, { formatModuleId }) => formatModuleId(id),
"moduleIssuer.profile.total": (value, { formatTime }) => formatTime(value),
"moduleReason.type": type => type,
"moduleReason.userRequest": (userRequest, { cyan }) => cyan(userRequest),
"moduleReason.moduleId": (moduleId, { formatModuleId }) =>
moduleId !== null ? formatModuleId(moduleId) : undefined,
"moduleReason.module": (module, { magenta }) => magenta(module),
"moduleReason.loc": loc => loc,
"moduleReason.explanation": (explanation, { cyan }) => cyan(explanation),
"moduleReason.resolvedModule": (module, { magenta }) => magenta(module),
"module.profile.total": (value, { formatTime }) => formatTime(value),
"module.profile.resolving": (value, { formatTime }) =>
`resolving: ${formatTime(value)}`,
"module.profile.restoring": (value, { formatTime }) =>
`restoring: ${formatTime(value)}`,
"module.profile.integration": (value, { formatTime }) =>
`integration: ${formatTime(value)}`,
"module.profile.building": (value, { formatTime }) =>
`building: ${formatTime(value)}`,
"module.profile.storing": (value, { formatTime }) =>
`storing: ${formatTime(value)}`,
"module.profile.additionalResolving": (value, { formatTime }) =>
value ? `additional resolving: ${formatTime(value)}` : undefined,
"module.profile.additionalIntegration": (value, { formatTime }) =>
value ? `additional integration: ${formatTime(value)}` : undefined,
"chunkGroup.kind!": (_, { chunkGroupKind }) => chunkGroupKind,
"chunkGroup.name": (name, { bold }) => bold(name),
"chunkGroup.isOverSizeLimit": (isOverSizeLimit, { formatFlag, yellow }) =>
isOverSizeLimit ? yellow(formatFlag("big")) : undefined,
"chunkGroup.separator!": () => "=",
"chunkGroup.assets[]": (asset, { green }) => green(asset),
"chunkGroup.childAssets": (childAssets, context, printer) =>
Array.isArray(childAssets)
? undefined
: printer.print(
context.type,
Object.keys(childAssets).map(key => ({
type: key,
children: childAssets[key]
})),
context
),
"chunkGroup.childAssets[].type": type => `${type}:`,
"chunkGroup.childAssets[].children[]": (file, { formatFilename }) =>
formatFilename(file),
"chunk.id": (id, { formatChunkId }) => formatChunkId(id),
"chunk.files[]": (file, { formatFilename }) => formatFilename(file),
"chunk.names[]": name => name,
"chunk.sizes": (sizes, context) => printSizes(sizes, context),
"chunk.parents[]": (parents, context) =>
context.formatChunkId(parents, "parent"),
"chunk.siblings[]": (siblings, context) =>
context.formatChunkId(siblings, "sibling"),
"chunk.children[]": (children, context) =>
context.formatChunkId(children, "child"),
"chunk.childrenByOrder": (childrenByOrder, context, printer) =>
Array.isArray(childrenByOrder)
? undefined
: printer.print(
context.type,
Object.keys(childrenByOrder).map(key => ({
type: key,
children: childrenByOrder[key]
})),
context
),
"chunk.childrenByOrder[].type": type => `${type}:`,
"chunk.childrenByOrder[].children[]": (id, { formatChunkId }) =>
isValidId(id) ? formatChunkId(id) : undefined,
"chunk.entry": (entry, { formatFlag, yellow }) =>
entry ? yellow(formatFlag("entry")) : undefined,
"chunk.initial": (initial, { formatFlag, yellow }) =>
initial ? yellow(formatFlag("initial")) : undefined,
"chunk.rendered": (rendered, { formatFlag, green }) =>
rendered ? green(formatFlag("rendered")) : undefined,
"chunk.recorded": (recorded, { formatFlag, green }) =>
recorded ? green(formatFlag("recorded")) : undefined,
"chunk.reason": (reason, { yellow }) =>
(reason ? yellow(reason) : undefined),
"chunk.rootModules": (modules, context) => {
let maxModuleId = 0;
for (const module of modules) {
if (typeof module.id === "number") {
if (maxModuleId < module.id) maxModuleId = module.id;
}
}
context.maxModuleId = maxModuleId;
},
"chunk.filteredRootModules": (
filteredRootModules,
{ chunk: { rootModules } }
) =>
filteredRootModules > 0
? ` ${
rootModules && rootModules.length > 0
? ` + ${filteredRootModules} hidden`
: filteredRootModules
} root ${plural(filteredRootModules, "module", "modules")}`
: undefined,
"chunk.nonRootModules": (
nonRootModules,
{ chunk: { filteredRootModules, rootModules } }
) =>
nonRootModules > 0
? ` ${
(rootModules && rootModules.length > 0) || filteredRootModules > 0
? ` + ${nonRootModules} hidden`
: nonRootModules
} dependent ${plural(nonRootModules, "module", "modules")}`
: undefined,
"chunk.modules": (modules, context) => {
let maxModuleId = 0;
for (const module of modules) {
if (typeof module.id === "number") {
if (maxModuleId < module.id) maxModuleId = module.id;
}
}
context.maxModuleId = maxModuleId;
},
"chunk.filteredModules": (filteredModules, { chunk: { modules } }) =>
filteredModules > 0
? ` ${
modules && modules.length > 0
? ` + ${filteredModules} hidden`
: filteredModules
} chunk ${plural(filteredModules, "module", "modules")}`
: undefined,
"chunk.separator!": () => "\n",
"chunkOrigin.request": request => request,
"chunkOrigin.moduleId": (moduleId, { formatModuleId }) =>
moduleId !== null && moduleId !== undefined
? formatModuleId(moduleId)
: undefined,
"chunkOrigin.moduleName": (moduleName, { bold }) => bold(moduleName),
"chunkOrigin.loc": loc => loc,
"error.compilerPath": (compilerPath, { bold }) =>
compilerPath ? bold(`(${compilerPath})`) : undefined,
"error.chunkId": (chunkId, { formatChunkId }) =>
isValidId(chunkId) ? formatChunkId(chunkId) : undefined,
"error.chunkEntry": (chunkEntry, { formatFlag }) =>
chunkEntry ? formatFlag("entry") : undefined,
"error.chunkInitial": (chunkInitial, { formatFlag }) =>
chunkInitial ? formatFlag("initial") : undefined,
"error.file": (file, { bold }) => bold(file),
"error.moduleName": (moduleName, { bold }) => {
return moduleName.includes("!")
? `${bold(moduleName.replace(/^(\s|\S)*!/, ""))} (${moduleName})`
: `${bold(moduleName)}`;
},
"error.loc": (loc, { green }) => green(loc),
"error.message": (message, { bold }) => bold(message),
"error.details": details => details,
"error.stack": stack => stack,
"error.missing[]": missing => `[${missing}]`,
"error.moduleTrace": moduleTrace => undefined,
"error.separator!": () => "\n",
"moduleTraceItem.originName": originName => originName,
"moduleTraceDependency.loc": loc => loc
};
/** @type {Record<string, string>} */
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.children[]": "compilation",
"asset.chunks[]": "assetChunk",
"asset.chunkNames[]": "assetChunkName",
"module.modules[]": "module",
"module.reasons[]": "moduleReason",
"module.issuerPath[]": "moduleIssuer",
"chunk.origins[]": "chunkOrigin",
"chunk.rootModules[]": "module",
"chunk.modules[]": "module",
"error.moduleTrace[]": "moduleTraceItem",
"moduleTraceItem.dependencies[]": "moduleTraceDependency"
};
const ERROR_PREFERED_ORDER = [
"compilerPath",
"chunkId",
"chunkEntry",
"chunkInitial",
"file",
"separator!",
"moduleName",
"loc",
"separator!",
"message",
"details",
"stack",
"separator!",
"missing",
"separator!",
"moduleTrace"
];
/** @type {Record<string, string[]>} */
const PREFERED_ORDERS = {
compilation: [
"name",
"hash",
"version",
"time",
"builtAt",
"env",
"publicPath",
"assets",
"filteredAssets",
"entrypoints",
"namedChunkGroups",
"chunks",
"modules",
"filteredModules",
"warnings",
"errors",
"children",
"needAdditionalPass"
],
asset: ["name", "size", "chunks", "emitted", "isOverSizeLimit", "chunkNames"],
chunkGroup: [
"kind!",
"name",
"isOverSizeLimit",
"separator!",
"assets",
"childAssets"
],
module: [
"id",
"name",
"identifier",
"sizes",
"chunks",
"depth",
"cacheable",
"orphan",
"runtime",
"optional",
"built",
"assets",
"failed",
"warnings",
"errors",
"separator!",
"providedExports",
"separator!",
"usedExports",
"separator!",
"optimizationBailout",
"separator!",
"reasons",
"separator!",
"issuerPath",
"profile",
"separator!",
"modules"
],
moduleReason: [
"type",
"userRequest",
"moduleId",
"module",
"resolvedModule",
"loc",
"explanation"
],
"module.profile": [
"total",
"separator!",
"resolving",
"restoring",
"integration",
"building",
"storing",
"additionalResolving",
"additionalIntegration"
],
chunk: [
"id",
"files",
"names",
"sizes",
"parents",
"siblings",
"children",
"childrenByOrder",
"entry",
"initial",
"rendered",
"recorded",
"reason",
"separator!",
"origins",
"separator!",
"rootModules",
"separator!",
"filteredRootModules",
"separator!",
"nonRootModules",
"separator!",
"modules",
"separator!",
"filteredModules"
],
chunkOrigin: ["request", "moduleId", "moduleName", "loc"],
error: ERROR_PREFERED_ORDER,
warning: ERROR_PREFERED_ORDER,
"chunk.childrenByOrder[]": ["type", "children"],
"chunkGroup.childAssets[]": ["type", "children"]
};
const itemsJoinOneLine = items => items.filter(Boolean).join(" ");
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;
/** @type {Record<string, (items: string[]) => string>} */
const SIMPLE_ITEMS_JOINER = {
"chunk.parents": itemsJoinOneLine,
"chunk.siblings": itemsJoinOneLine,
"chunk.children": itemsJoinOneLine,
"chunk.names": itemsJoinCommaBrackets,
"chunk.files": itemsJoinComma,
"chunk.childrenByOrder": itemsJoinOneLine,
"chunk.childrenByOrder[].children": itemsJoinOneLine,
"chunkGroup.assets": itemsJoinOneLine,
"chunkGroup.childAssets": itemsJoinOneLine,
"chunkGroup.childAssets[].children": itemsJoinOneLine,
"asset.chunks": itemsJoinComma,
"asset.chunkNames": itemsJoinComma,
"module.chunks": itemsJoinOneLine,
"module.issuerPath": items =>
items
.filter(Boolean)
.map(item => `${item} ->`)
.join(" "),
"compilation.errors": itemsJoinMoreSpacing,
"compilation.warnings": itemsJoinMoreSpacing,
"moduleTraceItem.dependencies": itemsJoinOneLine,
"compilation.children": items =>
items.map(item => indent(item, " ", true)).join("\n")
};
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;
return items
.map(item => {
if (!item.content) return;
let content = indent(item.content, indenter, !firstInLine);
if (firstInLine) {
content = content.replace(/^\n+/, "");
}
if (!content) return;
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, (items: ({ element: string, content: string })[], context: UsualContext) => 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 === "errors";
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("");
},
module: (items, { module, maxModuleId }) => {
let hasName = false;
let indenter = " ";
if (maxModuleId >= 10) indenter += " ";
if (maxModuleId >= 100) indenter += " ";
if (maxModuleId >= 1000) indenter += " ";
let prefix = "";
if (typeof module.id === "number") {
if (module.id < 1000 && maxModuleId >= 1000) prefix += " ";
if (module.id < 100 && maxModuleId >= 100) prefix += " ";
if (module.id < 10 && maxModuleId >= 10) prefix += " ";
} else if (typeof module.id === "string" && module.id !== "") {
if (maxModuleId >= 1000) prefix += " ";
if (maxModuleId >= 100) prefix += " ";
if (maxModuleId >= 10) prefix += " ";
}
return (
(prefix +
joinExplicitNewLine(
items.filter(item => {
switch (item.element) {
case "id":
if (module.id === module.name && item.content) hasName = true;
break;
case "name":
if (hasName) return false;
if (item.content) hasName = true;
break;
case "identifier":
if (hasName) return false;
if (item.content) hasName = true;
break;
}
return true;
}),
indenter
))
);
},
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: joinOneLine,
"chunkGroup.childAssets[]": items => `(${joinOneLine(items)})`,
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;
})
);
},
"module.profile": joinInBrackets,
moduleIssuer: joinOneLine,
chunkOrigin: items => " > " + joinOneLine(items),
"errors[].error": joinError(true),
"warnings[].error": joinError(false),
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}]`,
formatSize: require("../SizeFormatHelpers").formatSize,
formatTime: (time, { timeReference, bold, green, yellow, red }) => {
if (timeReference && time !== timeReference) {
const times = [
timeReference / 2,
timeReference / 4,
timeReference / 8,
timeReference / 16
];
if (time < times[3]) return `${time}ms`;
else if (time < times[2]) return bold(`${time}ms`);
else if (time < times[1]) return green(`${time}ms`);
else if (time < times[0]) return yellow(`${time}ms`);
else return red(`${time}ms`);
} else {
return `${time}ms`;
}
}
};
const RESULT_MODIFIER = {
"module.modules": result => {
return indent(result, "| ");
}
};
const createOrder = (array, preferedOrder) => {
const originalArray = array.slice();
const set = new Set(array);
const usedSet = new Set();
array.length = 0;
for (const element of preferedOrder) {
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;
};
const filterColors = value => value.replace(/\u001b\[\d+m/g, "");
const table = (array, align, splitter) => {
const rows = array.length;
const cols = array[0].length;
const colSizes = new Array(cols);
for (let col = 0; col < cols; col++) {
colSizes[col] = 0;
}
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const value = filterColors(`${array[row][col]}`);
if (value.length > colSizes[col]) {
colSizes[col] = value.length;
}
}
}
const lines = [];
for (let row = 0; row < rows; row++) {
let str = "";
for (let col = 0; col < cols; col++) {
const value = `${array[row][col]}`;
let l = filterColors(value).length;
if (align[col] === "l") {
str += value;
}
for (; l < colSizes[col] && col !== cols - 1; l++) {
str += " ";
}
if (align[col] === "r") {
str += value;
}
if (col + 1 < cols && colSizes[col] !== 0) {
str += splitter || " ";
}
}
lines.push(str.trimRight());
}
return lines.join("\n");
};
class DefaultStatsPrinterPlugin {
/**
* @param {Compiler} compiler webpack compiler
* @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}${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(PREFERED_ORDERS)) {
const preferedOrder = PREFERED_ORDERS[key];
stats.hooks.sortElements
.for(key)
.tap("DefaultStatsPrinterPlugin", (elements, context) => {
createOrder(elements, preferedOrder);
});
}
for (const key of Object.keys(ITEM_NAMES)) {
const itemName = ITEM_NAMES[key];
stats.hooks.getItemName
.for(key)
.tap("DefaultStatsPrinterPlugin", () => 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);
}
// Print assets as table
stats.hooks.printElements
.for("compilation.assets[].asset")
.tap("DefaultStatsPrinterPlugin", (elements, { bold }) => {
const elementsMap = elements.reduce(
(obj, e) => ((obj[e.element] = e.content), obj),
Object.create(null)
);
return [
elementsMap.name || "",
elementsMap.size || "",
elementsMap.chunks || "",
elementsMap.emitted || "",
elementsMap.isOverSizeLimit || "",
elementsMap.chunkNames || ""
];
});
stats.hooks.printItems
.for("compilation.assets")
.tap("DefaultStatsPrinterPlugin", (items, { bold }) => {
if (items.length === 0) return undefined;
let header = ["Asset", "Size", "Chunks", "", "", "Chunk Names"];
header = header.map(h => (h ? bold(h) : h));
return table([header].concat(items), "rrrlll");
});
}
);
});
}
}
module.exports = DefaultStatsPrinterPlugin;