code:
/*
	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 {import("./StatsPrinter").StatsPrinterContext} StatsPrinterContext */

const DATA_URI_CONTENT_LENGTH = 16;
const MAX_MODULE_IDENTIFIER_LENGTH = 80;

/**
 * @param {number} n a number
 * @param {string} singular singular
 * @param {string} plural plural
 * @returns {string} if n is 1, singular, else plural
 */
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
 */
const printSizes = (sizes, { formatSize = n => `${n}` }) => {
	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]]);
	}
};

/**
 * @param {string} resource resource
 * @returns {string} resource name for display
 */
const getResourceName = resource => {
	const dataUrl = /^data:[^,]+,/.exec(resource);
	if (!dataUrl) return resource;

	const len = dataUrl[0].length + DATA_URI_CONTENT_LENGTH;
	if (resource.length < len) return resource;
	return `${resource.slice(
		0,
		Math.min(resource.length - /* '..'.length */ 2, len)
	)}..`;
};

/**
 * @param {string} name module name
 * @returns {[string,string]} prefix and module name
 */
const getModuleName = name => {
	const [, prefix, resource] = /^(.*!)?([^!]*)$/.exec(name);

	if (resource.length > MAX_MODULE_IDENTIFIER_LENGTH) {
		const truncatedResource = `${resource.slice(
			0,
			Math.min(
				resource.length - /* '...(truncated)'.length */ 14,
				MAX_MODULE_IDENTIFIER_LENGTH
			)
		)}...(truncated)`;

		return [prefix, getResourceName(truncatedResource)];
	}

	return [prefix, getResourceName(resource)];
};

/**
 * @param {string} str string
 * @param {function(string): string} fn function to apply to each line
 * @returns {string} joined string
 */
const mapLines = (str, fn) => str.split("\n").map(fn).join("\n");

/**
 * @param {number} n a number
 * @returns {string} number as two digit string, leading 0
 */
const twoDigit = n => (n >= 10 ? `${n}` : `0${n}`);

const isValidId = id => {
	return typeof id === "number" || id;
};

/**
 * @template T
 * @param {Array<T>} 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}`;
};

/** @type {Record<string, (thing: any, context: StatsPrinterContext, printer: StatsPrinter) => string | void>} */
const SIMPLE_PRINTERS = {
	"compilation.summary!": (
		_,
		{
			type,
			bold,
			green,
			red,
			yellow,
			formatDateTime,
			formatTime,
			compilation: {
				name,
				hash,
				version,
				time,
				builtAt,
				errorsCount,
				warningsCount
			}
		}
	) => {
		const root = type === "compilation.summary!";
		const warningsMessage =
			warningsCount > 0
				? yellow(
						`${warningsCount} ${plural(warningsCount, "warning", "warnings")}`
					)
				: "";
		const errorsMessage =
			errorsCount > 0
				? red(`${errorsCount} ${plural(errorsCount, "error", "errors")}`)
				: "";
		const timeMessage = root && time ? ` in ${formatTime(time)}` : "";
		const hashMessage = hash ? ` (${hash})` : "";
		const builtAtMessage =
			root && builtAt ? `${formatDateTime(builtAt)}: ` : "";
		const versionMessage = root && version ? `webpack ${version}` : "";
		const nameMessage =
			root && name
				? bold(name)
				: name
					? `Child ${bold(name)}`
					: root
						? ""
						: "Child";
		const subjectMessage =
			nameMessage && versionMessage
				? `${nameMessage} (${versionMessage})`
				: versionMessage || nameMessage || "webpack";
		let statusMessage;
		if (errorsMessage && warningsMessage) {
			statusMessage = `compiled with ${errorsMessage} and ${warningsMessage}`;
		} else if (errorsMessage) {
			statusMessage = `compiled with ${errorsMessage}`;
		} else if (warningsMessage) {
			statusMessage = `compiled with ${warningsMessage}`;
		} else if (errorsCount === 0 && warningsCount === 0) {
			statusMessage = `compiled ${green("successfully")}`;
		} else {
			statusMessage = `compiled`;
		}
		if (
			builtAtMessage ||
			versionMessage ||
			errorsMessage ||
			warningsMessage ||
			(errorsCount === 0 && warningsCount === 0) ||
			timeMessage ||
			hashMessage
		)
			return `${builtAtMessage}${subjectMessage} ${statusMessage}${timeMessage}${hashMessage}`;
	},
	"compilation.filteredWarningDetailsCount": count =>
		count
			? `${count} ${plural(
					count,
					"warning has",
					"warnings have"
				)} detailed information that is not shown.\nUse 'stats.errorDetails: true' resp. '--stats-error-details' to show it.`
			: undefined,
	"compilation.filteredErrorDetailsCount": (count, { yellow }) =>
		count
			? yellow(
					`${count} ${plural(
						count,
						"error has",
						"errors have"
					)} detailed information that is not shown.\nUse 'stats.errorDetails: true' resp. '--stats-error-details' to show it.`
				)
			: undefined,
	"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), {
					...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, {
				...context,
				chunkGroupKind: "Chunk Group"
			});
		}
	},
	"compilation.assetsByChunkName": () => "",

	"compilation.filteredModules": (
		filteredModules,
		{ compilation: { modules } }
	) =>
		filteredModules > 0
			? `${moreCount(modules, filteredModules)} ${plural(
					filteredModules,
					"module",
					"modules"
				)}`
			: undefined,
	"compilation.filteredAssets": (
		filteredAssets,
		{ compilation: { assets } }
	) =>
		filteredAssets > 0
			? `${moreCount(assets, filteredAssets)} ${plural(
					filteredAssets,
					"asset",
					"assets"
				)}`
			: undefined,
	"compilation.logging": (logging, context, printer) =>
		Array.isArray(logging)
			? undefined
			: printer.print(
					context.type,
					Object.entries(logging).map(([name, value]) => ({ ...value, name })),
					context
				),
	"compilation.warningsInChildren!": (_, { yellow, compilation }) => {
		if (
			!compilation.children &&
			compilation.warningsCount > 0 &&
			compilation.warnings
		) {
			const childWarnings =
				compilation.warningsCount - compilation.warnings.length;
			if (childWarnings > 0) {
				return yellow(
					`${childWarnings} ${plural(
						childWarnings,
						"WARNING",
						"WARNINGS"
					)} in child compilations${
						compilation.children
							? ""
							: " (Use 'stats.children: true' resp. '--stats-children' for more details)"
					}`
				);
			}
		}
	},
	"compilation.errorsInChildren!": (_, { red, compilation }) => {
		if (
			!compilation.children &&
			compilation.errorsCount > 0 &&
			compilation.errors
		) {
			const childErrors = compilation.errorsCount - compilation.errors.length;
			if (childErrors > 0) {
				return red(
					`${childErrors} ${plural(
						childErrors,
						"ERROR",
						"ERRORS"
					)} in child compilations${
						compilation.children
							? ""
							: " (Use 'stats.children: true' resp. '--stats-children' for more details)"
					}`
				);
			}
		}
	},

	"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.emitted": (emitted, { green, formatFlag }) =>
		emitted ? green(formatFlag("emitted")) : undefined,
	"asset.comparedForEmit": (comparedForEmit, { yellow, formatFlag }) =>
		comparedForEmit ? yellow(formatFlag("compared for emit")) : undefined,
	"asset.cached": (cached, { green, formatFlag }) =>
		cached ? green(formatFlag("cached")) : undefined,
	"asset.isOverSizeLimit": (isOverSizeLimit, { yellow, formatFlag }) =>
		isOverSizeLimit ? yellow(formatFlag("big")) : undefined,

	"asset.info.immutable": (immutable, { green, formatFlag }) =>
		immutable ? green(formatFlag("immutable")) : undefined,
	"asset.info.javascriptModule": (javascriptModule, { formatFlag }) =>
		javascriptModule ? formatFlag("javascript module") : undefined,
	"asset.info.sourceFilename": (sourceFilename, { formatFlag }) =>
		sourceFilename
			? formatFlag(
					sourceFilename === true
						? "from source file"
						: `from: ${sourceFilename}`
				)
			: undefined,
	"asset.info.development": (development, { green, formatFlag }) =>
		development ? green(formatFlag("dev")) : undefined,
	"asset.info.hotModuleReplacement": (
		hotModuleReplacement,
		{ green, formatFlag }
	) => (hotModuleReplacement ? green(formatFlag("hmr")) : undefined),
	"asset.separator!": () => "\n",
	"asset.filteredRelated": (filteredRelated, { asset: { related } }) =>
		filteredRelated > 0
			? `${moreCount(related, filteredRelated)} related ${plural(
					filteredRelated,
					"asset",
					"assets"
				)}`
			: undefined,
	"asset.filteredChildren": (filteredChildren, { asset: { children } }) =>
		filteredChildren > 0
			? `${moreCount(children, filteredChildren)} ${plural(
					filteredChildren,
					"asset",
					"assets"
				)}`
			: undefined,

	assetChunk: (id, { formatChunkId }) => formatChunkId(id),

	assetChunkName: name => name,
	assetChunkIdHint: name => name,

	"module.type": type => (type !== "module" ? type : undefined),
	"module.id": (id, { formatModuleId }) =>
		isValidId(id) ? formatModuleId(id) : undefined,
	"module.name": (name, { bold }) => {
		const [prefix, resource] = getModuleName(name);
		return `${prefix || ""}${bold(resource || "")}`;
	},
	"module.identifier": identifier => undefined,
	"module.layer": (layer, { formatLayer }) =>
		layer ? formatLayer(layer) : undefined,
	"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.dependent": (dependent, { formatFlag, cyan }) =>
		dependent ? cyan(formatFlag("dependent")) : undefined,
	"module.built": (built, { formatFlag, yellow }) =>
		built ? yellow(formatFlag("built")) : undefined,
	"module.codeGenerated": (codeGenerated, { formatFlag, yellow }) =>
		codeGenerated ? yellow(formatFlag("code generated")) : undefined,
	"module.buildTimeExecuted": (buildTimeExecuted, { formatFlag, green }) =>
		buildTimeExecuted ? green(formatFlag("build time executed")) : undefined,
	"module.cached": (cached, { formatFlag, green }) =>
		cached ? green(formatFlag("cached")) : undefined,
	"module.assets": (assets, { formatFlag, magenta }) =>
		assets && assets.length
			? magenta(
					formatFlag(
						`${assets.length} ${plural(assets.length, "asset", "assets")}`
					)
				)
			: undefined,
	"module.warnings": (warnings, { formatFlag, yellow }) =>
		warnings === true
			? yellow(formatFlag("warnings"))
			: warnings
				? yellow(
						formatFlag(`${warnings} ${plural(warnings, "warning", "warnings")}`)
					)
				: undefined,
	"module.errors": (errors, { formatFlag, red }) =>
		errors === true
			? red(formatFlag("errors"))
			: 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 === 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.filteredModules": (filteredModules, { module: { modules } }) =>
		filteredModules > 0
			? `${moreCount(modules, filteredModules)} nested ${plural(
					filteredModules,
					"module",
					"modules"
				)}`
			: undefined,
	"module.filteredReasons": (filteredReasons, { module: { reasons } }) =>
		filteredReasons > 0
			? `${moreCount(reasons, filteredReasons)} ${plural(
					filteredReasons,
					"reason",
					"reasons"
				)}`
			: undefined,
	"module.filteredChildren": (filteredChildren, { module: { children } }) =>
		filteredChildren > 0
			? `${moreCount(children, filteredChildren)} ${plural(
					filteredChildren,
					"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(getResourceName(userRequest)),
	"moduleReason.moduleId": (moduleId, { formatModuleId }) =>
		isValidId(moduleId) ? formatModuleId(moduleId) : undefined,
	"moduleReason.module": (module, { magenta }) => magenta(module),
	"moduleReason.loc": loc => loc,
	"moduleReason.explanation": (explanation, { cyan }) => cyan(explanation),
	"moduleReason.active": (active, { formatFlag }) =>
		active ? undefined : formatFlag("inactive"),
	"moduleReason.resolvedModule": (module, { magenta }) => magenta(module),
	"moduleReason.filteredChildren": (
		filteredChildren,
		{ moduleReason: { children } }
	) =>
		filteredChildren > 0
			? `${moreCount(children, filteredChildren)} ${plural(
					filteredChildren,
					"reason",
					"reasons"
				)}`
			: undefined,

	"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.separator!": () => "\n",
	"chunkGroup.name": (name, { bold }) => bold(name),
	"chunkGroup.isOverSizeLimit": (isOverSizeLimit, { formatFlag, yellow }) =>
		isOverSizeLimit ? yellow(formatFlag("big")) : undefined,
	"chunkGroup.assetsSize": (size, { formatSize }) =>
		size ? formatSize(size) : undefined,
	"chunkGroup.auxiliaryAssetsSize": (size, { formatSize }) =>
		size ? `(${formatSize(size)})` : undefined,
	"chunkGroup.filteredAssets": (n, { chunkGroup: { assets } }) =>
		n > 0
			? `${moreCount(assets, n)} ${plural(n, "asset", "assets")}`
			: undefined,
	"chunkGroup.filteredAuxiliaryAssets": (
		n,
		{ chunkGroup: { auxiliaryAssets } }
	) =>
		n > 0
			? `${moreCount(auxiliaryAssets, n)} auxiliary ${plural(
					n,
					"asset",
					"assets"
				)}`
			: undefined,
	"chunkGroup.is!": () => "=",
	"chunkGroupAsset.name": (asset, { green }) => green(asset),
	"chunkGroupAsset.size": (size, { formatSize, chunkGroup }) =>
		chunkGroup.assets.length > 1 ||
		(chunkGroup.auxiliaryAssets && chunkGroup.auxiliaryAssets.length > 0)
			? formatSize(size)
			: undefined,
	"chunkGroup.children": (children, context, printer) =>
		Array.isArray(children)
			? undefined
			: printer.print(
					context.type,
					Object.keys(children).map(key => ({
						type: key,
						children: children[key]
					})),
					context
				),
	"chunkGroupChildGroup.type": type => `${type}:`,
	"chunkGroupChild.assets[]": (file, { formatFilename }) =>
		formatFilename(file),
	"chunkGroupChild.chunks[]": (id, { formatChunkId }) => formatChunkId(id),
	"chunkGroupChild.name": name => (name ? `(name: ${name})` : undefined),

	"chunk.id": (id, { formatChunkId }) => formatChunkId(id),
	"chunk.files[]": (file, { formatFilename }) => formatFilename(file),
	"chunk.names[]": name => name,
	"chunk.idHints[]": name => name,
	"chunk.runtime[]": 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.filteredModules": (filteredModules, { chunk: { modules } }) =>
		filteredModules > 0
			? `${moreCount(modules, filteredModules)} chunk ${plural(
					filteredModules,
					"module",
					"modules"
				)}`
			: undefined,
	"chunk.separator!": () => "\n",

	"chunkOrigin.request": request => request,
	"chunkOrigin.moduleId": (moduleId, { formatModuleId }) =>
		isValidId(moduleId) ? 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, formatError }) =>
		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",

	"loggingEntry(error).loggingEntry.message": (message, { red }) =>
		mapLines(message, x => `<e> ${red(x)}`),
	"loggingEntry(warn).loggingEntry.message": (message, { yellow }) =>
		mapLines(message, x => `<w> ${yellow(x)}`),
	"loggingEntry(info).loggingEntry.message": (message, { green }) =>
		mapLines(message, x => `<i> ${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 => `<s> ${magenta(x)}`),
	"loggingEntry(profile).loggingEntry.message": (message, { magenta }) =>
		mapLines(message, x => `<p> ${magenta(x)}`),
	"loggingEntry(profileEnd).loggingEntry.message": (message, { magenta }) =>
		mapLines(message, x => `</p> ${magenta(x)}`),
	"loggingEntry(time).loggingEntry.message": (message, { magenta }) =>
		mapLines(message, x => `<t> ${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<string, string | Function>} */
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!",
	"filteredDetails",
	"separator!",
	"stack",
	"separator!",
	"missing",
	"separator!",
	"moduleTrace"
];

/** @type {Record<string, string[]>} */
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, (items: string[]) => 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, (items: ({ element: string, content: string })[], context: StatsPrinterContext) => 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;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const { HookMap, SyncBailHook, SyncWaterfallHook } = require("tapable");
const { concatComparators, keepOriginalOrder } = require("../util/comparators");
const smartGrouping = require("../util/smartGrouping");

/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../WebpackError")} WebpackError */
/** @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
 */

/** @typedef {KnownStatsFactoryContext & Record<string, any>} StatsFactoryContext */

class StatsFactory {
	constructor() {
		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]>[]>>} */ ({});
		for (const key of Object.keys(hooks)) {
			this._caches[key] = new Map();
		}
		this._inCreate = false;
	}

	_getAllLevelHooks(hookMap, cache, type) {
		const cacheEntry = cache.get(type);
		if (cacheEntry !== undefined) {
			return cacheEntry;
		}
		const hooks = [];
		const typeParts = type.split(".");
		for (let i = 0; i < typeParts.length; i++) {
			const hook = hookMap.get(typeParts.slice(i).join("."));
			if (hook) {
				hooks.push(hook);
			}
		}
		cache.set(type, hooks);
		return hooks;
	}

	_forEachLevel(hookMap, cache, type, fn) {
		for (const hook of this._getAllLevelHooks(hookMap, cache, type)) {
			const result = fn(hook);
			if (result !== undefined) return result;
		}
	}

	_forEachLevelWaterfall(hookMap, cache, type, data, fn) {
		for (const hook of this._getAllLevelHooks(hookMap, cache, type)) {
			data = fn(hook, data);
		}
		return data;
	}

	_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);
				if (r !== undefined) {
					if (r) i++;
					return r;
				}
			}
			i++;
			return true;
		});
	}

	/**
	 * @param {string} type type
	 * @param {any} data factory data
	 * @param {Omit<StatsFactoryContext, "type">} baseContext context used as base
	 * @returns {any} 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;
			}
		}
	}

	_create(type, data, baseContext) {
		const context = {
			...baseContext,
			type,
			[type]: data
		};
		if (Array.isArray(data)) {
			// run filter on unsorted items
			const items = this._forEachLevelFilter(
				this.hooks.filter,
				this._caches.filter,
				type,
				data,
				(h, r, idx, i) => h.call(r, context, idx, i),
				true
			);

			// sort items
			const comparators = [];
			this._forEachLevel(this.hooks.sort, this._caches.sort, type, h =>
				h.call(comparators, context)
			);
			if (comparators.length > 0) {
				items.sort(
					// @ts-expect-error number of arguments is correct
					concatComparators(...comparators, keepOriginalOrder(items))
				);
			}

			// run filter on sorted items
			const items2 = this._forEachLevelFilter(
				this.hooks.filterSorted,
				this._caches.filterSorted,
				type,
				items,
				(h, r, idx, i) => h.call(r, context, idx, i),
				false
			);

			// for each item
			let resultItems = items2.map((item, i) => {
				const itemContext = {
					...context,
					_index: i
				};

				// run getItemName
				const itemName = this._forEachLevel(
					this.hooks.getItemName,
					this._caches.getItemName,
					`${type}[]`,
					h => h.call(item, itemContext)
				);
				if (itemName) itemContext[itemName] = item;
				const innerType = itemName ? `${type}[].${itemName}` : `${type}[]`;

				// run getItemFactory
				const itemFactory =
					this._forEachLevel(
						this.hooks.getItemFactory,
						this._caches.getItemFactory,
						innerType,
						h => h.call(item, itemContext)
					) || this;

				// run item factory
				return itemFactory.create(innerType, item, itemContext);
			});

			// sort result items
			const comparators2 = [];
			this._forEachLevel(
				this.hooks.sortResults,
				this._caches.sortResults,
				type,
				h => h.call(comparators2, context)
			);
			if (comparators2.length > 0) {
				resultItems.sort(
					// @ts-expect-error number of arguments is correct
					concatComparators(...comparators2, keepOriginalOrder(resultItems))
				);
			}

			// group result items
			const groupConfigs = [];
			this._forEachLevel(
				this.hooks.groupResults,
				this._caches.groupResults,
				type,
				h => h.call(groupConfigs, context)
			);
			if (groupConfigs.length > 0) {
				resultItems = smartGrouping(resultItems, groupConfigs);
			}

			// run filter on sorted result items
			const finalResultItems = this._forEachLevelFilter(
				this.hooks.filterResults,
				this._caches.filterResults,
				type,
				resultItems,
				(h, r, idx, i) => h.call(r, context, idx, i),
				false
			);

			// run merge on mapped items
			let result = this._forEachLevel(
				this.hooks.merge,
				this._caches.merge,
				type,
				h => h.call(finalResultItems, context)
			);
			if (result === undefined) result = finalResultItems;

			// run result on merged items
			return this._forEachLevelWaterfall(
				this.hooks.result,
				this._caches.result,
				type,
				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)
			);
		}
	}
}
module.exports = StatsFactory;

license: mit


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

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").StatsModule} StatsModule */
/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleReason} StatsModuleReason */

/**
 * @typedef {object} PrintedElement
 * @property {string} element
 * @property {string} content
 */

/**
 * @typedef {object} KnownStatsPrinterContext
 * @property {string=} type
 * @property {StatsCompilation=} compilation
 * @property {StatsChunkGroup=} chunkGroup
 * @property {StatsAsset=} asset
 * @property {StatsModule=} module
 * @property {StatsChunk=} chunk
 * @property {StatsModuleReason=} 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 {(dateTime: number) => string=} formatDateTime
 * @property {(flag: string) => string=} formatFlag
 * @property {(time: number, boldQuantity?: boolean) => string=} formatTime
 * @property {string=} chunkGroupKind
 */

/** @typedef {KnownStatsPrinterContext & Record<string, any>} StatsPrinterContext */

class StatsPrinter {
	constructor() {
		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[]>>} */
		this._levelHookCache = new Map();
		this._inPrint = false;
	}

	/**
	 * get all level hooks
	 * @private
	 * @template {Hook} T
	 * @param {HookMap<T>} hookMap HookMap
	 * @param {string} type type
	 * @returns {T[]} hooks
	 */
	_getAllLevelHooks(hookMap, type) {
		let cache = /** @type {Map<string, T[]>} */ (
			this._levelHookCache.get(hookMap)
		);
		if (cache === undefined) {
			cache = new Map();
			this._levelHookCache.set(hookMap, cache);
		}
		const cacheEntry = cache.get(type);
		if (cacheEntry !== undefined) {
			return cacheEntry;
		}
		/** @type {T[]} */
		const hooks = [];
		const typeParts = type.split(".");
		for (let i = 0; i < typeParts.length; i++) {
			const hook = hookMap.get(typeParts.slice(i).join("."));
			if (hook) {
				hooks.push(hook);
			}
		}
		cache.set(type, hooks);
		return hooks;
	}

	/**
	 * Run `fn` for each level
	 * @private
	 * @template T
	 * @template R
	 * @param {HookMap<SyncBailHook<T, R>>} hookMap HookMap
	 * @param {string} type type
	 * @param {(hook: SyncBailHook<T, R>) => R} fn function
	 * @returns {R} result of `fn`
	 */
	_forEachLevel(hookMap, type, fn) {
		for (const hook of this._getAllLevelHooks(hookMap, type)) {
			const result = fn(hook);
			if (result !== undefined) return result;
		}
	}

	/**
	 * Run `fn` for each level
	 * @private
	 * @template T
	 * @param {HookMap<SyncWaterfallHook<T>>} hookMap HookMap
	 * @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`
	 */
	_forEachLevelWaterfall(hookMap, type, data, fn) {
		for (const hook of this._getAllLevelHooks(hookMap, type)) {
			data = fn(hook, data);
		}
		return data;
	}

	/**
	 * @param {string} type The type
	 * @param {object} object Object to print
	 * @param {object=} 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;
			}
		}
	}

	/**
	 * @private
	 * @param {string} type type
	 * @param {object} object object
	 * @param {object=} baseContext context
	 * @returns {string} printed result
	 */
	_print(type, object, baseContext) {
		const context = {
			...baseContext,
			type,
			[type]: object
		};

		let printResult = this._forEachLevel(this.hooks.print, type, hook =>
			hook.call(object, context)
		);
		if (printResult === undefined) {
			if (Array.isArray(object)) {
				const sortedItems = object.slice();
				this._forEachLevel(this.hooks.sortItems, type, h =>
					h.call(sortedItems, context)
				);
				const printedItems = sortedItems.map((item, i) => {
					const itemContext = {
						...context,
						_index: i
					};
					const itemName = this._forEachLevel(
						this.hooks.getItemName,
						`${type}[]`,
						h => h.call(item, itemContext)
					);
					if (itemName) itemContext[itemName] = item;
					return this.print(
						itemName ? `${type}[].${itemName}` : `${type}[]`,
						item,
						itemContext
					);
				});
				printResult = this._forEachLevel(this.hooks.printItems, type, h =>
					h.call(printedItems, context)
				);
				if (printResult === undefined) {
					const result = printedItems.filter(Boolean);
					if (result.length > 0) printResult = result.join("\n");
				}
			} else if (object !== null && typeof object === "object") {
				const elements = Object.keys(object).filter(
					key => object[key] !== undefined
				);
				this._forEachLevel(this.hooks.sortElements, type, h =>
					h.call(elements, context)
				);
				const printedElements = elements.map(element => {
					const content = this.print(`${type}.${element}`, object[element], {
						...context,
						_parent: object,
						_element: element,
						[element]: object[element]
					});
					return { element, content };
				});
				printResult = this._forEachLevel(this.hooks.printElements, type, h =>
					h.call(printedElements, context)
				);
				if (printResult === undefined) {
					const result = printedElements.map(e => e.content).filter(Boolean);
					if (result.length > 0) printResult = result.join("\n");
				}
			}
		}

		return this._forEachLevelWaterfall(
			this.hooks.result,
			type,
			printResult,
			(h, r) => h.call(r, context)
		);
	}
}
module.exports = StatsPrinter;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

/**
 * Compare two arrays or strings by performing strict equality check for each value.
 * @template T [T=any]
 * @param {ArrayLike<T>} a Array of values to be compared
 * @param {ArrayLike<T>} b Array of values to be compared
 * @returns {boolean} returns true if all the elements of passed arrays are strictly equal.
 */

exports.equals = (a, b) => {
	if (a.length !== b.length) return false;
	for (let i = 0; i < a.length; i++) {
		if (a[i] !== b[i]) return false;
	}
	return true;
};

/**
 * Partition an array by calling a predicate function on each value.
 * @template T [T=any]
 * @param {Array<T>} arr Array of values to be partitioned
 * @param {(value: T) => boolean} fn Partition function which partitions based on truthiness of result.
 * @returns {[Array<T>, Array<T>]} returns the values of `arr` partitioned into two new arrays based on fn predicate.
 */
exports.groupBy = (arr = [], fn) => {
	return arr.reduce(
		/**
		 * @param {[Array<T>, Array<T>]} groups An accumulator storing already partitioned values returned from previous call.
		 * @param {T} value The value of the current element
		 * @returns {[Array<T>, Array<T>]} returns an array of partitioned groups accumulator resulting from calling a predicate on the current value.
		 */
		(groups, value) => {
			groups[fn(value) ? 0 : 1].push(value);
			return groups;
		},
		[[], []]
	);
};


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

/**
 * @template T
 */
class ArrayQueue {
	/**
	 * @param {Iterable<T>=} items The initial elements.
	 */
	constructor(items) {
		/**
		 * @private
		 * @type {T[]}
		 */
		this._list = items ? Array.from(items) : [];
		/**
		 * @private
		 * @type {T[]}
		 */
		this._listReversed = [];
	}

	/**
	 * Returns the number of elements in this queue.
	 * @returns {number} The number of elements in this queue.
	 */
	get length() {
		return this._list.length + this._listReversed.length;
	}

	/**
	 * Empties the queue.
	 */
	clear() {
		this._list.length = 0;
		this._listReversed.length = 0;
	}

	/**
	 * Appends the specified element to this queue.
	 * @param {T} item The element to add.
	 * @returns {void}
	 */
	enqueue(item) {
		this._list.push(item);
	}

	/**
	 * Retrieves and removes the head of this queue.
	 * @returns {T | undefined} The head of the queue of `undefined` if this queue is empty.
	 */
	dequeue() {
		if (this._listReversed.length === 0) {
			if (this._list.length === 0) return undefined;
			if (this._list.length === 1) return this._list.pop();
			if (this._list.length < 16) return this._list.shift();
			const temp = this._listReversed;
			this._listReversed = this._list;
			this._listReversed.reverse();
			this._list = temp;
		}
		return this._listReversed.pop();
	}

	/**
	 * Finds and removes an item
	 * @param {T} item the item
	 * @returns {void}
	 */
	delete(item) {
		const i = this._list.indexOf(item);
		if (i >= 0) {
			this._list.splice(i, 1);
		} else {
			const i = this._listReversed.indexOf(item);
			if (i >= 0) this._listReversed.splice(i, 1);
		}
	}

	[Symbol.iterator]() {
		return {
			next: () => {
				const item = this.dequeue();
				if (item) {
					return {
						done: false,
						value: item
					};
				}
				return {
					done: true,
					value: undefined
				};
			}
		};
	}
}

module.exports = ArrayQueue;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const { SyncHook, AsyncSeriesHook } = require("tapable");
const { makeWebpackError } = require("../HookWebpackError");
const WebpackError = require("../WebpackError");
const ArrayQueue = require("./ArrayQueue");

const QUEUED_STATE = 0;
const PROCESSING_STATE = 1;
const DONE_STATE = 2;

let inHandleResult = 0;

/**
 * @template T
 * @callback Callback
 * @param {(WebpackError | null)=} err
 * @param {T=} result
 */

/**
 * @template T
 * @template K
 * @template R
 */
class AsyncQueueEntry {
	/**
	 * @param {T} item the item
	 * @param {Callback<R>} callback the callback
	 */
	constructor(item, callback) {
		this.item = item;
		/** @type {typeof QUEUED_STATE | typeof PROCESSING_STATE | typeof DONE_STATE} */
		this.state = QUEUED_STATE;
		this.callback = callback;
		/** @type {Callback<R>[] | undefined} */
		this.callbacks = undefined;
		this.result = undefined;
		/** @type {WebpackError | undefined} */
		this.error = undefined;
	}
}

/**
 * @template T
 * @template K
 * @template R
 */
class AsyncQueue {
	/**
	 * @param {object} options options object
	 * @param {string=} options.name name of the queue
	 * @param {number=} options.parallelism how many items should be processed at once
	 * @param {AsyncQueue<any, any, any>=} options.parent parent queue, which will have priority over this queue and with shared parallelism
	 * @param {function(T): K=} options.getKey extract key from item
	 * @param {function(T, Callback<R>): void} options.processor async function to process items
	 */
	constructor({ name, parallelism, parent, processor, getKey }) {
		this._name = name;
		this._parallelism = parallelism || 1;
		this._processor = processor;
		this._getKey =
			getKey || /** @type {(T) => K} */ (item => /** @type {any} */ (item));
		/** @type {Map<K, AsyncQueueEntry<T, K, R>>} */
		this._entries = new Map();
		/** @type {ArrayQueue<AsyncQueueEntry<T, K, R>>} */
		this._queued = new ArrayQueue();
		/** @type {AsyncQueue<any, any, any>[] | undefined} */
		this._children = undefined;
		this._activeTasks = 0;
		this._willEnsureProcessing = false;
		this._needProcessing = false;
		this._stopped = false;
		this._root = parent ? parent._root : this;
		if (parent) {
			if (this._root._children === undefined) {
				this._root._children = [this];
			} else {
				this._root._children.push(this);
			}
		}

		this.hooks = {
			/** @type {AsyncSeriesHook<[T]>} */
			beforeAdd: new AsyncSeriesHook(["item"]),
			/** @type {SyncHook<[T]>} */
			added: new SyncHook(["item"]),
			/** @type {AsyncSeriesHook<[T]>} */
			beforeStart: new AsyncSeriesHook(["item"]),
			/** @type {SyncHook<[T]>} */
			started: new SyncHook(["item"]),
			/** @type {SyncHook<[T, Error, R]>} */
			result: new SyncHook(["item", "error", "result"])
		};

		this._ensureProcessing = this._ensureProcessing.bind(this);
	}

	/**
	 * @param {T} item an item
	 * @param {Callback<R>} callback callback function
	 * @returns {void}
	 */
	add(item, callback) {
		if (this._stopped) return callback(new WebpackError("Queue was stopped"));
		this.hooks.beforeAdd.callAsync(item, err => {
			if (err) {
				callback(
					makeWebpackError(err, `AsyncQueue(${this._name}).hooks.beforeAdd`)
				);
				return;
			}
			const key = this._getKey(item);
			const entry = this._entries.get(key);
			if (entry !== undefined) {
				if (entry.state === DONE_STATE) {
					if (inHandleResult++ > 3) {
						process.nextTick(() => callback(entry.error, entry.result));
					} else {
						callback(entry.error, entry.result);
					}
					inHandleResult--;
				} else if (entry.callbacks === undefined) {
					entry.callbacks = [callback];
				} else {
					entry.callbacks.push(callback);
				}
				return;
			}
			const newEntry = new AsyncQueueEntry(item, callback);
			if (this._stopped) {
				this.hooks.added.call(item);
				this._root._activeTasks++;
				process.nextTick(() =>
					this._handleResult(newEntry, new WebpackError("Queue was stopped"))
				);
			} else {
				this._entries.set(key, newEntry);
				this._queued.enqueue(newEntry);
				const root = this._root;
				root._needProcessing = true;
				if (root._willEnsureProcessing === false) {
					root._willEnsureProcessing = true;
					setImmediate(root._ensureProcessing);
				}
				this.hooks.added.call(item);
			}
		});
	}

	/**
	 * @param {T} item an item
	 * @returns {void}
	 */
	invalidate(item) {
		const key = this._getKey(item);
		const entry =
			/** @type {AsyncQueueEntry<T, K, R>} */
			(this._entries.get(key));
		this._entries.delete(key);
		if (entry.state === QUEUED_STATE) {
			this._queued.delete(entry);
		}
	}

	/**
	 * Waits for an already started item
	 * @param {T} item an item
	 * @param {Callback<R>} callback callback function
	 * @returns {void}
	 */
	waitFor(item, callback) {
		const key = this._getKey(item);
		const entry = this._entries.get(key);
		if (entry === undefined) {
			return callback(
				new WebpackError(
					"waitFor can only be called for an already started item"
				)
			);
		}
		if (entry.state === DONE_STATE) {
			process.nextTick(() => callback(entry.error, entry.result));
		} else if (entry.callbacks === undefined) {
			entry.callbacks = [callback];
		} else {
			entry.callbacks.push(callback);
		}
	}

	/**
	 * @returns {void}
	 */
	stop() {
		this._stopped = true;
		const queue = this._queued;
		this._queued = new ArrayQueue();
		const root = this._root;
		for (const entry of queue) {
			this._entries.delete(this._getKey(entry.item));
			root._activeTasks++;
			this._handleResult(entry, new WebpackError("Queue was stopped"));
		}
	}

	/**
	 * @returns {void}
	 */
	increaseParallelism() {
		const root = this._root;
		root._parallelism++;
		/* istanbul ignore next */
		if (root._willEnsureProcessing === false && root._needProcessing) {
			root._willEnsureProcessing = true;
			setImmediate(root._ensureProcessing);
		}
	}

	/**
	 * @returns {void}
	 */
	decreaseParallelism() {
		const root = this._root;
		root._parallelism--;
	}

	/**
	 * @param {T} item an item
	 * @returns {boolean} true, if the item is currently being processed
	 */
	isProcessing(item) {
		const key = this._getKey(item);
		const entry = this._entries.get(key);
		return entry !== undefined && entry.state === PROCESSING_STATE;
	}

	/**
	 * @param {T} item an item
	 * @returns {boolean} true, if the item is currently queued
	 */
	isQueued(item) {
		const key = this._getKey(item);
		const entry = this._entries.get(key);
		return entry !== undefined && entry.state === QUEUED_STATE;
	}

	/**
	 * @param {T} item an item
	 * @returns {boolean} true, if the item is currently queued
	 */
	isDone(item) {
		const key = this._getKey(item);
		const entry = this._entries.get(key);
		return entry !== undefined && entry.state === DONE_STATE;
	}

	/**
	 * @returns {void}
	 */
	_ensureProcessing() {
		while (this._activeTasks < this._parallelism) {
			const entry = this._queued.dequeue();
			if (entry === undefined) break;
			this._activeTasks++;
			entry.state = PROCESSING_STATE;
			this._startProcessing(entry);
		}
		this._willEnsureProcessing = false;
		if (this._queued.length > 0) return;
		if (this._children !== undefined) {
			for (const child of this._children) {
				while (this._activeTasks < this._parallelism) {
					const entry = child._queued.dequeue();
					if (entry === undefined) break;
					this._activeTasks++;
					entry.state = PROCESSING_STATE;
					child._startProcessing(entry);
				}
				if (child._queued.length > 0) return;
			}
		}
		if (!this._willEnsureProcessing) this._needProcessing = false;
	}

	/**
	 * @param {AsyncQueueEntry<T, K, R>} entry the entry
	 * @returns {void}
	 */
	_startProcessing(entry) {
		this.hooks.beforeStart.callAsync(entry.item, err => {
			if (err) {
				this._handleResult(
					entry,
					makeWebpackError(err, `AsyncQueue(${this._name}).hooks.beforeStart`)
				);
				return;
			}
			let inCallback = false;
			try {
				this._processor(entry.item, (e, r) => {
					inCallback = true;
					this._handleResult(entry, e, r);
				});
			} catch (err) {
				if (inCallback) throw err;
				this._handleResult(entry, err, null);
			}
			this.hooks.started.call(entry.item);
		});
	}

	/**
	 * @param {AsyncQueueEntry<T, K, R>} entry the entry
	 * @param {WebpackError=} err error, if any
	 * @param {R=} result result, if any
	 * @returns {void}
	 */
	_handleResult(entry, err, result) {
		this.hooks.result.callAsync(entry.item, err, result, hookError => {
			const error = hookError
				? makeWebpackError(hookError, `AsyncQueue(${this._name}).hooks.result`)
				: err;

			const callback = entry.callback;
			const callbacks = entry.callbacks;
			entry.state = DONE_STATE;
			entry.callback = undefined;
			entry.callbacks = undefined;
			entry.result = result;
			entry.error = error;

			const root = this._root;
			root._activeTasks--;
			if (root._willEnsureProcessing === false && root._needProcessing) {
				root._willEnsureProcessing = true;
				setImmediate(root._ensureProcessing);
			}

			if (inHandleResult++ > 3) {
				process.nextTick(() => {
					callback(error, result);
					if (callbacks !== undefined) {
						for (const callback of callbacks) {
							callback(error, result);
						}
					}
				});
			} else {
				callback(error, result);
				if (callbacks !== undefined) {
					for (const callback of callbacks) {
						callback(error, result);
					}
				}
			}
			inHandleResult--;
		});
	}

	clear() {
		this._entries.clear();
		this._queued.clear();
		this._activeTasks = 0;
		this._willEnsureProcessing = false;
		this._needProcessing = false;
		this._stopped = false;
	}
}

module.exports = AsyncQueue;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

class Hash {
	/* istanbul ignore next */
	/**
	 * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding}
	 * @abstract
	 * @param {string|Buffer} data data
	 * @param {string=} inputEncoding data encoding
	 * @returns {this} updated hash
	 */
	update(data, inputEncoding) {
		const AbstractMethodError = require("../AbstractMethodError");
		throw new AbstractMethodError();
	}

	/* istanbul ignore next */
	/**
	 * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding}
	 * @abstract
	 * @param {string=} encoding encoding of the return value
	 * @returns {string|Buffer} digest
	 */
	digest(encoding) {
		const AbstractMethodError = require("../AbstractMethodError");
		throw new AbstractMethodError();
	}
}

module.exports = Hash;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

/**
 * @template T
 * @param {Iterable<T>} set a set
 * @returns {T | undefined} last item
 */
const last = set => {
	let last;
	for (const item of set) last = item;
	return last;
};

/**
 * @template T
 * @param {Iterable<T>} iterable iterable
 * @param {function(T): boolean} filter predicate
 * @returns {boolean} true, if some items match the filter predicate
 */
const someInIterable = (iterable, filter) => {
	for (const item of iterable) {
		if (filter(item)) return true;
	}
	return false;
};

/**
 * @template T
 * @param {Iterable<T>} iterable an iterable
 * @returns {number} count of items
 */
const countIterable = iterable => {
	let i = 0;
	// eslint-disable-next-line no-unused-vars
	for (const _ of iterable) i++;
	return i;
};

exports.last = last;
exports.someInIterable = someInIterable;
exports.countIterable = countIterable;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const { first } = require("./SetHelpers");
const SortableSet = require("./SortableSet");

/**
 * Multi layer bucket sorted set:
 * Supports adding non-existing items (DO NOT ADD ITEM TWICE),
 * Supports removing exiting items (DO NOT REMOVE ITEM NOT IN SET),
 * Supports popping the first items according to defined order,
 * Supports iterating all items without order,
 * Supports updating an item in an efficient way,
 * Supports size property, which is the number of items,
 * Items are lazy partially sorted when needed
 * @template T
 * @template K
 */
class LazyBucketSortedSet {
	/**
	 * @param {function(T): K} getKey function to get key from item
	 * @param {function(K, K): number} comparator comparator to sort keys
	 * @param  {...((function(T): any) | (function(any, any): number))} args more pairs of getKey and comparator plus optional final comparator for the last layer
	 */
	constructor(getKey, comparator, ...args) {
		this._getKey = getKey;
		this._innerArgs = args;
		this._leaf = args.length <= 1;
		this._keys = new SortableSet(undefined, comparator);
		/** @type {Map<K, LazyBucketSortedSet<T, any> | SortableSet<T>>} */
		this._map = new Map();
		this._unsortedItems = new Set();
		this.size = 0;
	}

	/**
	 * @param {T} item an item
	 * @returns {void}
	 */
	add(item) {
		this.size++;
		this._unsortedItems.add(item);
	}

	/**
	 * @param {K} key key of item
	 * @param {T} item the item
	 * @returns {void}
	 */
	_addInternal(key, item) {
		let entry = this._map.get(key);
		if (entry === undefined) {
			entry = this._leaf
				? new SortableSet(undefined, this._innerArgs[0])
				: new /** @type {any} */ (LazyBucketSortedSet)(...this._innerArgs);
			this._keys.add(key);
			this._map.set(key, entry);
		}
		entry.add(item);
	}

	/**
	 * @param {T} item an item
	 * @returns {void}
	 */
	delete(item) {
		this.size--;
		if (this._unsortedItems.has(item)) {
			this._unsortedItems.delete(item);
			return;
		}
		const key = this._getKey(item);
		const entry = this._map.get(key);
		entry.delete(item);
		if (entry.size === 0) {
			this._deleteKey(key);
		}
	}

	/**
	 * @param {K} key key to be removed
	 * @returns {void}
	 */
	_deleteKey(key) {
		this._keys.delete(key);
		this._map.delete(key);
	}

	/**
	 * @returns {T | undefined} an item
	 */
	popFirst() {
		if (this.size === 0) return undefined;
		this.size--;
		if (this._unsortedItems.size > 0) {
			for (const item of this._unsortedItems) {
				const key = this._getKey(item);
				this._addInternal(key, item);
			}
			this._unsortedItems.clear();
		}
		this._keys.sort();
		const key = first(this._keys);
		const entry = this._map.get(key);
		if (this._leaf) {
			const leafEntry = /** @type {SortableSet<T>} */ (entry);
			leafEntry.sort();
			const item = first(leafEntry);
			leafEntry.delete(item);
			if (leafEntry.size === 0) {
				this._deleteKey(key);
			}
			return item;
		} else {
			const nodeEntry = /** @type {LazyBucketSortedSet<T, any>} */ (entry);
			const item = nodeEntry.popFirst();
			if (nodeEntry.size === 0) {
				this._deleteKey(key);
			}
			return item;
		}
	}

	/**
	 * @param {T} item to be updated item
	 * @returns {function(true=): void} finish update
	 */
	startUpdate(item) {
		if (this._unsortedItems.has(item)) {
			return remove => {
				if (remove) {
					this._unsortedItems.delete(item);
					this.size--;
					return;
				}
			};
		}
		const key = this._getKey(item);
		if (this._leaf) {
			const oldEntry = /** @type {SortableSet<T>} */ (this._map.get(key));
			return remove => {
				if (remove) {
					this.size--;
					oldEntry.delete(item);
					if (oldEntry.size === 0) {
						this._deleteKey(key);
					}
					return;
				}
				const newKey = this._getKey(item);
				if (key === newKey) {
					// This flags the sortable set as unordered
					oldEntry.add(item);
				} else {
					oldEntry.delete(item);
					if (oldEntry.size === 0) {
						this._deleteKey(key);
					}
					this._addInternal(newKey, item);
				}
			};
		} else {
			const oldEntry = /** @type {LazyBucketSortedSet<T, any>} */ (
				this._map.get(key)
			);
			const finishUpdate = oldEntry.startUpdate(item);
			return remove => {
				if (remove) {
					this.size--;
					finishUpdate(true);
					if (oldEntry.size === 0) {
						this._deleteKey(key);
					}
					return;
				}
				const newKey = this._getKey(item);
				if (key === newKey) {
					finishUpdate();
				} else {
					finishUpdate(true);
					if (oldEntry.size === 0) {
						this._deleteKey(key);
					}
					this._addInternal(newKey, item);
				}
			};
		}
	}

	/**
	 * @param {Iterator<T>[]} iterators list of iterators to append to
	 * @returns {void}
	 */
	_appendIterators(iterators) {
		if (this._unsortedItems.size > 0)
			iterators.push(this._unsortedItems[Symbol.iterator]());
		for (const key of this._keys) {
			const entry = this._map.get(key);
			if (this._leaf) {
				const leafEntry = /** @type {SortableSet<T>} */ (entry);
				const iterator = leafEntry[Symbol.iterator]();
				iterators.push(iterator);
			} else {
				const nodeEntry = /** @type {LazyBucketSortedSet<T, any>} */ (entry);
				nodeEntry._appendIterators(iterators);
			}
		}
	}

	/**
	 * @returns {Iterator<T>} the iterator
	 */
	[Symbol.iterator]() {
		const iterators = [];
		this._appendIterators(iterators);
		iterators.reverse();
		let currentIterator = iterators.pop();
		return {
			next: () => {
				const res = currentIterator.next();
				if (res.done) {
					if (iterators.length === 0) return res;
					currentIterator = iterators.pop();
					return currentIterator.next();
				}
				return res;
			}
		};
	}
}

module.exports = LazyBucketSortedSet;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const makeSerializable = require("./makeSerializable.js");

/**
 * @template T
 * @param {Set<T>} targetSet set where items should be added
 * @param {Set<Iterable<T>>} toMerge iterables to be merged
 * @returns {void}
 */
const merge = (targetSet, toMerge) => {
	for (const set of toMerge) {
		for (const item of set) {
			targetSet.add(item);
		}
	}
};

/**
 * @template T
 * @param {Set<Iterable<T>>} targetSet set where iterables should be added
 * @param {Array<LazySet<T>>} toDeepMerge lazy sets to be flattened
 * @returns {void}
 */
const flatten = (targetSet, toDeepMerge) => {
	for (const set of toDeepMerge) {
		if (set._set.size > 0) targetSet.add(set._set);
		if (set._needMerge) {
			for (const mergedSet of set._toMerge) {
				targetSet.add(mergedSet);
			}
			flatten(targetSet, set._toDeepMerge);
		}
	}
};

/**
 * Like Set but with an addAll method to eventually add items from another iterable.
 * Access methods make sure that all delayed operations are executed.
 * Iteration methods deopts to normal Set performance until clear is called again (because of the chance of modifications during iteration).
 * @template T
 */
class LazySet {
	/**
	 * @param {Iterable<T>=} iterable init iterable
	 */
	constructor(iterable) {
		/** @type {Set<T>} */
		this._set = new Set(iterable);
		/** @type {Set<Iterable<T>>} */
		this._toMerge = new Set();
		/** @type {Array<LazySet<T>>} */
		this._toDeepMerge = [];
		this._needMerge = false;
		this._deopt = false;
	}

	_flatten() {
		flatten(this._toMerge, this._toDeepMerge);
		this._toDeepMerge.length = 0;
	}

	_merge() {
		this._flatten();
		merge(this._set, this._toMerge);
		this._toMerge.clear();
		this._needMerge = false;
	}

	_isEmpty() {
		return (
			this._set.size === 0 &&
			this._toMerge.size === 0 &&
			this._toDeepMerge.length === 0
		);
	}

	get size() {
		if (this._needMerge) this._merge();
		return this._set.size;
	}

	/**
	 * @param {T} item an item
	 * @returns {LazySet<T>} itself
	 */
	add(item) {
		this._set.add(item);
		return this;
	}

	/**
	 * @param {Iterable<T> | LazySet<T>} iterable a immutable iterable or another immutable LazySet which will eventually be merged into the Set
	 * @returns {LazySet<T>} itself
	 */
	addAll(iterable) {
		if (this._deopt) {
			const _set = this._set;
			for (const item of iterable) {
				_set.add(item);
			}
		} else {
			if (iterable instanceof LazySet) {
				if (iterable._isEmpty()) return this;
				this._toDeepMerge.push(iterable);
				this._needMerge = true;
				if (this._toDeepMerge.length > 100000) {
					this._flatten();
				}
			} else {
				this._toMerge.add(iterable);
				this._needMerge = true;
			}
			if (this._toMerge.size > 100000) this._merge();
		}
		return this;
	}

	clear() {
		this._set.clear();
		this._toMerge.clear();
		this._toDeepMerge.length = 0;
		this._needMerge = false;
		this._deopt = false;
	}

	/**
	 * @param {T} value an item
	 * @returns {boolean} true, if the value was in the Set before
	 */
	delete(value) {
		if (this._needMerge) this._merge();
		return this._set.delete(value);
	}

	entries() {
		this._deopt = true;
		if (this._needMerge) this._merge();
		return this._set.entries();
	}

	/**
	 * @param {function(T, T, Set<T>): void} callbackFn function called for each entry
	 * @param {any} thisArg this argument for the callbackFn
	 * @returns {void}
	 */
	forEach(callbackFn, thisArg) {
		this._deopt = true;
		if (this._needMerge) this._merge();
		this._set.forEach(callbackFn, thisArg);
	}

	/**
	 * @param {T} item an item
	 * @returns {boolean} true, when the item is in the Set
	 */
	has(item) {
		if (this._needMerge) this._merge();
		return this._set.has(item);
	}

	keys() {
		this._deopt = true;
		if (this._needMerge) this._merge();
		return this._set.keys();
	}

	values() {
		this._deopt = true;
		if (this._needMerge) this._merge();
		return this._set.values();
	}

	[Symbol.iterator]() {
		this._deopt = true;
		if (this._needMerge) this._merge();
		return this._set[Symbol.iterator]();
	}

	/* istanbul ignore next */
	get [Symbol.toStringTag]() {
		return "LazySet";
	}

	/**
	 * @param {import("../serialization/ObjectMiddleware").ObjectSerializerContext} context context
	 */
	serialize({ write }) {
		if (this._needMerge) this._merge();
		write(this._set.size);
		for (const item of this._set) write(item);
	}

	/**
	 * @template T
	 * @param {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} context context
	 * @returns {LazySet<T>} lazy set
	 */
	static deserialize({ read }) {
		const count = read();
		const items = [];
		for (let i = 0; i < count; i++) {
			items.push(read());
		}
		return new LazySet(items);
	}
}

makeSerializable(LazySet, "webpack/lib/util/LazySet");

module.exports = LazySet;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

/**
 * getOrInsert is a helper function for maps that allows you to get a value
 * from a map if it exists, or insert a new value if it doesn't. If it value doesn't
 * exist, it will be computed by the provided function.
 *
 * @template K
 * @template V
 * @param {Map<K, V>} map The map object to check
 * @param {K} key The key to check
 * @param {function(): V} computer function which will compute the value if it doesn't exist
 * @returns {V} The value from the map, or the computed value
 *
 * @example
 * ```js
 * const map = new Map();
 * const value = getOrInsert(map, "key", () => "value");
 * console.log(value); // "value"
 * ```
 */
exports.getOrInsert = (map, key, computer) => {
	// Grab key from map
	const value = map.get(key);
	// If the value already exists, return it
	if (value !== undefined) return value;
	// Otherwise compute the value, set it in the map, and return it
	const newValue = computer();
	map.set(key, newValue);
	return newValue;
};


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const binarySearchBounds = require("../util/binarySearchBounds");

/** @typedef {function(number): void} Callback */

class ParallelismFactorCalculator {
	constructor() {
		/** @type {number[]} */
		this._rangePoints = [];
		/** @type {Callback[]} */
		this._rangeCallbacks = [];
	}

	/**
	 * @param {number} start range start
	 * @param {number} end range end
	 * @param {Callback} callback callback
	 * @returns {void}
	 */
	range(start, end, callback) {
		if (start === end) return callback(1);
		this._rangePoints.push(start);
		this._rangePoints.push(end);
		this._rangeCallbacks.push(callback);
	}

	calculate() {
		const segments = Array.from(new Set(this._rangePoints)).sort((a, b) =>
			a < b ? -1 : 1
		);
		const parallelism = segments.map(() => 0);
		const rangeStartIndices = [];
		for (let i = 0; i < this._rangePoints.length; i += 2) {
			const start = this._rangePoints[i];
			const end = this._rangePoints[i + 1];
			let idx = binarySearchBounds.eq(segments, start);
			rangeStartIndices.push(idx);
			do {
				parallelism[idx]++;
				idx++;
			} while (segments[idx] < end);
		}
		for (let i = 0; i < this._rangeCallbacks.length; i++) {
			const start = this._rangePoints[i * 2];
			const end = this._rangePoints[i * 2 + 1];
			let idx = rangeStartIndices[i];
			let sum = 0;
			let totalDuration = 0;
			let current = start;
			do {
				const p = parallelism[idx];
				idx++;
				const duration = segments[idx] - current;
				totalDuration += duration;
				current = segments[idx];
				sum += p * duration;
			} while (current < end);
			this._rangeCallbacks[i](sum / totalDuration);
		}
	}
}

module.exports = ParallelismFactorCalculator;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

/**
 * @template T
 */
class Queue {
	/**
	 * @param {Iterable<T>=} items The initial elements.
	 */
	constructor(items) {
		/**
		 * @private
		 * @type {Set<T>}
		 */
		this._set = new Set(items);
		/**
		 * @private
		 * @type {Iterator<T>}
		 */
		this._iterator = this._set[Symbol.iterator]();
	}

	/**
	 * Returns the number of elements in this queue.
	 * @returns {number} The number of elements in this queue.
	 */
	get length() {
		return this._set.size;
	}

	/**
	 * Appends the specified element to this queue.
	 * @param {T} item The element to add.
	 * @returns {void}
	 */
	enqueue(item) {
		this._set.add(item);
	}

	/**
	 * Retrieves and removes the head of this queue.
	 * @returns {T | undefined} The head of the queue of `undefined` if this queue is empty.
	 */
	dequeue() {
		const result = this._iterator.next();
		if (result.done) return undefined;
		this._set.delete(result.value);
		return result.value;
	}
}

module.exports = Queue;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

class Semaphore {
	/**
	 * Creates an instance of Semaphore.
	 *
	 * @param {number} available the amount available number of "tasks"
	 * in the Semaphore
	 */
	constructor(available) {
		this.available = available;
		/** @type {(function(): void)[]} */
		this.waiters = [];
		/** @private */
		this._continue = this._continue.bind(this);
	}

	/**
	 * @param {function(): void} callback function block to capture and run
	 * @returns {void}
	 */
	acquire(callback) {
		if (this.available > 0) {
			this.available--;
			callback();
		} else {
			this.waiters.push(callback);
		}
	}

	release() {
		this.available++;
		if (this.waiters.length > 0) {
			process.nextTick(this._continue);
		}
	}

	_continue() {
		if (this.available > 0) {
			if (this.waiters.length > 0) {
				this.available--;
				const callback = /** @type {(function(): void)} */ (this.waiters.pop());
				callback();
			}
		}
	}
}

module.exports = Semaphore;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

/**
 * intersect creates Set containing the intersection of elements between all sets
 * @template T
 * @param {Set<T>[]} sets an array of sets being checked for shared elements
 * @returns {Set<T>} returns a new Set containing the intersecting items
 */
const intersect = sets => {
	if (sets.length === 0) return new Set();
	if (sets.length === 1) return new Set(sets[0]);
	let minSize = Infinity;
	let minIndex = -1;
	for (let i = 0; i < sets.length; i++) {
		const size = sets[i].size;
		if (size < minSize) {
			minIndex = i;
			minSize = size;
		}
	}
	const current = new Set(sets[minIndex]);
	for (let i = 0; i < sets.length; i++) {
		if (i === minIndex) continue;
		const set = sets[i];
		for (const item of current) {
			if (!set.has(item)) {
				current.delete(item);
			}
		}
	}
	return current;
};

/**
 * Checks if a set is the subset of another set
 * @template T
 * @param {Set<T>} bigSet a Set which contains the original elements to compare against
 * @param {Set<T>} smallSet the set whose elements might be contained inside of bigSet
 * @returns {boolean} returns true if smallSet contains all elements inside of the bigSet
 */
const isSubset = (bigSet, smallSet) => {
	if (bigSet.size < smallSet.size) return false;
	for (const item of smallSet) {
		if (!bigSet.has(item)) return false;
	}
	return true;
};

/**
 * @template T
 * @param {Set<T>} set a set
 * @param {function(T): boolean} fn selector function
 * @returns {T | undefined} found item
 */
const find = (set, fn) => {
	for (const item of set) {
		if (fn(item)) return item;
	}
};

/**
 * @template T
 * @param {Set<T>} set a set
 * @returns {T | undefined} first item
 */
const first = set => {
	const entry = set.values().next();
	return entry.done ? undefined : entry.value;
};

/**
 * @template T
 * @param {Set<T>} a first
 * @param {Set<T>} b second
 * @returns {Set<T>} combined set, may be identical to a or b
 */
const combine = (a, b) => {
	if (b.size === 0) return a;
	if (a.size === 0) return b;
	const set = new Set(a);
	for (const item of b) set.add(item);
	return set;
};

exports.intersect = intersect;
exports.isSubset = isSubset;
exports.find = find;
exports.first = first;
exports.combine = combine;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const NONE = Symbol("not sorted");

/**
 * A subset of Set that offers sorting functionality
 * @template T item type in set
 * @extends {Set<T>}
 */
class SortableSet extends Set {
	/**
	 * Create a new sortable set
	 * @param {Iterable<T>=} initialIterable The initial iterable value
	 * @typedef {function(T, T): number} SortFunction
	 * @param {SortFunction=} defaultSort Default sorting function
	 */
	constructor(initialIterable, defaultSort) {
		super(initialIterable);
		/**
		 * @private
		 * @type {undefined | function(T, T): number}}
		 */
		this._sortFn = defaultSort;
		/**
		 * @private
		 * @type {typeof NONE | undefined | function(T, T): number}}
		 */
		this._lastActiveSortFn = NONE;
		/**
		 * @private
		 * @type {Map<Function, any> | undefined}
		 */
		this._cache = undefined;
		/**
		 * @private
		 * @type {Map<Function, any> | undefined}
		 */
		this._cacheOrderIndependent = undefined;
	}

	/**
	 * @param {T} value value to add to set
	 * @returns {this} returns itself
	 */
	add(value) {
		this._lastActiveSortFn = NONE;
		this._invalidateCache();
		this._invalidateOrderedCache();
		super.add(value);
		return this;
	}

	/**
	 * @param {T} value value to delete
	 * @returns {boolean} true if value existed in set, false otherwise
	 */
	delete(value) {
		this._invalidateCache();
		this._invalidateOrderedCache();
		return super.delete(value);
	}

	/**
	 * @returns {void}
	 */
	clear() {
		this._invalidateCache();
		this._invalidateOrderedCache();
		return super.clear();
	}

	/**
	 * Sort with a comparer function
	 * @param {SortFunction} sortFn Sorting comparer function
	 * @returns {void}
	 */
	sortWith(sortFn) {
		if (this.size <= 1 || sortFn === this._lastActiveSortFn) {
			// already sorted - nothing to do
			return;
		}

		const sortedArray = Array.from(this).sort(sortFn);
		super.clear();
		for (let i = 0; i < sortedArray.length; i += 1) {
			super.add(sortedArray[i]);
		}
		this._lastActiveSortFn = sortFn;
		this._invalidateCache();
	}

	sort() {
		this.sortWith(this._sortFn);
		return this;
	}

	/**
	 * Get data from cache
	 * @template R
	 * @param {function(SortableSet<T>): R} fn function to calculate value
	 * @returns {R} returns result of fn(this), cached until set changes
	 */
	getFromCache(fn) {
		if (this._cache === undefined) {
			this._cache = new Map();
		} else {
			const result = this._cache.get(fn);
			const data = /** @type {R} */ (result);
			if (data !== undefined) {
				return data;
			}
		}
		const newData = fn(this);
		this._cache.set(fn, newData);
		return newData;
	}

	/**
	 * Get data from cache (ignoring sorting)
	 * @template R
	 * @param {function(SortableSet<T>): R} fn function to calculate value
	 * @returns {R} returns result of fn(this), cached until set changes
	 */
	getFromUnorderedCache(fn) {
		if (this._cacheOrderIndependent === undefined) {
			this._cacheOrderIndependent = new Map();
		} else {
			const result = this._cacheOrderIndependent.get(fn);
			const data = /** @type {R} */ (result);
			if (data !== undefined) {
				return data;
			}
		}
		const newData = fn(this);
		this._cacheOrderIndependent.set(fn, newData);
		return newData;
	}

	/**
	 * @private
	 * @returns {void}
	 */
	_invalidateCache() {
		if (this._cache !== undefined) {
			this._cache.clear();
		}
	}

	/**
	 * @private
	 * @returns {void}
	 */
	_invalidateOrderedCache() {
		if (this._cacheOrderIndependent !== undefined) {
			this._cacheOrderIndependent.clear();
		}
	}

	/**
	 * @returns {T[]} the raw array
	 */
	toJSON() {
		return Array.from(this);
	}
}

module.exports = SortableSet;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

/**
 * @template K
 * @template V
 *
 * The StackedCacheMap is a data structure designed as an alternative to a Map
 * in situations where you need to handle multiple item additions and
 * frequently access the largest map.
 *
 * It is particularly optimized for efficiently adding multiple items
 * at once, which can be achieved using the `addAll` method.
 *
 * It has a fallback Map that is used when the map to be added is mutable.
 *
 * Note: `delete` and `has` are not supported for performance reasons.
 *
 * @example
 * ```js
 * const map = new StackedCacheMap();
 * map.addAll(new Map([["a", 1], ["b", 2]]), true);
 * map.addAll(new Map([["c", 3], ["d", 4]]), true);
 * map.get("a"); // 1
 * map.get("d"); // 4
 * for (const [key, value] of map) {
 * 		console.log(key, value);
 * }
 * ```
 */
class StackedCacheMap {
	constructor() {
		/** @type {Map<K, V>} */
		this.map = new Map();
		/** @type {ReadonlyMap<K, V>[]} */
		this.stack = [];
	}

	/**
	 * If `immutable` is true, the map can be referenced by the StackedCacheMap
	 * and should not be changed afterwards. If the map is mutable, all items
	 * are copied into a fallback Map.
	 * @param {ReadonlyMap<K, V>} map map to add
	 * @param {boolean=} immutable if 'map' is immutable and StackedCacheMap can keep referencing it
	 */
	addAll(map, immutable) {
		if (immutable) {
			this.stack.push(map);

			// largest map should go first
			for (let i = this.stack.length - 1; i > 0; i--) {
				const beforeLast = this.stack[i - 1];
				if (beforeLast.size >= map.size) break;
				this.stack[i] = beforeLast;
				this.stack[i - 1] = map;
			}
		} else {
			for (const [key, value] of map) {
				this.map.set(key, value);
			}
		}
	}

	/**
	 * @param {K} item the key of the element to add
	 * @param {V} value the value of the element to add
	 * @returns {void}
	 */
	set(item, value) {
		this.map.set(item, value);
	}

	/**
	 * @param {K} item the item to delete
	 * @returns {void}
	 */
	delete(item) {
		throw new Error("Items can't be deleted from a StackedCacheMap");
	}

	/**
	 * @param {K} item the item to test
	 * @returns {boolean} true if the item exists in this set
	 */
	has(item) {
		throw new Error(
			"Checking StackedCacheMap.has before reading is inefficient, use StackedCacheMap.get and check for undefined"
		);
	}

	/**
	 * @param {K} item the key of the element to return
	 * @returns {V} the value of the element
	 */
	get(item) {
		for (const map of this.stack) {
			const value = map.get(item);
			if (value !== undefined) return value;
		}
		return this.map.get(item);
	}

	clear() {
		this.stack.length = 0;
		this.map.clear();
	}

	/**
	 * @returns {number} size of the map
	 */
	get size() {
		let size = this.map.size;
		for (const map of this.stack) {
			size += map.size;
		}
		return size;
	}

	/**
	 * @returns {Iterator<[K, V]>} iterator
	 */
	[Symbol.iterator]() {
		const iterators = this.stack.map(map => map[Symbol.iterator]());
		let current = this.map[Symbol.iterator]();
		return {
			next() {
				let result = current.next();
				while (result.done && iterators.length > 0) {
					current = iterators.pop();
					result = current.next();
				}
				return result;
			}
		};
	}
}

module.exports = StackedCacheMap;