webpack/lib/stats/DefaultStatsFactoryPlugin.js

2319 lines
68 KiB
JavaScript

/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const util = require("util");
const ModuleDependency = require("../dependencies/ModuleDependency");
const formatLocation = require("../formatLocation");
const { LogType } = require("../logging/Logger");
const AggressiveSplittingPlugin = require("../optimize/AggressiveSplittingPlugin");
const ConcatenatedModule = require("../optimize/ConcatenatedModule");
const SizeLimitsPlugin = require("../performance/SizeLimitsPlugin");
const { countIterable } = require("../util/IterableHelpers");
const {
compareLocations,
compareChunksById,
compareNumbers,
compareIds,
concatComparators,
compareSelect,
compareModulesByIdentifier
} = require("../util/comparators");
const { makePathsRelative, parseResource } = require("../util/identifier");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../ChunkGroup")} ChunkGroup */
/** @typedef {import("../ChunkGroup").OriginRecord} OriginRecord */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compilation").Asset} Asset */
/** @typedef {import("../Compilation").AssetInfo} AssetInfo */
/** @typedef {import("../Compilation").NormalizedStatsOptions} NormalizedStatsOptions */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */
/** @typedef {import("../ModuleProfile")} ModuleProfile */
/** @typedef {import("../RequestShortener")} RequestShortener */
/** @typedef {import("../WebpackError")} WebpackError */
/** @template T @typedef {import("../util/comparators").Comparator<T>} Comparator<T> */
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
/** @typedef {import("../util/smartGrouping").GroupConfig<any, object>} GroupConfig */
/** @typedef {import("./StatsFactory")} StatsFactory */
/** @typedef {import("./StatsFactory").StatsFactoryContext} StatsFactoryContext */
/** @typedef {KnownStatsCompilation & Record<string, any>} StatsCompilation */
/**
* @typedef {Object} KnownStatsCompilation
* @property {any=} env
* @property {string=} name
* @property {string=} hash
* @property {string=} version
* @property {number=} time
* @property {number=} builtAt
* @property {boolean=} needAdditionalPass
* @property {string=} publicPath
* @property {string=} outputPath
* @property {Record<string, string[]>=} assetsByChunkName
* @property {StatsAsset[]=} assets
* @property {number=} filteredAssets
* @property {StatsChunk[]=} chunks
* @property {StatsModule[]=} modules
* @property {number=} filteredModules
* @property {Record<string, StatsChunkGroup>=} entrypoints
* @property {Record<string, StatsChunkGroup>=} namedChunkGroups
* @property {StatsError[]=} errors
* @property {number=} errorsCount
* @property {StatsError[]=} warnings
* @property {number=} warningsCount
* @property {StatsCompilation[]=} children
* @property {Record<string, StatsLogging>=} logging
*/
/** @typedef {KnownStatsLogging & Record<string, any>} StatsLogging */
/**
* @typedef {Object} KnownStatsLogging
* @property {StatsLoggingEntry[]} entries
* @property {number} filteredEntries
* @property {boolean} debug
*/
/** @typedef {KnownStatsLoggingEntry & Record<string, any>} StatsLoggingEntry */
/**
* @typedef {Object} KnownStatsLoggingEntry
* @property {string} type
* @property {string} message
* @property {string[]=} trace
* @property {StatsLoggingEntry[]=} children
* @property {any[]=} args
* @property {number=} time
*/
/** @typedef {KnownStatsAsset & Record<string, any>} StatsAsset */
/**
* @typedef {Object} KnownStatsAsset
* @property {string} type
* @property {string} name
* @property {AssetInfo} info
* @property {number} size
* @property {boolean} emitted
* @property {boolean} comparedForEmit
* @property {boolean} cached
* @property {StatsAsset[]=} related
* @property {(string|number)[]=} chunkNames
* @property {(string|number)[]=} chunkIdHints
* @property {(string|number)[]=} chunks
* @property {(string|number)[]=} auxiliaryChunkNames
* @property {(string|number)[]=} auxiliaryChunks
* @property {(string|number)[]=} auxiliaryChunkIdHints
* @property {number=} filteredRelated
* @property {boolean=} isOverSizeLimit
*/
/** @typedef {KnownStatsChunkGroup & Record<string, any>} StatsChunkGroup */
/**
* @typedef {Object} KnownStatsChunkGroup
* @property {string=} name
* @property {(string|number)[]=} chunks
* @property {({ name: string, size?: number })[]=} assets
* @property {number=} filteredAssets
* @property {number=} assetsSize
* @property {({ name: string, size?: number })[]=} auxiliaryAssets
* @property {number=} filteredAuxiliaryAssets
* @property {number=} auxiliaryAssetsSize
* @property {{ [x: string]: StatsChunkGroup[] }=} children
* @property {{ [x: string]: string[] }=} childAssets
* @property {boolean=} isOverSizeLimit
*/
/** @typedef {KnownStatsModule & Record<string, any>} StatsModule */
/**
* @typedef {Object} KnownStatsModule
* @property {string=} type
* @property {string=} moduleType
* @property {string=} layer
* @property {string=} identifier
* @property {string=} name
* @property {string=} nameForCondition
* @property {number=} index
* @property {number=} preOrderIndex
* @property {number=} index2
* @property {number=} postOrderIndex
* @property {number=} size
* @property {{[x: string]: number}=} sizes
* @property {boolean=} cacheable
* @property {boolean=} built
* @property {boolean=} codeGenerated
* @property {boolean=} cached
* @property {boolean=} optional
* @property {boolean=} orphan
* @property {string|number=} id
* @property {string|number=} issuerId
* @property {(string|number)[]=} chunks
* @property {(string|number)[]=} assets
* @property {boolean=} dependent
* @property {string=} issuer
* @property {string=} issuerName
* @property {StatsModuleIssuer[]=} issuerPath
* @property {boolean=} failed
* @property {number=} errors
* @property {number=} warnings
* @property {StatsProfile=} profile
* @property {StatsModuleReason[]=} reasons
* @property {(boolean | string[])=} usedExports
* @property {string[]=} providedExports
* @property {string[]=} optimizationBailout
* @property {number=} depth
* @property {StatsModule[]=} modules
* @property {number=} filteredModules
* @property {ReturnType<Source["source"]>=} source
*/
/** @typedef {KnownStatsProfile & Record<string, any>} StatsProfile */
/**
* @typedef {Object} KnownStatsProfile
* @property {number} total
* @property {number} resolving
* @property {number} restoring
* @property {number} building
* @property {number} integration
* @property {number} storing
* @property {number} additionalResolving
* @property {number} additionalIntegration
* @property {number} factory
* @property {number} dependencies
*/
/** @typedef {KnownStatsModuleIssuer & Record<string, any>} StatsModuleIssuer */
/**
* @typedef {Object} KnownStatsModuleIssuer
* @property {string=} identifier
* @property {string=} name
* @property {(string|number)=} id
* @property {StatsProfile=} profile
*/
/** @typedef {KnownStatsModuleReason & Record<string, any>} StatsModuleReason */
/**
* @typedef {Object} KnownStatsModuleReason
* @property {string=} moduleIdentifier
* @property {string=} module
* @property {string=} moduleName
* @property {string=} resolvedModuleIdentifier
* @property {string=} resolvedModule
* @property {string=} type
* @property {boolean} active
* @property {string=} explanation
* @property {string=} userRequest
* @property {string=} loc
* @property {(string|number)=} moduleId
* @property {(string|number)=} resolvedModuleId
*/
/** @typedef {KnownStatsChunk & Record<string, any>} StatsChunk */
/**
* @typedef {Object} KnownStatsChunk
* @property {boolean} rendered
* @property {boolean} initial
* @property {boolean} entry
* @property {boolean} recorded
* @property {string=} reason
* @property {number} size
* @property {Record<string, number>=} sizes
* @property {string[]=} names
* @property {string[]=} idHints
* @property {string[]=} runtime
* @property {string[]=} files
* @property {string[]=} auxiliaryFiles
* @property {string} hash
* @property {Record<string, (string|number)[]>=} childrenByOrder
* @property {(string|number)=} id
* @property {(string|number)[]=} siblings
* @property {(string|number)[]=} parents
* @property {(string|number)[]=} children
* @property {StatsModule[]=} modules
* @property {number=} filteredModules
* @property {StatsChunkOrigin[]=} origins
*/
/** @typedef {KnownStatsChunkOrigin & Record<string, any>} StatsChunkOrigin */
/**
* @typedef {Object} KnownStatsChunkOrigin
* @property {string=} module
* @property {string=} moduleIdentifier
* @property {string=} moduleName
* @property {string=} loc
* @property {string=} request
* @property {(string|number)=} moduleId
*/
/** @typedef {KnownStatsModuleTraceItem & Record<string, any>} StatsModuleTraceItem */
/**
* @typedef {Object} KnownStatsModuleTraceItem
* @property {string=} originIdentifier
* @property {string=} originName
* @property {string=} moduleIdentifier
* @property {string=} moduleName
* @property {StatsModuleTraceDependency[]=} dependencies
* @property {(string|number)=} originId
* @property {(string|number)=} moduleId
*/
/** @typedef {KnownStatsModuleTraceDependency & Record<string, any>} StatsModuleTraceDependency */
/**
* @typedef {Object} KnownStatsModuleTraceDependency
* @property {string=} loc
*/
/** @typedef {KnownStatsError & Record<string, any>} StatsError */
/**
* @typedef {Object} KnownStatsError
* @property {string} message
* @property {string=} chunkName
* @property {boolean=} chunkEntry
* @property {boolean=} chunkInitial
* @property {string=} file
* @property {string=} moduleIdentifier
* @property {string=} moduleName
* @property {string=} loc
* @property {string|number=} chunkId
* @property {string|number=} moduleId
* @property {any=} moduleTrace
* @property {any=} details
* @property {any=} stack
*/
/** @typedef {Asset & { type: string, related: PreprocessedAsset[] }} PreprocessedAsset */
/**
* @template T
* @template O
* @typedef {Record<string, (object: O, data: T, context: StatsFactoryContext, options: NormalizedStatsOptions, factory: StatsFactory) => void>} ExtractorsByOption
*/
/**
* @typedef {Object} SimpleExtractors
* @property {ExtractorsByOption<Compilation, StatsCompilation>} compilation
* @property {ExtractorsByOption<PreprocessedAsset, StatsAsset>} asset
* @property {ExtractorsByOption<PreprocessedAsset, StatsAsset>} asset$visible
* @property {ExtractorsByOption<{ name: string, chunkGroup: ChunkGroup }, StatsChunkGroup>} chunkGroup
* @property {ExtractorsByOption<Module, StatsModule>} module
* @property {ExtractorsByOption<Module, StatsModule>} module$visible
* @property {ExtractorsByOption<Module, StatsModuleIssuer>} moduleIssuer
* @property {ExtractorsByOption<ModuleProfile, StatsProfile>} profile
* @property {ExtractorsByOption<ModuleGraphConnection, StatsModuleReason>} moduleReason
* @property {ExtractorsByOption<Chunk, StatsChunk>} chunk
* @property {ExtractorsByOption<OriginRecord, StatsChunkOrigin>} chunkOrigin
* @property {ExtractorsByOption<WebpackError, StatsError>} error
* @property {ExtractorsByOption<WebpackError, StatsError>} warning
* @property {ExtractorsByOption<{ origin: Module, module: Module }, StatsModuleTraceItem>} moduleTraceItem
* @property {ExtractorsByOption<Dependency, StatsModuleTraceDependency>} moduleTraceDependency
*/
/**
* @template T
* @template I
* @param {Iterable<T>} items items to select from
* @param {function(T): Iterable<I>} selector selector function to select values from item
* @returns {I[]} array of values
*/
const uniqueArray = (items, selector) => {
/** @type {Set<I>} */
const set = new Set();
for (const item of items) {
for (const i of selector(item)) {
set.add(i);
}
}
return Array.from(set);
};
/**
* @template T
* @template I
* @param {Iterable<T>} items items to select from
* @param {function(T): Iterable<I>} selector selector function to select values from item
* @param {Comparator<I>} comparator comparator function
* @returns {I[]} array of values
*/
const uniqueOrderedArray = (items, selector, comparator) => {
return uniqueArray(items, selector).sort(comparator);
};
/** @template T @template R @typedef {{ [P in keyof T]: R }} MappedValues<T, R> */
/**
* @template T
* @template R
* @param {T} obj object to be mapped
* @param {function(T[keyof T], keyof T): R} fn mapping function
* @returns {MappedValues<T, R>} mapped object
*/
const mapObject = (obj, fn) => {
const newObj = Object.create(null);
for (const key of Object.keys(obj)) {
newObj[key] = fn(obj[key], /** @type {keyof T} */ (key));
}
return newObj;
};
/**
* @param {Compilation} compilation the compilation
* @param {function(Compilation, string): any[]} getItems get items
* @returns {number} total number
*/
const countWithChildren = (compilation, getItems) => {
let count = getItems(compilation, "").length;
for (const child of compilation.children) {
count += countWithChildren(child, (c, type) =>
getItems(c, `.children[].compilation${type}`)
);
}
return count;
};
/** @type {ExtractorsByOption<WebpackError | string, StatsError>} */
const EXTRACT_ERROR = {
_: (object, error, context, { requestShortener }) => {
// TODO webpack 6 disallow strings in the errors/warnings list
if (typeof error === "string") {
object.message = error;
} else {
if (error.chunk) {
object.chunkName = error.chunk.name;
object.chunkEntry = error.chunk.hasRuntime();
object.chunkInitial = error.chunk.canBeInitial();
}
if (error.file) {
object.file = error.file;
}
if (error.module) {
object.moduleIdentifier = error.module.identifier();
object.moduleName = error.module.readableIdentifier(requestShortener);
}
if (error.loc) {
object.loc = formatLocation(error.loc);
}
object.message = error.message;
}
},
ids: (object, error, { compilation: { chunkGraph } }) => {
if (typeof error !== "string") {
if (error.chunk) {
object.chunkId = error.chunk.id;
}
if (error.module) {
object.moduleId = chunkGraph.getModuleId(error.module);
}
}
},
moduleTrace: (object, error, context, options, factory) => {
if (typeof error !== "string" && error.module) {
const {
type,
compilation: { moduleGraph }
} = context;
/** @type {Set<Module>} */
const visitedModules = new Set();
const moduleTrace = [];
let current = error.module;
while (current) {
if (visitedModules.has(current)) break; // circular (technically impossible, but how knows)
visitedModules.add(current);
const origin = moduleGraph.getIssuer(current);
if (!origin) break;
moduleTrace.push({ origin, module: current });
current = origin;
}
object.moduleTrace = factory.create(
`${type}.moduleTrace`,
moduleTrace,
context
);
}
},
errorDetails: (
object,
error,
{ type, compilation, cachedGetErrors, cachedGetWarnings },
{ errorDetails }
) => {
if (
typeof error !== "string" &&
(errorDetails === true ||
(type.endsWith(".error") && cachedGetErrors(compilation).length < 3))
) {
object.details = error.details;
}
},
errorStack: (object, error) => {
if (typeof error !== "string") {
object.stack = error.stack;
}
}
};
/** @type {SimpleExtractors} */
const SIMPLE_EXTRACTORS = {
compilation: {
_: (object, compilation, context, options) => {
if (!context.makePathsRelative) {
context.makePathsRelative = makePathsRelative.bindContextCache(
compilation.compiler.context,
compilation.compiler.root
);
}
if (!context.cachedGetErrors) {
const map = new WeakMap();
context.cachedGetErrors = compilation => {
return (
map.get(compilation) ||
(errors => (map.set(compilation, errors), errors))(
compilation.getErrors()
)
);
};
}
if (!context.cachedGetWarnings) {
const map = new WeakMap();
context.cachedGetWarnings = compilation => {
return (
map.get(compilation) ||
(warnings => (map.set(compilation, warnings), warnings))(
compilation.getWarnings()
)
);
};
}
if (compilation.name) {
object.name = compilation.name;
}
if (compilation.needAdditionalPass) {
object.needAdditionalPass = true;
}
const { logging, loggingDebug, loggingTrace } = options;
if (logging || (loggingDebug && loggingDebug.length > 0)) {
const util = require("util");
object.logging = {};
let acceptedTypes;
let collapsedGroups = false;
switch (logging) {
default:
acceptedTypes = new Set();
break;
case "error":
acceptedTypes = new Set([LogType.error]);
break;
case "warn":
acceptedTypes = new Set([LogType.error, LogType.warn]);
break;
case "info":
acceptedTypes = new Set([
LogType.error,
LogType.warn,
LogType.info
]);
break;
case "log":
acceptedTypes = new Set([
LogType.error,
LogType.warn,
LogType.info,
LogType.log,
LogType.group,
LogType.groupEnd,
LogType.groupCollapsed,
LogType.clear
]);
break;
case "verbose":
acceptedTypes = new Set([
LogType.error,
LogType.warn,
LogType.info,
LogType.log,
LogType.group,
LogType.groupEnd,
LogType.groupCollapsed,
LogType.profile,
LogType.profileEnd,
LogType.time,
LogType.status,
LogType.clear
]);
collapsedGroups = true;
break;
}
const cachedMakePathsRelative = makePathsRelative.bindContextCache(
options.context,
compilation.compiler.root
);
let depthInCollapsedGroup = 0;
for (const [origin, logEntries] of compilation.logging) {
const debugMode = loggingDebug.some(fn => fn(origin));
if (logging === false && !debugMode) continue;
/** @type {KnownStatsLoggingEntry[]} */
const groupStack = [];
/** @type {KnownStatsLoggingEntry[]} */
const rootList = [];
let currentList = rootList;
let processedLogEntries = 0;
for (const entry of logEntries) {
let type = entry.type;
if (!debugMode && !acceptedTypes.has(type)) continue;
// Expand groups in verbose and debug modes
if (
type === LogType.groupCollapsed &&
(debugMode || collapsedGroups)
)
type = LogType.group;
if (depthInCollapsedGroup === 0) {
processedLogEntries++;
}
if (type === LogType.groupEnd) {
groupStack.pop();
if (groupStack.length > 0) {
currentList = groupStack[groupStack.length - 1].children;
} else {
currentList = rootList;
}
if (depthInCollapsedGroup > 0) depthInCollapsedGroup--;
continue;
}
let message = undefined;
if (entry.type === LogType.time) {
message = `${entry.args[0]}: ${
entry.args[1] * 1000 + entry.args[2] / 1000000
} ms`;
} else if (entry.args && entry.args.length > 0) {
message = util.format(entry.args[0], ...entry.args.slice(1));
}
/** @type {KnownStatsLoggingEntry} */
const newEntry = {
...entry,
type,
message,
trace: loggingTrace ? entry.trace : undefined,
children:
type === LogType.group || type === LogType.groupCollapsed
? []
: undefined
};
currentList.push(newEntry);
if (newEntry.children) {
groupStack.push(newEntry);
currentList = newEntry.children;
if (depthInCollapsedGroup > 0) {
depthInCollapsedGroup++;
} else if (type === LogType.groupCollapsed) {
depthInCollapsedGroup = 1;
}
}
}
let name = cachedMakePathsRelative(origin).replace(/\|/g, " ");
if (name in object.logging) {
let i = 1;
while (`${name}#${i}` in object.logging) {
i++;
}
name = `${name}#${i}`;
}
object.logging[name] = {
entries: rootList,
filteredEntries: logEntries.length - processedLogEntries,
debug: debugMode
};
}
}
},
hash: (object, compilation) => {
object.hash = compilation.hash;
},
version: object => {
object.version = require("../../package.json").version;
},
env: (object, compilation, context, { _env }) => {
object.env = _env;
},
timings: (object, compilation) => {
object.time = compilation.endTime - compilation.startTime;
},
builtAt: (object, compilation) => {
object.builtAt = compilation.endTime;
},
publicPath: (object, compilation) => {
object.publicPath = compilation.getPath(
compilation.outputOptions.publicPath
);
},
outputPath: (object, compilation) => {
object.outputPath = compilation.outputOptions.path;
},
assets: (object, compilation, context, options, factory) => {
const { type } = context;
/** @type {Map<string, Chunk[]>} */
const compilationFileToChunks = new Map();
/** @type {Map<string, Chunk[]>} */
const compilationAuxiliaryFileToChunks = new Map();
for (const chunk of compilation.chunks) {
for (const file of chunk.files) {
let array = compilationFileToChunks.get(file);
if (array === undefined) {
array = [];
compilationFileToChunks.set(file, array);
}
array.push(chunk);
}
for (const file of chunk.auxiliaryFiles) {
let array = compilationAuxiliaryFileToChunks.get(file);
if (array === undefined) {
array = [];
compilationAuxiliaryFileToChunks.set(file, array);
}
array.push(chunk);
}
}
/** @type {Map<string, PreprocessedAsset>} */
const assetMap = new Map();
/** @type {Set<PreprocessedAsset>} */
const assets = new Set();
for (const asset of compilation.getAssets()) {
/** @type {PreprocessedAsset} */
const item = {
...asset,
type: "asset",
related: undefined
};
assets.add(item);
assetMap.set(asset.name, item);
}
for (const item of assetMap.values()) {
const related = item.info.related;
if (!related) continue;
for (const type of Object.keys(related)) {
const relatedEntry = related[type];
const deps = Array.isArray(relatedEntry)
? relatedEntry
: [relatedEntry];
for (const dep of deps) {
const depItem = assetMap.get(dep);
if (!depItem) continue;
assets.delete(depItem);
depItem.type = type;
item.related = item.related || [];
item.related.push(depItem);
}
}
}
object.assetsByChunkName = {};
for (const [file, chunks] of compilationFileToChunks) {
for (const chunk of chunks) {
const name = chunk.name;
if (!name) continue;
if (
!Object.prototype.hasOwnProperty.call(
object.assetsByChunkName,
name
)
) {
object.assetsByChunkName[name] = [];
}
object.assetsByChunkName[name].push(file);
}
}
const groupedAssets = factory.create(
`${type}.assets`,
Array.from(assets),
{
...context,
compilationFileToChunks,
compilationAuxiliaryFileToChunks
}
);
const limited = spaceLimited(groupedAssets, options.assetsSpace);
object.assets = limited.children;
object.filteredAssets = limited.filteredChildren;
},
chunks: (object, compilation, context, options, factory) => {
const { type } = context;
object.chunks = factory.create(
`${type}.chunks`,
Array.from(compilation.chunks),
context
);
},
modules: (object, compilation, context, options, factory) => {
const { type } = context;
const array = Array.from(compilation.modules);
const groupedModules = factory.create(`${type}.modules`, array, context);
const limited = spaceLimited(groupedModules, options.modulesSpace);
object.modules = limited.children;
object.filteredModules = limited.filteredChildren;
},
entrypoints: (
object,
compilation,
context,
{ entrypoints, chunkGroups, chunkGroupAuxiliary, chunkGroupChildren },
factory
) => {
const { type } = context;
const array = Array.from(compilation.entrypoints, ([key, value]) => ({
name: key,
chunkGroup: value
}));
if (entrypoints === "auto" && !chunkGroups) {
if (array.length > 5) return;
if (
!chunkGroupChildren &&
array.every(({ chunkGroup }) => {
if (chunkGroup.chunks.length !== 1) return false;
const chunk = chunkGroup.chunks[0];
return (
chunk.files.size === 1 &&
(!chunkGroupAuxiliary || chunk.auxiliaryFiles.size === 0)
);
})
) {
return;
}
}
object.entrypoints = factory.create(
`${type}.entrypoints`,
array,
context
);
},
chunkGroups: (object, compilation, context, options, factory) => {
const { type } = context;
const array = Array.from(
compilation.namedChunkGroups,
([key, value]) => ({
name: key,
chunkGroup: value
})
);
object.namedChunkGroups = factory.create(
`${type}.namedChunkGroups`,
array,
context
);
},
errors: (object, compilation, context, options, factory) => {
const { type, cachedGetErrors } = context;
object.errors = factory.create(
`${type}.errors`,
cachedGetErrors(compilation),
context
);
},
errorsCount: (object, compilation, { cachedGetErrors }) => {
object.errorsCount = countWithChildren(compilation, c =>
cachedGetErrors(c)
);
},
warnings: (object, compilation, context, options, factory) => {
const { type, cachedGetWarnings } = context;
object.warnings = factory.create(
`${type}.warnings`,
cachedGetWarnings(compilation),
context
);
},
warningsCount: (
object,
compilation,
context,
{ warningsFilter },
factory
) => {
const { type, cachedGetWarnings } = context;
object.warningsCount = countWithChildren(compilation, (c, childType) => {
if (!warningsFilter && warningsFilter.length === 0)
return cachedGetWarnings(c);
return factory
.create(`${type}${childType}.warnings`, cachedGetWarnings(c), context)
.filter(warning => {
const warningString = Object.keys(warning)
.map(key => `${warning[key]}`)
.join("\n");
return !warningsFilter.some(filter =>
filter(warning, warningString)
);
});
});
},
errorDetails: (
object,
compilation,
{ cachedGetErrors, cachedGetWarnings },
{ errorDetails, errors, warnings }
) => {
if (errorDetails === "auto") {
if (warnings) {
const warnings = cachedGetWarnings(compilation);
object.filteredWarningDetailsCount = warnings
.map(e => typeof e !== "string" && e.details)
.filter(Boolean).length;
}
if (errors) {
const errors = cachedGetErrors(compilation);
if (errors.length >= 3) {
object.filteredErrorDetailsCount = errors
.map(e => typeof e !== "string" && e.details)
.filter(Boolean).length;
}
}
}
},
children: (object, compilation, context, options, factory) => {
const { type } = context;
object.children = factory.create(
`${type}.children`,
compilation.children,
context
);
}
},
asset: {
_: (object, asset, context, options, factory) => {
const { compilation } = context;
object.type = asset.type;
object.name = asset.name;
object.size = asset.source.size();
object.emitted = compilation.emittedAssets.has(asset.name);
object.comparedForEmit = compilation.comparedForEmitAssets.has(
asset.name
);
const cached = !object.emitted && !object.comparedForEmit;
object.cached = cached;
object.info = asset.info;
if (!cached || options.cachedAssets) {
Object.assign(
object,
factory.create(`${context.type}$visible`, asset, context)
);
}
}
},
asset$visible: {
_: (
object,
asset,
{ compilation, compilationFileToChunks, compilationAuxiliaryFileToChunks }
) => {
const chunks = compilationFileToChunks.get(asset.name) || [];
const auxiliaryChunks =
compilationAuxiliaryFileToChunks.get(asset.name) || [];
object.chunkNames = uniqueOrderedArray(
chunks,
c => (c.name ? [c.name] : []),
compareIds
);
object.chunkIdHints = uniqueOrderedArray(
chunks,
c => Array.from(c.idNameHints),
compareIds
);
object.auxiliaryChunkNames = uniqueOrderedArray(
auxiliaryChunks,
c => (c.name ? [c.name] : []),
compareIds
);
object.auxiliaryChunkIdHints = uniqueOrderedArray(
auxiliaryChunks,
c => Array.from(c.idNameHints),
compareIds
);
object.filteredRelated = asset.related ? asset.related.length : undefined;
},
relatedAssets: (object, asset, context, options, factory) => {
const { type } = context;
object.related = factory.create(
`${type.slice(0, -8)}.related`,
asset.related,
context
);
object.filteredRelated = asset.related
? asset.related.length - object.related.length
: undefined;
},
ids: (
object,
asset,
{ compilationFileToChunks, compilationAuxiliaryFileToChunks }
) => {
const chunks = compilationFileToChunks.get(asset.name) || [];
const auxiliaryChunks =
compilationAuxiliaryFileToChunks.get(asset.name) || [];
object.chunks = uniqueOrderedArray(chunks, c => c.ids, compareIds);
object.auxiliaryChunks = uniqueOrderedArray(
auxiliaryChunks,
c => c.ids,
compareIds
);
},
performance: (object, asset) => {
object.isOverSizeLimit = SizeLimitsPlugin.isOverSizeLimit(asset.source);
}
},
chunkGroup: {
_: (
object,
{ name, chunkGroup },
{ compilation, compilation: { moduleGraph, chunkGraph } },
{ ids, chunkGroupAuxiliary, chunkGroupChildren, chunkGroupMaxAssets }
) => {
const children =
chunkGroupChildren &&
chunkGroup.getChildrenByOrders(moduleGraph, chunkGraph);
/**
* @param {string} name Name
* @returns {{ name: string, size: number }} Asset object
*/
const toAsset = name => {
const asset = compilation.getAsset(name);
return {
name,
size: asset ? asset.info.size : -1
};
};
/** @type {(total: number, asset: { size: number }) => number} */
const sizeReducer = (total, { size }) => total + size;
const assets = uniqueArray(chunkGroup.chunks, c => c.files).map(toAsset);
const auxiliaryAssets = uniqueOrderedArray(
chunkGroup.chunks,
c => c.auxiliaryFiles,
compareIds
).map(toAsset);
const assetsSize = assets.reduce(sizeReducer, 0);
const auxiliaryAssetsSize = auxiliaryAssets.reduce(sizeReducer, 0);
/** @type {KnownStatsChunkGroup} */
const statsChunkGroup = {
name,
chunks: ids ? chunkGroup.chunks.map(c => c.id) : undefined,
assets: assets.length <= chunkGroupMaxAssets ? assets : undefined,
filteredAssets:
assets.length <= chunkGroupMaxAssets ? 0 : assets.length,
assetsSize,
auxiliaryAssets:
chunkGroupAuxiliary && auxiliaryAssets.length <= chunkGroupMaxAssets
? auxiliaryAssets
: undefined,
filteredAuxiliaryAssets:
chunkGroupAuxiliary && auxiliaryAssets.length <= chunkGroupMaxAssets
? 0
: auxiliaryAssets.length,
auxiliaryAssetsSize,
children: children
? mapObject(children, groups =>
groups.map(group => {
const assets = uniqueArray(group.chunks, c => c.files).map(
toAsset
);
const auxiliaryAssets = uniqueOrderedArray(
group.chunks,
c => c.auxiliaryFiles,
compareIds
).map(toAsset);
/** @type {KnownStatsChunkGroup} */
const childStatsChunkGroup = {
name: group.name,
chunks: ids ? group.chunks.map(c => c.id) : undefined,
assets:
assets.length <= chunkGroupMaxAssets ? assets : undefined,
filteredAssets:
assets.length <= chunkGroupMaxAssets ? 0 : assets.length,
auxiliaryAssets:
chunkGroupAuxiliary &&
auxiliaryAssets.length <= chunkGroupMaxAssets
? auxiliaryAssets
: undefined,
filteredAuxiliaryAssets:
chunkGroupAuxiliary &&
auxiliaryAssets.length <= chunkGroupMaxAssets
? 0
: auxiliaryAssets.length
};
return childStatsChunkGroup;
})
)
: undefined,
childAssets: children
? mapObject(children, groups => {
/** @type {Set<string>} */
const set = new Set();
for (const group of groups) {
for (const chunk of group.chunks) {
for (const asset of chunk.files) {
set.add(asset);
}
}
}
return Array.from(set);
})
: undefined
};
Object.assign(object, statsChunkGroup);
},
performance: (object, { chunkGroup }) => {
object.isOverSizeLimit = SizeLimitsPlugin.isOverSizeLimit(chunkGroup);
}
},
module: {
_: (object, module, context, options, factory) => {
const { compilation, type } = context;
const built = compilation.builtModules.has(module);
const codeGenerated = compilation.codeGeneratedModules.has(module);
/** @type {{[x: string]: number}} */
const sizes = {};
for (const sourceType of module.getSourceTypes()) {
sizes[sourceType] = module.size(sourceType);
}
/** @type {KnownStatsModule} */
const statsModule = {
type: "module",
moduleType: module.type,
layer: module.layer,
size: module.size(),
sizes,
built,
codeGenerated,
cached: !built && !codeGenerated
};
Object.assign(object, statsModule);
if (built || codeGenerated || options.cachedModules) {
Object.assign(
object,
factory.create(`${type}$visible`, module, context)
);
}
}
},
module$visible: {
_: (object, module, context, { requestShortener }, factory) => {
const { compilation, type, rootModules } = context;
const { moduleGraph } = compilation;
/** @type {Module[]} */
const path = [];
const issuer = moduleGraph.getIssuer(module);
let current = issuer;
while (current) {
path.push(current);
current = moduleGraph.getIssuer(current);
}
path.reverse();
const profile = moduleGraph.getProfile(module);
const errors = module.getErrors();
const errorsCount = errors !== undefined ? countIterable(errors) : 0;
const warnings = module.getWarnings();
const warningsCount =
warnings !== undefined ? countIterable(warnings) : 0;
/** @type {{[x: string]: number}} */
const sizes = {};
for (const sourceType of module.getSourceTypes()) {
sizes[sourceType] = module.size(sourceType);
}
/** @type {KnownStatsModule} */
const statsModule = {
identifier: module.identifier(),
name: module.readableIdentifier(requestShortener),
nameForCondition: module.nameForCondition(),
index: moduleGraph.getPreOrderIndex(module),
preOrderIndex: moduleGraph.getPreOrderIndex(module),
index2: moduleGraph.getPostOrderIndex(module),
postOrderIndex: moduleGraph.getPostOrderIndex(module),
cacheable: module.buildInfo.cacheable,
optional: module.isOptional(moduleGraph),
orphan:
!type.endsWith("module.modules[].module$visible") &&
compilation.chunkGraph.getNumberOfModuleChunks(module) === 0,
dependent: rootModules ? !rootModules.has(module) : undefined,
issuer: issuer && issuer.identifier(),
issuerName: issuer && issuer.readableIdentifier(requestShortener),
issuerPath:
issuer &&
factory.create(`${type.slice(0, -8)}.issuerPath`, path, context),
failed: errorsCount > 0,
errors: errorsCount,
warnings: warningsCount
};
Object.assign(object, statsModule);
if (profile) {
object.profile = factory.create(
`${type.slice(0, -8)}.profile`,
profile,
context
);
}
},
ids: (object, module, { compilation: { chunkGraph, moduleGraph } }) => {
object.id = chunkGraph.getModuleId(module);
const issuer = moduleGraph.getIssuer(module);
object.issuerId = issuer && chunkGraph.getModuleId(issuer);
object.chunks = Array.from(
chunkGraph.getOrderedModuleChunksIterable(module, compareChunksById),
chunk => chunk.id
);
},
moduleAssets: (object, module) => {
object.assets = module.buildInfo.assets
? Object.keys(module.buildInfo.assets)
: [];
},
reasons: (object, module, context, options, factory) => {
const {
type,
compilation: { moduleGraph }
} = context;
object.reasons = factory.create(
`${type.slice(0, -8)}.reasons`,
Array.from(moduleGraph.getIncomingConnections(module)),
context
);
},
usedExports: (
object,
module,
{ runtime, compilation: { moduleGraph } }
) => {
const usedExports = moduleGraph.getUsedExports(module, runtime);
if (usedExports === null) {
object.usedExports = null;
} else if (typeof usedExports === "boolean") {
object.usedExports = usedExports;
} else {
object.usedExports = Array.from(usedExports);
}
},
providedExports: (object, module, { compilation: { moduleGraph } }) => {
const providedExports = moduleGraph.getProvidedExports(module);
object.providedExports = Array.isArray(providedExports)
? providedExports
: null;
},
optimizationBailout: (
object,
module,
{ compilation: { moduleGraph } },
{ requestShortener }
) => {
object.optimizationBailout = moduleGraph
.getOptimizationBailout(module)
.map(item => {
if (typeof item === "function") return item(requestShortener);
return item;
});
},
depth: (object, module, { compilation: { moduleGraph } }) => {
object.depth = moduleGraph.getDepth(module);
},
nestedModules: (object, module, context, options, factory) => {
const { type } = context;
if (module instanceof ConcatenatedModule) {
const modules = module.modules;
const groupedModules = factory.create(
`${type.slice(0, -8)}.modules`,
modules,
context
);
const limited = spaceLimited(
groupedModules,
options.nestedModulesSpace
);
object.modules = limited.children;
object.filteredModules = limited.filteredChildren;
}
},
source: (object, module) => {
const originalSource = module.originalSource();
if (originalSource) {
object.source = originalSource.source();
}
}
},
profile: {
_: (object, profile) => {
/** @type {KnownStatsProfile} */
const statsProfile = {
total:
profile.factory +
profile.restoring +
profile.integration +
profile.building +
profile.storing,
resolving: profile.factory,
restoring: profile.restoring,
building: profile.building,
integration: profile.integration,
storing: profile.storing,
additionalResolving: profile.additionalFactories,
additionalIntegration: profile.additionalIntegration,
// TODO remove this in webpack 6
factory: profile.factory,
// TODO remove this in webpack 6
dependencies: profile.additionalFactories
};
Object.assign(object, statsProfile);
}
},
moduleIssuer: {
_: (object, module, context, { requestShortener }, factory) => {
const { compilation, type } = context;
const { moduleGraph } = compilation;
const profile = moduleGraph.getProfile(module);
/** @type {KnownStatsModuleIssuer} */
const statsModuleIssuer = {
identifier: module.identifier(),
name: module.readableIdentifier(requestShortener)
};
Object.assign(object, statsModuleIssuer);
if (profile) {
object.profile = factory.create(`${type}.profile`, profile, context);
}
},
ids: (object, module, { compilation: { chunkGraph } }) => {
object.id = chunkGraph.getModuleId(module);
}
},
moduleReason: {
_: (object, reason, { runtime }, { requestShortener }) => {
const dep = reason.dependency;
const moduleDep =
dep && dep instanceof ModuleDependency ? dep : undefined;
/** @type {KnownStatsModuleReason} */
const statsModuleReason = {
moduleIdentifier: reason.originModule
? reason.originModule.identifier()
: null,
module: reason.originModule
? reason.originModule.readableIdentifier(requestShortener)
: null,
moduleName: reason.originModule
? reason.originModule.readableIdentifier(requestShortener)
: null,
resolvedModuleIdentifier: reason.resolvedOriginModule
? reason.resolvedOriginModule.identifier()
: null,
resolvedModule: reason.resolvedOriginModule
? reason.resolvedOriginModule.readableIdentifier(requestShortener)
: null,
type: reason.dependency ? reason.dependency.type : null,
active: reason.isActive(runtime),
explanation: reason.explanation,
userRequest: (moduleDep && moduleDep.userRequest) || null
};
Object.assign(object, statsModuleReason);
if (reason.dependency) {
const locInfo = formatLocation(reason.dependency.loc);
if (locInfo) {
object.loc = locInfo;
}
}
},
ids: (object, reason, { compilation: { chunkGraph } }) => {
object.moduleId = reason.originModule
? chunkGraph.getModuleId(reason.originModule)
: null;
object.resolvedModuleId = reason.resolvedOriginModule
? chunkGraph.getModuleId(reason.resolvedOriginModule)
: null;
}
},
chunk: {
_: (object, chunk, { makePathsRelative, compilation: { chunkGraph } }) => {
const childIdByOrder = chunk.getChildIdsByOrders(chunkGraph);
/** @type {KnownStatsChunk} */
const statsChunk = {
rendered: chunk.rendered,
initial: chunk.canBeInitial(),
entry: chunk.hasRuntime(),
recorded: AggressiveSplittingPlugin.wasChunkRecorded(chunk),
reason: chunk.chunkReason,
size: chunkGraph.getChunkModulesSize(chunk),
sizes: chunkGraph.getChunkModulesSizes(chunk),
names: chunk.name ? [chunk.name] : [],
idHints: Array.from(chunk.idNameHints),
runtime:
chunk.runtime === undefined
? undefined
: typeof chunk.runtime === "string"
? [makePathsRelative(chunk.runtime)]
: Array.from(chunk.runtime.sort(), makePathsRelative),
files: Array.from(chunk.files),
auxiliaryFiles: Array.from(chunk.auxiliaryFiles).sort(compareIds),
hash: chunk.renderedHash,
childrenByOrder: childIdByOrder
};
Object.assign(object, statsChunk);
},
ids: (object, chunk) => {
object.id = chunk.id;
},
chunkRelations: (object, chunk, { compilation: { chunkGraph } }) => {
/** @type {Set<string|number>} */
const parents = new Set();
/** @type {Set<string|number>} */
const children = new Set();
/** @type {Set<string|number>} */
const siblings = new Set();
for (const chunkGroup of chunk.groupsIterable) {
for (const parentGroup of chunkGroup.parentsIterable) {
for (const chunk of parentGroup.chunks) {
parents.add(chunk.id);
}
}
for (const childGroup of chunkGroup.childrenIterable) {
for (const chunk of childGroup.chunks) {
children.add(chunk.id);
}
}
for (const sibling of chunkGroup.chunks) {
if (sibling !== chunk) siblings.add(sibling.id);
}
}
object.siblings = Array.from(siblings).sort(compareIds);
object.parents = Array.from(parents).sort(compareIds);
object.children = Array.from(children).sort(compareIds);
},
chunkModules: (object, chunk, context, options, factory) => {
const {
type,
compilation: { chunkGraph }
} = context;
const array = chunkGraph.getChunkModules(chunk);
const groupedModules = factory.create(`${type}.modules`, array, {
...context,
runtime: chunk.runtime,
rootModules: new Set(chunkGraph.getChunkRootModules(chunk))
});
const limited = spaceLimited(groupedModules, options.chunkModulesSpace);
object.modules = limited.children;
object.filteredModules = limited.filteredChildren;
},
chunkOrigins: (object, chunk, context, options, factory) => {
const {
type,
compilation: { chunkGraph }
} = context;
/** @type {Set<string>} */
const originsKeySet = new Set();
const origins = [];
for (const g of chunk.groupsIterable) {
origins.push(...g.origins);
}
const array = origins.filter(origin => {
const key = [
origin.module ? chunkGraph.getModuleId(origin.module) : undefined,
formatLocation(origin.loc),
origin.request
].join();
if (originsKeySet.has(key)) return false;
originsKeySet.add(key);
return true;
});
object.origins = factory.create(`${type}.origins`, array, context);
}
},
chunkOrigin: {
_: (object, origin, context, { requestShortener }) => {
/** @type {KnownStatsChunkOrigin} */
const statsChunkOrigin = {
module: origin.module ? origin.module.identifier() : "",
moduleIdentifier: origin.module ? origin.module.identifier() : "",
moduleName: origin.module
? origin.module.readableIdentifier(requestShortener)
: "",
loc: formatLocation(origin.loc),
request: origin.request
};
Object.assign(object, statsChunkOrigin);
},
ids: (object, origin, { compilation: { chunkGraph } }) => {
object.moduleId = origin.module
? chunkGraph.getModuleId(origin.module)
: undefined;
}
},
error: EXTRACT_ERROR,
warning: EXTRACT_ERROR,
moduleTraceItem: {
_: (object, { origin, module }, context, { requestShortener }, factory) => {
const {
type,
compilation: { moduleGraph }
} = context;
object.originIdentifier = origin.identifier();
object.originName = origin.readableIdentifier(requestShortener);
object.moduleIdentifier = module.identifier();
object.moduleName = module.readableIdentifier(requestShortener);
const dependencies = Array.from(
moduleGraph.getIncomingConnections(module)
)
.filter(c => c.resolvedOriginModule === origin && c.dependency)
.map(c => c.dependency);
object.dependencies = factory.create(
`${type}.dependencies`,
Array.from(new Set(dependencies)),
context
);
},
ids: (object, { origin, module }, { compilation: { chunkGraph } }) => {
object.originId = chunkGraph.getModuleId(origin);
object.moduleId = chunkGraph.getModuleId(module);
}
},
moduleTraceDependency: {
_: (object, dependency) => {
object.loc = formatLocation(dependency.loc);
}
}
};
/** @type {Record<string, Record<string, (thing: any, context: StatsFactoryContext, options: NormalizedStatsOptions) => boolean | undefined>>} */
const FILTER = {
"module.reasons": {
"!orphanModules": (reason, { compilation: { chunkGraph } }) => {
if (
reason.originModule &&
chunkGraph.getNumberOfModuleChunks(reason.originModule) === 0
) {
return false;
}
}
}
};
/** @type {Record<string, Record<string, (thing: Object, context: StatsFactoryContext, options: NormalizedStatsOptions) => boolean | undefined>>} */
const FILTER_RESULTS = {
"compilation.warnings": {
warningsFilter: util.deprecate(
(warning, context, { warningsFilter }) => {
const warningString = Object.keys(warning)
.map(key => `${warning[key]}`)
.join("\n");
return !warningsFilter.some(filter => filter(warning, warningString));
},
"config.stats.warningsFilter is deprecated in favor of config.ignoreWarnings",
"DEP_WEBPACK_STATS_WARNINGS_FILTER"
)
}
};
/** @type {Record<string, (comparators: Function[], context: StatsFactoryContext) => void>} */
const MODULES_SORTER = {
_: (comparators, { compilation: { moduleGraph } }) => {
comparators.push(
compareSelect(
/**
* @param {Module} m module
* @returns {number} depth
*/
m => moduleGraph.getDepth(m),
compareNumbers
),
compareSelect(
/**
* @param {Module} m module
* @returns {number} index
*/
m => moduleGraph.getPreOrderIndex(m),
compareNumbers
),
compareSelect(
/**
* @param {Module} m module
* @returns {string} identifier
*/
m => m.identifier(),
compareIds
)
);
}
};
/** @type {Record<string, Record<string, (comparators: Function[], context: StatsFactoryContext) => void>>} */
const SORTERS = {
"compilation.chunks": {
_: comparators => {
comparators.push(compareSelect(c => c.id, compareIds));
}
},
"compilation.modules": MODULES_SORTER,
"chunk.rootModules": MODULES_SORTER,
"chunk.modules": MODULES_SORTER,
"module.modules": MODULES_SORTER,
"module.reasons": {
_: (comparators, { compilation: { chunkGraph } }) => {
comparators.push(
compareSelect(x => x.originModule, compareModulesByIdentifier)
);
comparators.push(
compareSelect(x => x.resolvedOriginModule, compareModulesByIdentifier)
);
comparators.push(
compareSelect(
x => x.dependency,
concatComparators(
compareSelect(
/**
* @param {Dependency} x dependency
* @returns {DependencyLocation} location
*/
x => x.loc,
compareLocations
),
compareSelect(x => x.type, compareIds)
)
)
);
}
},
"chunk.origins": {
_: (comparators, { compilation: { chunkGraph } }) => {
comparators.push(
compareSelect(
origin =>
origin.module ? chunkGraph.getModuleId(origin.module) : undefined,
compareIds
),
compareSelect(origin => formatLocation(origin.loc), compareIds),
compareSelect(origin => origin.request, compareIds)
);
}
}
};
const getItemSize = item => {
// Each item takes 1 line
// + the size of the children
// + 1 extra line when it has children and filteredChildren
return !item.children
? 1
: item.filteredChildren
? 2 + getTotalSize(item.children)
: 1 + getTotalSize(item.children);
};
const getTotalSize = children => {
let size = 0;
for (const child of children) {
size += getItemSize(child);
}
return size;
};
const getTotalItems = children => {
let count = 0;
for (const child of children) {
if (!child.children && !child.filteredChildren) {
count++;
} else {
if (child.children) count += getTotalItems(child.children);
if (child.filteredChildren) count += child.filteredChildren;
}
}
return count;
};
const collapse = children => {
// After collapse each child must take exactly one line
const newChildren = [];
for (const child of children) {
if (child.children) {
let filteredChildren = child.filteredChildren || 0;
filteredChildren += getTotalItems(child.children);
newChildren.push({
...child,
children: undefined,
filteredChildren
});
} else {
newChildren.push(child);
}
}
return newChildren;
};
const spaceLimited = (itemsAndGroups, max) => {
/** @type {any[] | undefined} */
let children = undefined;
/** @type {number | undefined} */
let filteredChildren = undefined;
// This are the groups, which take 1+ lines each
const groups = itemsAndGroups.filter(c => c.children || c.filteredChildren);
// The sizes of the groups are stored in groupSizes
const groupSizes = groups.map(g => getItemSize(g));
// This are the items, which take 1 line each
const items = itemsAndGroups.filter(c => !c.children && !c.filteredChildren);
// The total of group sizes
let groupsSize = groupSizes.reduce((a, b) => a + b, 0);
if (groupsSize + items.length <= max) {
// The total size in the current state fits into the max
// keep all
children = groups.concat(items);
} else if (
groups.length > 0 &&
groups.length + Math.min(1, items.length) < max
) {
// If each group would take 1 line the total would be below the maximum
// collapse some groups, keep items
while (groupsSize + items.length + (filteredChildren ? 1 : 0) > max) {
// calculate how much we are over the size limit
// this allows to approach the limit faster
// it's always > 1
const oversize =
items.length + groupsSize + (filteredChildren ? 1 : 0) - max;
// Find the maximum group and process only this one
const maxGroupSize = Math.max(...groupSizes);
if (maxGroupSize < items.length) {
filteredChildren = items.length;
items.length = 0;
continue;
}
for (let i = 0; i < groups.length; i++) {
if (groupSizes[i] === maxGroupSize) {
const group = groups[i];
// run this algorithm recursively and limit the size of the children to
// current size - oversize / number of groups
// So it should always end up being smaller
const headerSize = !group.children
? 0
: group.filteredChildren
? 2
: 1;
const limited = spaceLimited(
group.children,
groupSizes[i] - headerSize - oversize / groups.length
);
groups[i] = {
...group,
children: limited.children,
filteredChildren:
(group.filteredChildren || 0) + limited.filteredChildren
};
const newSize = getItemSize(groups[i]);
groupsSize -= groupSizes[i] - newSize;
groupSizes[i] = newSize;
break;
}
}
}
children = groups.concat(items);
} else if (
groups.length > 0 &&
groups.length + Math.min(1, items.length) <= max
) {
// If we have only enough space to show one line per group and one line for the filtered items
// collapse all groups and items
children = groups.length ? collapse(groups) : undefined;
filteredChildren = items.length;
} else {
// If we have no space
// collapse complete group
filteredChildren = getTotalItems(itemsAndGroups);
}
return {
children,
filteredChildren
};
};
const assetGroup = (children, assets) => {
let size = 0;
for (const asset of children) {
size += asset.size;
}
return {
size
};
};
const moduleGroup = (children, modules) => {
let size = 0;
const sizes = {};
for (const module of children) {
size += module.size;
for (const key of Object.keys(module.sizes)) {
sizes[key] = (sizes[key] || 0) + module.sizes[key];
}
}
return {
size,
sizes
};
};
/** @type {Record<string, (groupConfigs: GroupConfig[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void>} */
const ASSETS_GROUPERS = {
_: (groupConfigs, context, options) => {
const groupByFlag = (name, exclude) => {
groupConfigs.push({
getKeys: asset => {
return asset[name] ? ["1"] : undefined;
},
getOptions: () => {
return {
groupChildren: !exclude,
force: exclude
};
},
createGroup: (key, children, assets) => {
return exclude
? {
type: "assets by status",
[name]: !!key,
filteredChildren: assets.length,
...assetGroup(children, assets)
}
: {
type: "assets by status",
[name]: !!key,
children,
...assetGroup(children, assets)
};
}
});
};
const {
groupAssetsByEmitStatus,
groupAssetsByPath,
groupAssetsByExtension
} = options;
if (groupAssetsByEmitStatus) {
groupByFlag("emitted");
groupByFlag("comparedForEmit");
groupByFlag("isOverSizeLimit");
}
if (groupAssetsByEmitStatus || !options.cachedAssets) {
groupByFlag("cached", !options.cachedAssets);
}
if (groupAssetsByPath || groupAssetsByExtension) {
groupConfigs.push({
getKeys: asset => {
const extensionMatch =
groupAssetsByExtension && /(\.[^.]+)(?:\?.*|$)/.exec(asset.name);
const extension = extensionMatch ? extensionMatch[1] : "";
const pathMatch =
groupAssetsByPath && /(.+)[/\\][^/\\]+(?:\?.*|$)/.exec(asset.name);
const path = pathMatch ? pathMatch[1].split(/[/\\]/) : [];
const keys = [];
if (groupAssetsByPath) {
keys.push(".");
if (extension)
keys.push(
path.length
? `${path.join("/")}/*${extension}`
: `*${extension}`
);
while (path.length > 0) {
keys.push(path.join("/") + "/");
path.pop();
}
} else {
if (extension) keys.push(`*${extension}`);
}
return keys;
},
createGroup: (key, children, assets) => {
return {
type: groupAssetsByPath ? "assets by path" : "assets by extension",
name: key,
children,
...assetGroup(children, assets)
};
}
});
}
},
groupAssetsByInfo: (groupConfigs, context, options) => {
const groupByAssetInfoFlag = name => {
groupConfigs.push({
getKeys: asset => {
return asset.info && asset.info[name] ? ["1"] : undefined;
},
createGroup: (key, children, assets) => {
return {
type: "assets by info",
info: {
[name]: !!key
},
children,
...assetGroup(children, assets)
};
}
});
};
groupByAssetInfoFlag("immutable");
groupByAssetInfoFlag("development");
groupByAssetInfoFlag("hotModuleReplacement");
},
groupAssetsByChunk: (groupConfigs, context, options) => {
const groupByNames = name => {
groupConfigs.push({
getKeys: asset => {
return asset[name];
},
createGroup: (key, children, assets) => {
return {
type: "assets by chunk",
[name]: [key],
children,
...assetGroup(children, assets)
};
}
});
};
groupByNames("chunkNames");
groupByNames("auxiliaryChunkNames");
groupByNames("chunkIdHints");
groupByNames("auxiliaryChunkIdHints");
},
excludeAssets: (groupConfigs, context, { excludeAssets }) => {
groupConfigs.push({
getKeys: asset => {
const ident = asset.name;
const excluded = excludeAssets.some(fn => fn(ident, asset));
if (excluded) return ["excluded"];
},
getOptions: () => ({
groupChildren: false,
force: true
}),
createGroup: (key, children, assets) => ({
type: "hidden assets",
filteredChildren: assets.length,
...assetGroup(children, assets)
})
});
}
};
/** @type {function("module" | "chunk" | "root-of-chunk" | "nested"): Record<string, (groupConfigs: GroupConfig[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void>} */
const MODULES_GROUPERS = type => ({
_: (groupConfigs, context, options) => {
const groupByFlag = (name, type, exclude) => {
groupConfigs.push({
getKeys: module => {
return module[name] ? ["1"] : undefined;
},
getOptions: () => {
return {
groupChildren: !exclude,
force: exclude
};
},
createGroup: (key, children, modules) => {
return {
type,
[name]: !!key,
...(exclude ? { filteredChildren: modules.length } : { children }),
...moduleGroup(children, modules)
};
}
});
};
const {
groupModulesByCacheStatus,
groupModulesByLayer,
groupModulesByAttributes,
groupModulesByType,
groupModulesByPath,
groupModulesByExtension
} = options;
if (groupModulesByAttributes) {
groupByFlag("errors", "modules with errors");
groupByFlag("warnings", "modules with warnings");
groupByFlag("assets", "modules with assets");
groupByFlag("optional", "optional modules");
}
if (groupModulesByCacheStatus) {
groupByFlag("cacheable", "cacheable modules");
groupByFlag("built", "built modules");
groupByFlag("codeGenerated", "code generated modules");
}
if (groupModulesByCacheStatus || !options.cachedModules) {
groupByFlag("cached", "cached modules", !options.cachedModules);
}
if (groupModulesByAttributes || !options.orphanModules) {
groupByFlag("orphan", "orphan modules", !options.orphanModules);
}
if (groupModulesByAttributes || !options.dependentModules) {
groupByFlag("dependent", "dependent modules", !options.dependentModules);
}
if (groupModulesByType || !options.runtimeModules) {
groupConfigs.push({
getKeys: module => {
if (!module.moduleType) return;
if (groupModulesByType) {
return [module.moduleType.split("/", 1)[0]];
} else if (module.moduleType === "runtime") {
return ["runtime"];
}
},
getOptions: key => {
const exclude = key === "runtime" && !options.runtimeModules;
return {
groupChildren: !exclude,
force: exclude
};
},
createGroup: (key, children, modules) => {
const exclude = key === "runtime" && !options.runtimeModules;
return {
type: `${key} modules`,
moduleType: key,
...(exclude ? { filteredChildren: modules.length } : { children }),
...moduleGroup(children, modules)
};
}
});
}
if (groupModulesByLayer) {
groupConfigs.push({
getKeys: module => {
return [module.layer];
},
createGroup: (key, children, modules) => {
return {
type: "modules by layer",
layer: key,
children,
...moduleGroup(children, modules)
};
}
});
}
if (groupModulesByPath || groupModulesByExtension) {
groupConfigs.push({
getKeys: module => {
if (!module.name) return;
const resource = parseResource(module.name.split("!").pop()).path;
const extensionMatch =
groupModulesByExtension && /(\.[^.]+)(?:\?.*|$)/.exec(resource);
const extension = extensionMatch ? extensionMatch[1] : "";
const pathMatch =
groupModulesByPath && /(.+)[/\\][^/\\]+(?:\?.*|$)/.exec(resource);
const path = pathMatch ? pathMatch[1].split(/[/\\]/) : [];
const keys = [];
if (groupModulesByPath) {
if (extension)
keys.push(
path.length
? `${path.join("/")}/*${extension}`
: `*${extension}`
);
while (path.length > 0) {
keys.push(path.join("/") + "/");
path.pop();
}
} else {
if (extension) keys.push(`*${extension}`);
}
return keys;
},
createGroup: (key, children, modules) => {
return {
type: groupModulesByPath
? "modules by path"
: "modules by extension",
name: key,
children,
...moduleGroup(children, modules)
};
}
});
}
},
excludeModules: (groupConfigs, context, { excludeModules }) => {
groupConfigs.push({
getKeys: module => {
const name = module.name;
if (name) {
const excluded = excludeModules.some(fn => fn(name, module, type));
if (excluded) return ["1"];
}
},
getOptions: () => ({
groupChildren: false,
force: true
}),
createGroup: (key, children, modules) => ({
type: "hidden modules",
filteredChildren: children.length,
...moduleGroup(children, modules)
})
});
}
});
/** @type {Record<string, Record<string, (groupConfigs: GroupConfig[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void>>} */
const RESULT_GROUPERS = {
"compilation.assets": ASSETS_GROUPERS,
"asset.related": ASSETS_GROUPERS,
"compilation.modules": MODULES_GROUPERS("module"),
"chunk.modules": MODULES_GROUPERS("chunk"),
"chunk.rootModules": MODULES_GROUPERS("root-of-chunk"),
"module.modules": MODULES_GROUPERS("nested")
};
// remove a prefixed "!" that can be specified to reverse sort order
const normalizeFieldKey = field => {
if (field[0] === "!") {
return field.substr(1);
}
return field;
};
// if a field is prefixed by a "!" reverse sort order
const sortOrderRegular = field => {
if (field[0] === "!") {
return false;
}
return true;
};
/**
* @param {string} field field name
* @returns {function(Object, Object): number} comparators
*/
const sortByField = field => {
if (!field) {
/**
* @param {any} a first
* @param {any} b second
* @returns {-1|0|1} zero
*/
const noSort = (a, b) => 0;
return noSort;
}
const fieldKey = normalizeFieldKey(field);
let sortFn = compareSelect(m => m[fieldKey], compareIds);
// if a field is prefixed with a "!" the sort is reversed!
const sortIsRegular = sortOrderRegular(field);
if (!sortIsRegular) {
const oldSortFn = sortFn;
sortFn = (a, b) => oldSortFn(b, a);
}
return sortFn;
};
const ASSET_SORTERS = {
/** @type {(comparators: Function[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void} */
assetsSort: (comparators, context, { assetsSort }) => {
comparators.push(sortByField(assetsSort));
},
_: comparators => {
comparators.push(compareSelect(a => a.name, compareIds));
}
};
/** @type {Record<string, Record<string, (comparators: Function[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void>>} */
const RESULT_SORTERS = {
"compilation.chunks": {
chunksSort: (comparators, context, { chunksSort }) => {
comparators.push(sortByField(chunksSort));
}
},
"compilation.modules": {
modulesSort: (comparators, context, { modulesSort }) => {
comparators.push(sortByField(modulesSort));
}
},
"chunk.modules": {
chunkModulesSort: (comparators, context, { chunkModulesSort }) => {
comparators.push(sortByField(chunkModulesSort));
}
},
"module.modules": {
nestedModulesSort: (comparators, context, { nestedModulesSort }) => {
comparators.push(sortByField(nestedModulesSort));
}
},
"compilation.assets": ASSET_SORTERS,
"asset.related": ASSET_SORTERS
};
/**
* @param {Record<string, Record<string, Function>>} config the config see above
* @param {NormalizedStatsOptions} options stats options
* @param {function(string, Function): void} fn handler function called for every active line in config
* @returns {void}
*/
const iterateConfig = (config, options, fn) => {
for (const hookFor of Object.keys(config)) {
const subConfig = config[hookFor];
for (const option of Object.keys(subConfig)) {
if (option !== "_") {
if (option.startsWith("!")) {
if (options[option.slice(1)]) continue;
} else {
const value = options[option];
if (
value === false ||
value === undefined ||
(Array.isArray(value) && value.length === 0)
)
continue;
}
}
fn(hookFor, subConfig[option]);
}
}
};
/** @type {Record<string, string>} */
const ITEM_NAMES = {
"compilation.children[]": "compilation",
"compilation.modules[]": "module",
"compilation.entrypoints[]": "chunkGroup",
"compilation.namedChunkGroups[]": "chunkGroup",
"compilation.errors[]": "error",
"compilation.warnings[]": "warning",
"chunk.modules[]": "module",
"chunk.rootModules[]": "module",
"chunk.origins[]": "chunkOrigin",
"compilation.chunks[]": "chunk",
"compilation.assets[]": "asset",
"asset.related[]": "asset",
"module.issuerPath[]": "moduleIssuer",
"module.reasons[]": "moduleReason",
"module.modules[]": "module",
"module.children[]": "module",
"moduleTrace[]": "moduleTraceItem",
"moduleTraceItem.dependencies[]": "moduleTraceDependency"
};
/**
* @param {Object[]} items items to be merged
* @returns {Object} an object
*/
const mergeToObject = items => {
const obj = Object.create(null);
for (const item of items) {
obj[item.name] = item;
}
return obj;
};
/** @type {Record<string, (items: Object[]) => any>} */
const MERGER = {
"compilation.entrypoints": mergeToObject,
"compilation.namedChunkGroups": mergeToObject
};
class DefaultStatsFactoryPlugin {
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap("DefaultStatsFactoryPlugin", compilation => {
compilation.hooks.statsFactory.tap(
"DefaultStatsFactoryPlugin",
(stats, options, context) => {
iterateConfig(SIMPLE_EXTRACTORS, options, (hookFor, fn) => {
stats.hooks.extract
.for(hookFor)
.tap("DefaultStatsFactoryPlugin", (obj, data, ctx) =>
fn(obj, data, ctx, options, stats)
);
});
iterateConfig(FILTER, options, (hookFor, fn) => {
stats.hooks.filter
.for(hookFor)
.tap("DefaultStatsFactoryPlugin", (item, ctx, idx, i) =>
fn(item, ctx, options, idx, i)
);
});
iterateConfig(FILTER_RESULTS, options, (hookFor, fn) => {
stats.hooks.filterResults
.for(hookFor)
.tap("DefaultStatsFactoryPlugin", (item, ctx, idx, i) =>
fn(item, ctx, options, idx, i)
);
});
iterateConfig(SORTERS, options, (hookFor, fn) => {
stats.hooks.sort
.for(hookFor)
.tap("DefaultStatsFactoryPlugin", (comparators, ctx) =>
fn(comparators, ctx, options)
);
});
iterateConfig(RESULT_SORTERS, options, (hookFor, fn) => {
stats.hooks.sortResults
.for(hookFor)
.tap("DefaultStatsFactoryPlugin", (comparators, ctx) =>
fn(comparators, ctx, options)
);
});
iterateConfig(RESULT_GROUPERS, options, (hookFor, fn) => {
stats.hooks.groupResults
.for(hookFor)
.tap("DefaultStatsFactoryPlugin", (groupConfigs, ctx) =>
fn(groupConfigs, ctx, options)
);
});
for (const key of Object.keys(ITEM_NAMES)) {
const itemName = ITEM_NAMES[key];
stats.hooks.getItemName
.for(key)
.tap("DefaultStatsFactoryPlugin", () => itemName);
}
for (const key of Object.keys(MERGER)) {
const merger = MERGER[key];
stats.hooks.merge.for(key).tap("DefaultStatsFactoryPlugin", merger);
}
if (options.children) {
if (Array.isArray(options.children)) {
stats.hooks.getItemFactory
.for("compilation.children[].compilation")
.tap("DefaultStatsFactoryPlugin", (comp, { _index: idx }) => {
if (idx < options.children.length) {
return compilation.createStatsFactory(
compilation.createStatsOptions(
options.children[idx],
context
)
);
}
});
} else if (options.children !== true) {
const childFactory = compilation.createStatsFactory(
compilation.createStatsOptions(options.children, context)
);
stats.hooks.getItemFactory
.for("compilation.children[].compilation")
.tap("DefaultStatsFactoryPlugin", () => {
return childFactory;
});
}
}
}
);
});
}
}
module.exports = DefaultStatsFactoryPlugin;