UNPKG

jointjs

Version:

JavaScript diagramming library

1,684 lines (1,649 loc) 59.2 kB
/* 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 { 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").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 {Asset & { type: string, related: ExtendedAsset[] }} ExtendedAsset */ /** @template T @typedef {Record<string, (object: Object, data: T, context: StatsFactoryContext, options: NormalizedStatsOptions, factory: StatsFactory) => void>} ExtractorsByOption */ /** * @typedef {Object} SimpleExtractors * @property {ExtractorsByOption<Compilation>} compilation * @property {ExtractorsByOption<ExtendedAsset>} asset * @property {ExtractorsByOption<ExtendedAsset>} asset$visible * @property {ExtractorsByOption<{ name: string, chunkGroup: ChunkGroup }>} chunkGroup * @property {ExtractorsByOption<Module>} module * @property {ExtractorsByOption<Module>} module$visible * @property {ExtractorsByOption<Module>} moduleIssuer * @property {ExtractorsByOption<ModuleProfile>} profile * @property {ExtractorsByOption<ModuleGraphConnection>} moduleReason * @property {ExtractorsByOption<Chunk>} chunk * @property {ExtractorsByOption<OriginRecord>} chunkOrigin * @property {ExtractorsByOption<WebpackError>} error * @property {ExtractorsByOption<WebpackError>} warning * @property {ExtractorsByOption<{ origin: Module, module: Module }>} moduleTraceItem * @property {ExtractorsByOption<Dependency>} 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; }; /** * @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; }; /** * @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>} */ 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) => { if (typeof error !== "string") { object.details = error.details; } }, errorStack: (object, error) => { if (typeof error !== "string") { object.stack = error.stack; } } }; /** @type {SimpleExtractors} */ const SIMPLE_EXTRACTORS = { compilation: { _: (object, compilation, context) => { 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; } }, 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, ExtendedAsset>} */ const assetMap = new Map(); const assets = new Set(); for (const asset of compilation.getAssets()) { 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) ); }); }); }, logging: (object, compilation, _context, options, factory) => { const util = require("util"); const { loggingDebug, loggingTrace, context } = options; object.logging = {}; let acceptedTypes; let collapsedGroups = false; switch (options.logging) { case "none": 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( context, compilation.compiler.root ); let depthInCollapsedGroup = 0; for (const [origin, logEntries] of compilation.logging) { const debugMode = loggingDebug.some(fn => fn(origin)); const groupStack = []; 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)); } 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 }; } }, 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); const toAsset = name => { const asset = compilation.getAsset(name); return { name, size: asset ? asset.info.size : -1 }; }; 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); Object.assign(object, { 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); return { 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 }; }) ) : 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 }); }, 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); const sizes = {}; for (const sourceType of module.getSourceTypes()) { sizes[sourceType] = module.size(sourceType); } Object.assign(object, { type: "module", moduleType: module.type, layer: module.layer, size: module.size(), sizes, built, codeGenerated, cached: !built && !codeGenerated }); 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; const sizes = {}; for (const sourceType of module.getSourceTypes()) { sizes[sourceType] = module.size(sourceType); } Object.assign(object, { 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 }); 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) => { Object.assign(object, { 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 }); } }, moduleIssuer: { _: (object, module, context, { requestShortener }, factory) => { const { compilation, type } = context; const { moduleGraph } = compilation; const profile = moduleGraph.getProfile(module); Object.assign(object, { identifier: module.identifier(), name: module.readableIdentifier(requestShortener) }); 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; Object.assign(object, { 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 }); 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); Object.assign(object, { 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 }); }, 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 }) => { Object.assign(object, { 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 }); }, 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(string): 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) => {