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

"use strict";

const { UsageState } = require("../ExportsInfo");
const {
	numberToIdentifier,
	NUMBER_OF_IDENTIFIER_START_CHARS,
	NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS
} = require("../Template");
const { assignDeterministicIds } = require("../ids/IdHelpers");
const { compareSelect, compareStringsNumeric } = require("../util/comparators");

/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../ExportsInfo")} ExportsInfo */
/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */

/**
 * @param {ExportsInfo} exportsInfo exports info
 * @returns {boolean} mangle is possible
 */
const canMangle = exportsInfo => {
	if (exportsInfo.otherExportsInfo.getUsed(undefined) !== UsageState.Unused)
		return false;
	let hasSomethingToMangle = false;
	for (const exportInfo of exportsInfo.exports) {
		if (exportInfo.canMangle === true) {
			hasSomethingToMangle = true;
		}
	}
	return hasSomethingToMangle;
};

// Sort by name
const comparator = compareSelect(e => e.name, compareStringsNumeric);
/**
 * @param {boolean} deterministic use deterministic names
 * @param {ExportsInfo} exportsInfo exports info
 * @param {boolean | undefined} isNamespace is namespace object
 * @returns {void}
 */
const mangleExportsInfo = (deterministic, exportsInfo, isNamespace) => {
	if (!canMangle(exportsInfo)) return;
	const usedNames = new Set();
	/** @type {ExportInfo[]} */
	const mangleableExports = [];

	// Avoid to renamed exports that are not provided when
	// 1. it's not a namespace export: non-provided exports can be found in prototype chain
	// 2. there are other provided exports and deterministic mode is chosen:
	//    non-provided exports would break the determinism
	let avoidMangleNonProvided = !isNamespace;
	if (!avoidMangleNonProvided && deterministic) {
		for (const exportInfo of exportsInfo.ownedExports) {
			if (exportInfo.provided !== false) {
				avoidMangleNonProvided = true;
				break;
			}
		}
	}
	for (const exportInfo of exportsInfo.ownedExports) {
		const name = exportInfo.name;
		if (!exportInfo.hasUsedName()) {
			if (
				// Can the export be mangled?
				exportInfo.canMangle !== true ||
				// Never rename 1 char exports
				(name.length === 1 && /^[a-zA-Z0-9_$]/.test(name)) ||
				// Don't rename 2 char exports in deterministic mode
				(deterministic &&
					name.length === 2 &&
					/^[a-zA-Z_$][a-zA-Z0-9_$]|^[1-9][0-9]/.test(name)) ||
				// Don't rename exports that are not provided
				(avoidMangleNonProvided && exportInfo.provided !== true)
			) {
				exportInfo.setUsedName(name);
				usedNames.add(name);
			} else {
				mangleableExports.push(exportInfo);
			}
		}
		if (exportInfo.exportsInfoOwned) {
			const used = exportInfo.getUsed(undefined);
			if (
				used === UsageState.OnlyPropertiesUsed ||
				used === UsageState.Unused
			) {
				mangleExportsInfo(
					deterministic,
					/** @type {ExportsInfo} */ (exportInfo.exportsInfo),
					false
				);
			}
		}
	}
	if (deterministic) {
		assignDeterministicIds(
			mangleableExports,
			e => e.name,
			comparator,
			(e, id) => {
				const name = numberToIdentifier(id);
				const size = usedNames.size;
				usedNames.add(name);
				if (size === usedNames.size) return false;
				e.setUsedName(name);
				return true;
			},
			[
				NUMBER_OF_IDENTIFIER_START_CHARS,
				NUMBER_OF_IDENTIFIER_START_CHARS *
					NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS
			],
			NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS,
			usedNames.size
		);
	} else {
		const usedExports = [];
		const unusedExports = [];
		for (const exportInfo of mangleableExports) {
			if (exportInfo.getUsed(undefined) === UsageState.Unused) {
				unusedExports.push(exportInfo);
			} else {
				usedExports.push(exportInfo);
			}
		}
		usedExports.sort(comparator);
		unusedExports.sort(comparator);
		let i = 0;
		for (const list of [usedExports, unusedExports]) {
			for (const exportInfo of list) {
				let name;
				do {
					name = numberToIdentifier(i++);
				} while (usedNames.has(name));
				exportInfo.setUsedName(name);
			}
		}
	}
};

class MangleExportsPlugin {
	/**
	 * @param {boolean} deterministic use deterministic names
	 */
	constructor(deterministic) {
		this._deterministic = deterministic;
	}
	/**
	 * Apply the plugin
	 * @param {Compiler} compiler the compiler instance
	 * @returns {void}
	 */
	apply(compiler) {
		const { _deterministic: deterministic } = this;
		compiler.hooks.compilation.tap("MangleExportsPlugin", compilation => {
			const moduleGraph = compilation.moduleGraph;
			compilation.hooks.optimizeCodeGeneration.tap(
				"MangleExportsPlugin",
				modules => {
					if (compilation.moduleMemCaches) {
						throw new Error(
							"optimization.mangleExports can't be used with cacheUnaffected as export mangling is a global effect"
						);
					}
					for (const module of modules) {
						const isNamespace =
							module.buildMeta && module.buildMeta.exportsType === "namespace";
						const exportsInfo = moduleGraph.getExportsInfo(module);
						mangleExportsInfo(deterministic, exportsInfo, isNamespace);
					}
				}
			);
		});
	}
}

module.exports = MangleExportsPlugin;


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

"use strict";

const { STAGE_BASIC } = require("../OptimizationStages");
const { runtimeEqual } = require("../util/runtime");

/** @typedef {import("../Compiler")} Compiler */

class MergeDuplicateChunksPlugin {
	/**
	 * @param {Compiler} compiler the compiler
	 * @returns {void}
	 */
	apply(compiler) {
		compiler.hooks.compilation.tap(
			"MergeDuplicateChunksPlugin",
			compilation => {
				compilation.hooks.optimizeChunks.tap(
					{
						name: "MergeDuplicateChunksPlugin",
						stage: STAGE_BASIC
					},
					chunks => {
						const { chunkGraph, moduleGraph } = compilation;

						// remember already tested chunks for performance
						const notDuplicates = new Set();

						// for each chunk
						for (const chunk of chunks) {
							// track a Set of all chunk that could be duplicates
							let possibleDuplicates;
							for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
								if (possibleDuplicates === undefined) {
									// when possibleDuplicates is not yet set,
									// create a new Set from chunks of the current module
									// including only chunks with the same number of modules
									for (const dup of chunkGraph.getModuleChunksIterable(
										module
									)) {
										if (
											dup !== chunk &&
											chunkGraph.getNumberOfChunkModules(chunk) ===
												chunkGraph.getNumberOfChunkModules(dup) &&
											!notDuplicates.has(dup)
										) {
											// delay allocating the new Set until here, reduce memory pressure
											if (possibleDuplicates === undefined) {
												possibleDuplicates = new Set();
											}
											possibleDuplicates.add(dup);
										}
									}
									// when no chunk is possible we can break here
									if (possibleDuplicates === undefined) break;
								} else {
									// validate existing possible duplicates
									for (const dup of possibleDuplicates) {
										// remove possible duplicate when module is not contained
										if (!chunkGraph.isModuleInChunk(module, dup)) {
											possibleDuplicates.delete(dup);
										}
									}
									// when all chunks has been removed we can break here
									if (possibleDuplicates.size === 0) break;
								}
							}

							// when we found duplicates
							if (
								possibleDuplicates !== undefined &&
								possibleDuplicates.size > 0
							) {
								outer: for (const otherChunk of possibleDuplicates) {
									if (otherChunk.hasRuntime() !== chunk.hasRuntime()) continue;
									if (chunkGraph.getNumberOfEntryModules(chunk) > 0) continue;
									if (chunkGraph.getNumberOfEntryModules(otherChunk) > 0)
										continue;
									if (!runtimeEqual(chunk.runtime, otherChunk.runtime)) {
										for (const module of chunkGraph.getChunkModulesIterable(
											chunk
										)) {
											const exportsInfo = moduleGraph.getExportsInfo(module);
											if (
												!exportsInfo.isEquallyUsed(
													chunk.runtime,
													otherChunk.runtime
												)
											) {
												continue outer;
											}
										}
									}
									// merge them
									if (chunkGraph.canChunksBeIntegrated(chunk, otherChunk)) {
										chunkGraph.integrateChunks(chunk, otherChunk);
										compilation.chunks.delete(otherChunk);
									}
								}
							}

							// don't check already processed chunks twice
							notDuplicates.add(chunk);
						}
					}
				);
			}
		);
	}
}
module.exports = MergeDuplicateChunksPlugin;

license: mit


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

"use strict";

const { STAGE_ADVANCED } = require("../OptimizationStages");
const createSchemaValidation = require("../util/create-schema-validation");

/** @typedef {import("../../declarations/plugins/optimize/MinChunkSizePlugin").MinChunkSizePluginOptions} MinChunkSizePluginOptions */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Compiler")} Compiler */

const validate = createSchemaValidation(
	require("../../schemas/plugins/optimize/MinChunkSizePlugin.check.js"),
	() => require("../../schemas/plugins/optimize/MinChunkSizePlugin.json"),
	{
		name: "Min Chunk Size Plugin",
		baseDataPath: "options"
	}
);

class MinChunkSizePlugin {
	/**
	 * @param {MinChunkSizePluginOptions} options options object
	 */
	constructor(options) {
		validate(options);
		this.options = options;
	}

	/**
	 * Apply the plugin
	 * @param {Compiler} compiler the compiler instance
	 * @returns {void}
	 */
	apply(compiler) {
		const options = this.options;
		const minChunkSize = options.minChunkSize;
		compiler.hooks.compilation.tap("MinChunkSizePlugin", compilation => {
			compilation.hooks.optimizeChunks.tap(
				{
					name: "MinChunkSizePlugin",
					stage: STAGE_ADVANCED
				},
				chunks => {
					const chunkGraph = compilation.chunkGraph;
					const equalOptions = {
						chunkOverhead: 1,
						entryChunkMultiplicator: 1
					};

					const chunkSizesMap = new Map();
					/** @type {[Chunk, Chunk][]} */
					const combinations = [];
					/** @type {Chunk[]} */
					const smallChunks = [];
					const visitedChunks = [];
					for (const a of chunks) {
						// check if one of the chunks sizes is smaller than the minChunkSize
						// and filter pairs that can NOT be integrated!
						if (chunkGraph.getChunkSize(a, equalOptions) < minChunkSize) {
							smallChunks.push(a);
							for (const b of visitedChunks) {
								if (chunkGraph.canChunksBeIntegrated(b, a))
									combinations.push([b, a]);
							}
						} else {
							for (const b of smallChunks) {
								if (chunkGraph.canChunksBeIntegrated(b, a))
									combinations.push([b, a]);
							}
						}
						chunkSizesMap.set(a, chunkGraph.getChunkSize(a, options));
						visitedChunks.push(a);
					}

					const sortedSizeFilteredExtendedPairCombinations = combinations
						.map(pair => {
							// extend combination pairs with size and integrated size
							const a = chunkSizesMap.get(pair[0]);
							const b = chunkSizesMap.get(pair[1]);
							const ab = chunkGraph.getIntegratedChunksSize(
								pair[0],
								pair[1],
								options
							);
							/** @type {[number, number, Chunk, Chunk]} */
							const extendedPair = [a + b - ab, ab, pair[0], pair[1]];
							return extendedPair;
						})
						.sort((a, b) => {
							// sadly javascript does an in place sort here
							// sort by size
							const diff = b[0] - a[0];
							if (diff !== 0) return diff;
							return a[1] - b[1];
						});

					if (sortedSizeFilteredExtendedPairCombinations.length === 0) return;

					const pair = sortedSizeFilteredExtendedPairCombinations[0];

					chunkGraph.integrateChunks(pair[2], pair[3]);
					compilation.chunks.delete(pair[3]);
					return true;
				}
			);
		});
	}
}
module.exports = MinChunkSizePlugin;


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

"use strict";

const SizeFormatHelpers = require("../SizeFormatHelpers");
const WebpackError = require("../WebpackError");

class MinMaxSizeWarning extends WebpackError {
	/**
	 * @param {string[] | undefined} keys keys
	 * @param {number} minSize minimum size
	 * @param {number} maxSize maximum size
	 */
	constructor(keys, minSize, maxSize) {
		let keysMessage = "Fallback cache group";
		if (keys) {
			keysMessage =
				keys.length > 1
					? `Cache groups ${keys.sort().join(", ")}`
					: `Cache group ${keys[0]}`;
		}
		super(
			`SplitChunksPlugin\n` +
				`${keysMessage}\n` +
				`Configured minSize (${SizeFormatHelpers.formatSize(minSize)}) is ` +
				`bigger than maxSize (${SizeFormatHelpers.formatSize(maxSize)}).\n` +
				"This seem to be a invalid optimization.splitChunks configuration."
		);
	}
}

module.exports = MinMaxSizeWarning;


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

"use strict";

const asyncLib = require("neo-async");
const ChunkGraph = require("../ChunkGraph");
const ModuleGraph = require("../ModuleGraph");
const { STAGE_DEFAULT } = require("../OptimizationStages");
const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency");
const { compareModulesByIdentifier } = require("../util/comparators");
const {
	intersectRuntime,
	mergeRuntimeOwned,
	filterRuntime,
	runtimeToString,
	mergeRuntime
} = require("../util/runtime");
const ConcatenatedModule = require("./ConcatenatedModule");

/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../Module").BuildInfo} BuildInfo */
/** @typedef {import("../RequestShortener")} RequestShortener */
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */

/**
 * @typedef {object} Statistics
 * @property {number} cached
 * @property {number} alreadyInConfig
 * @property {number} invalidModule
 * @property {number} incorrectChunks
 * @property {number} incorrectDependency
 * @property {number} incorrectModuleDependency
 * @property {number} incorrectChunksOfImporter
 * @property {number} incorrectRuntimeCondition
 * @property {number} importerFailed
 * @property {number} added
 */

/**
 * @param {string} msg message
 * @returns {string} formatted message
 */
const formatBailoutReason = msg => {
	return "ModuleConcatenation bailout: " + msg;
};

class ModuleConcatenationPlugin {
	/**
	 * @param {TODO} options options
	 */
	constructor(options) {
		if (typeof options !== "object") options = {};
		this.options = options;
	}

	/**
	 * Apply the plugin
	 * @param {Compiler} compiler the compiler instance
	 * @returns {void}
	 */
	apply(compiler) {
		const { _backCompat: backCompat } = compiler;
		compiler.hooks.compilation.tap("ModuleConcatenationPlugin", compilation => {
			if (compilation.moduleMemCaches) {
				throw new Error(
					"optimization.concatenateModules can't be used with cacheUnaffected as module concatenation is a global effect"
				);
			}
			const moduleGraph = compilation.moduleGraph;
			/** @type {Map<Module, string | ((requestShortener: RequestShortener) => string)>} */
			const bailoutReasonMap = new Map();

			/**
			 * @param {Module} module the module
			 * @param {string | ((requestShortener: RequestShortener) => string)} reason the reason
			 */
			const setBailoutReason = (module, reason) => {
				setInnerBailoutReason(module, reason);
				moduleGraph
					.getOptimizationBailout(module)
					.push(
						typeof reason === "function"
							? rs => formatBailoutReason(reason(rs))
							: formatBailoutReason(reason)
					);
			};

			/**
			 * @param {Module} module the module
			 * @param {string | ((requestShortener: RequestShortener) => string)} reason the reason
			 */
			const setInnerBailoutReason = (module, reason) => {
				bailoutReasonMap.set(module, reason);
			};

			/**
			 * @param {Module} module the module
			 * @param {RequestShortener} requestShortener the request shortener
			 * @returns {string | ((requestShortener: RequestShortener) => string) | undefined} the reason
			 */
			const getInnerBailoutReason = (module, requestShortener) => {
				const reason = bailoutReasonMap.get(module);
				if (typeof reason === "function") return reason(requestShortener);
				return reason;
			};

			/**
			 * @param {Module} module the module
			 * @param {Module | function(RequestShortener): string} problem the problem
			 * @returns {(requestShortener: RequestShortener) => string} the reason
			 */
			const formatBailoutWarning = (module, problem) => requestShortener => {
				if (typeof problem === "function") {
					return formatBailoutReason(
						`Cannot concat with ${module.readableIdentifier(
							requestShortener
						)}: ${problem(requestShortener)}`
					);
				}
				const reason = getInnerBailoutReason(module, requestShortener);
				const reasonWithPrefix = reason ? `: ${reason}` : "";
				if (module === problem) {
					return formatBailoutReason(
						`Cannot concat with ${module.readableIdentifier(
							requestShortener
						)}${reasonWithPrefix}`
					);
				} else {
					return formatBailoutReason(
						`Cannot concat with ${module.readableIdentifier(
							requestShortener
						)} because of ${problem.readableIdentifier(
							requestShortener
						)}${reasonWithPrefix}`
					);
				}
			};

			compilation.hooks.optimizeChunkModules.tapAsync(
				{
					name: "ModuleConcatenationPlugin",
					stage: STAGE_DEFAULT
				},
				(allChunks, modules, callback) => {
					const logger = compilation.getLogger(
						"webpack.ModuleConcatenationPlugin"
					);
					const { chunkGraph, moduleGraph } = compilation;
					const relevantModules = [];
					const possibleInners = new Set();
					const context = {
						chunkGraph,
						moduleGraph
					};
					logger.time("select relevant modules");
					for (const module of modules) {
						let canBeRoot = true;
						let canBeInner = true;

						const bailoutReason = module.getConcatenationBailoutReason(context);
						if (bailoutReason) {
							setBailoutReason(module, bailoutReason);
							continue;
						}

						// Must not be an async module
						if (moduleGraph.isAsync(module)) {
							setBailoutReason(module, `Module is async`);
							continue;
						}

						// Must be in strict mode
						if (!(/** @type {BuildInfo} */ (module.buildInfo).strict)) {
							setBailoutReason(module, `Module is not in strict mode`);
							continue;
						}

						// Module must be in any chunk (we don't want to do useless work)
						if (chunkGraph.getNumberOfModuleChunks(module) === 0) {
							setBailoutReason(module, "Module is not in any chunk");
							continue;
						}

						// Exports must be known (and not dynamic)
						const exportsInfo = moduleGraph.getExportsInfo(module);
						const relevantExports = exportsInfo.getRelevantExports(undefined);
						const unknownReexports = relevantExports.filter(exportInfo => {
							return (
								exportInfo.isReexport() && !exportInfo.getTarget(moduleGraph)
							);
						});
						if (unknownReexports.length > 0) {
							setBailoutReason(
								module,
								`Reexports in this module do not have a static target (${Array.from(
									unknownReexports,
									exportInfo =>
										`${
											exportInfo.name || "other exports"
										}: ${exportInfo.getUsedInfo()}`
								).join(", ")})`
							);
							continue;
						}

						// Root modules must have a static list of exports
						const unknownProvidedExports = relevantExports.filter(
							exportInfo => {
								return exportInfo.provided !== true;
							}
						);
						if (unknownProvidedExports.length > 0) {
							setBailoutReason(
								module,
								`List of module exports is dynamic (${Array.from(
									unknownProvidedExports,
									exportInfo =>
										`${
											exportInfo.name || "other exports"
										}: ${exportInfo.getProvidedInfo()} and ${exportInfo.getUsedInfo()}`
								).join(", ")})`
							);
							canBeRoot = false;
						}

						// Module must not be an entry point
						if (chunkGraph.isEntryModule(module)) {
							setInnerBailoutReason(module, "Module is an entry point");
							canBeInner = false;
						}

						if (canBeRoot) relevantModules.push(module);
						if (canBeInner) possibleInners.add(module);
					}
					logger.timeEnd("select relevant modules");
					logger.debug(
						`${relevantModules.length} potential root modules, ${possibleInners.size} potential inner modules`
					);
					// sort by depth
					// modules with lower depth are more likely suited as roots
					// this improves performance, because modules already selected as inner are skipped
					logger.time("sort relevant modules");
					relevantModules.sort((a, b) => {
						return (
							/** @type {number} */ (moduleGraph.getDepth(a)) -
							/** @type {number} */ (moduleGraph.getDepth(b))
						);
					});
					logger.timeEnd("sort relevant modules");

					/** @type {Statistics} */
					const stats = {
						cached: 0,
						alreadyInConfig: 0,
						invalidModule: 0,
						incorrectChunks: 0,
						incorrectDependency: 0,
						incorrectModuleDependency: 0,
						incorrectChunksOfImporter: 0,
						incorrectRuntimeCondition: 0,
						importerFailed: 0,
						added: 0
					};
					let statsCandidates = 0;
					let statsSizeSum = 0;
					let statsEmptyConfigurations = 0;

					logger.time("find modules to concatenate");
					const concatConfigurations = [];
					const usedAsInner = new Set();
					for (const currentRoot of relevantModules) {
						// when used by another configuration as inner:
						// the other configuration is better and we can skip this one
						// TODO reconsider that when it's only used in a different runtime
						if (usedAsInner.has(currentRoot)) continue;

						let chunkRuntime = undefined;
						for (const r of chunkGraph.getModuleRuntimes(currentRoot)) {
							chunkRuntime = mergeRuntimeOwned(chunkRuntime, r);
						}
						const exportsInfo = moduleGraph.getExportsInfo(currentRoot);
						const filteredRuntime = filterRuntime(chunkRuntime, r =>
							exportsInfo.isModuleUsed(r)
						);
						const activeRuntime =
							filteredRuntime === true
								? chunkRuntime
								: filteredRuntime === false
									? undefined
									: filteredRuntime;

						// create a configuration with the root
						const currentConfiguration = new ConcatConfiguration(
							currentRoot,
							activeRuntime
						);

						// cache failures to add modules
						const failureCache = new Map();

						// potential optional import candidates
						/** @type {Set<Module>} */
						const candidates = new Set();

						// try to add all imports
						for (const imp of this._getImports(
							compilation,
							currentRoot,
							activeRuntime
						)) {
							candidates.add(imp);
						}

						for (const imp of candidates) {
							const impCandidates = new Set();
							const problem = this._tryToAdd(
								compilation,
								currentConfiguration,
								imp,
								chunkRuntime,
								activeRuntime,
								possibleInners,
								impCandidates,
								failureCache,
								chunkGraph,
								true,
								stats
							);
							if (problem) {
								failureCache.set(imp, problem);
								currentConfiguration.addWarning(imp, problem);
							} else {
								for (const c of impCandidates) {
									candidates.add(c);
								}
							}
						}
						statsCandidates += candidates.size;
						if (!currentConfiguration.isEmpty()) {
							const modules = currentConfiguration.getModules();
							statsSizeSum += modules.size;
							concatConfigurations.push(currentConfiguration);
							for (const module of modules) {
								if (module !== currentConfiguration.rootModule) {
									usedAsInner.add(module);
								}
							}
						} else {
							statsEmptyConfigurations++;
							const optimizationBailouts =
								moduleGraph.getOptimizationBailout(currentRoot);
							for (const warning of currentConfiguration.getWarningsSorted()) {
								optimizationBailouts.push(
									formatBailoutWarning(warning[0], warning[1])
								);
							}
						}
					}
					logger.timeEnd("find modules to concatenate");
					logger.debug(
						`${
							concatConfigurations.length
						} successful concat configurations (avg size: ${
							statsSizeSum / concatConfigurations.length
						}), ${statsEmptyConfigurations} bailed out completely`
					);
					logger.debug(
						`${statsCandidates} candidates were considered for adding (${stats.cached} cached failure, ${stats.alreadyInConfig} already in config, ${stats.invalidModule} invalid module, ${stats.incorrectChunks} incorrect chunks, ${stats.incorrectDependency} incorrect dependency, ${stats.incorrectChunksOfImporter} incorrect chunks of importer, ${stats.incorrectModuleDependency} incorrect module dependency, ${stats.incorrectRuntimeCondition} incorrect runtime condition, ${stats.importerFailed} importer failed, ${stats.added} added)`
					);
					// HACK: Sort configurations by length and start with the longest one
					// to get the biggest groups possible. Used modules are marked with usedModules
					// TODO: Allow to reuse existing configuration while trying to add dependencies.
					// This would improve performance. O(n^2) -> O(n)
					logger.time(`sort concat configurations`);
					concatConfigurations.sort((a, b) => {
						return b.modules.size - a.modules.size;
					});
					logger.timeEnd(`sort concat configurations`);
					const usedModules = new Set();

					logger.time("create concatenated modules");
					asyncLib.each(
						concatConfigurations,
						(concatConfiguration, callback) => {
							const rootModule = concatConfiguration.rootModule;

							// Avoid overlapping configurations
							// TODO: remove this when todo above is fixed
							if (usedModules.has(rootModule)) return callback();
							const modules = concatConfiguration.getModules();
							for (const m of modules) {
								usedModules.add(m);
							}

							// Create a new ConcatenatedModule
							let newModule = ConcatenatedModule.create(
								rootModule,
								modules,
								concatConfiguration.runtime,
								compiler.root,
								compilation.outputOptions.hashFunction
							);

							const build = () => {
								newModule.build(
									compiler.options,
									compilation,
									null,
									null,
									err => {
										if (err) {
											if (!err.module) {
												err.module = newModule;
											}
											return callback(err);
										}
										integrate();
									}
								);
							};

							const integrate = () => {
								if (backCompat) {
									ChunkGraph.setChunkGraphForModule(newModule, chunkGraph);
									ModuleGraph.setModuleGraphForModule(newModule, moduleGraph);
								}

								for (const warning of concatConfiguration.getWarningsSorted()) {
									moduleGraph
										.getOptimizationBailout(newModule)
										.push(formatBailoutWarning(warning[0], warning[1]));
								}
								moduleGraph.cloneModuleAttributes(rootModule, newModule);
								for (const m of modules) {
									// add to builtModules when one of the included modules was built
									if (compilation.builtModules.has(m)) {
										compilation.builtModules.add(newModule);
									}
									if (m !== rootModule) {
										// attach external references to the concatenated module too
										moduleGraph.copyOutgoingModuleConnections(
											m,
											newModule,
											c => {
												return (
													c.originModule === m &&
													!(
														c.dependency instanceof HarmonyImportDependency &&
														modules.has(c.module)
													)
												);
											}
										);
										// remove module from chunk
										for (const chunk of chunkGraph.getModuleChunksIterable(
											rootModule
										)) {
											const sourceTypes = chunkGraph.getChunkModuleSourceTypes(
												chunk,
												m
											);
											if (sourceTypes.size === 1) {
												chunkGraph.disconnectChunkAndModule(chunk, m);
											} else {
												const newSourceTypes = new Set(sourceTypes);
												newSourceTypes.delete("javascript");
												chunkGraph.setChunkModuleSourceTypes(
													chunk,
													m,
													newSourceTypes
												);
											}
										}
									}
								}
								compilation.modules.delete(rootModule);
								ChunkGraph.clearChunkGraphForModule(rootModule);
								ModuleGraph.clearModuleGraphForModule(rootModule);

								// remove module from chunk
								chunkGraph.replaceModule(rootModule, newModule);
								// replace module references with the concatenated module
								moduleGraph.moveModuleConnections(rootModule, newModule, c => {
									const otherModule =
										c.module === rootModule ? c.originModule : c.module;
									const innerConnection =
										c.dependency instanceof HarmonyImportDependency &&
										modules.has(/** @type {Module} */ (otherModule));
									return !innerConnection;
								});
								// add concatenated module to the compilation
								compilation.modules.add(newModule);

								callback();
							};

							build();
						},
						err => {
							logger.timeEnd("create concatenated modules");
							process.nextTick(callback.bind(null, err));
						}
					);
				}
			);
		});
	}

	/**
	 * @param {Compilation} compilation the compilation
	 * @param {Module} module the module to be added
	 * @param {RuntimeSpec} runtime the runtime scope
	 * @returns {Set<Module>} the imported modules
	 */
	_getImports(compilation, module, runtime) {
		const moduleGraph = compilation.moduleGraph;
		const set = new Set();
		for (const dep of module.dependencies) {
			// Get reference info only for harmony Dependencies
			if (!(dep instanceof HarmonyImportDependency)) continue;

			const connection = moduleGraph.getConnection(dep);
			// Reference is valid and has a module
			if (
				!connection ||
				!connection.module ||
				!connection.isTargetActive(runtime)
			) {
				continue;
			}

			const importedNames = compilation.getDependencyReferencedExports(
				dep,
				undefined
			);

			if (
				importedNames.every(i =>
					Array.isArray(i) ? i.length > 0 : i.name.length > 0
				) ||
				Array.isArray(moduleGraph.getProvidedExports(module))
			) {
				set.add(connection.module);
			}
		}
		return set;
	}

	/**
	 * @param {Compilation} compilation webpack compilation
	 * @param {ConcatConfiguration} config concat configuration (will be modified when added)
	 * @param {Module} module the module to be added
	 * @param {RuntimeSpec} runtime the runtime scope of the generated code
	 * @param {RuntimeSpec} activeRuntime the runtime scope of the root module
	 * @param {Set<Module>} possibleModules modules that are candidates
	 * @param {Set<Module>} candidates list of potential candidates (will be added to)
	 * @param {Map<Module, Module | function(RequestShortener): string>} failureCache cache for problematic modules to be more performant
	 * @param {ChunkGraph} chunkGraph the chunk graph
	 * @param {boolean} avoidMutateOnFailure avoid mutating the config when adding fails
	 * @param {Statistics} statistics gathering metrics
	 * @returns {null | Module | function(RequestShortener): string} the problematic module
	 */
	_tryToAdd(
		compilation,
		config,
		module,
		runtime,
		activeRuntime,
		possibleModules,
		candidates,
		failureCache,
		chunkGraph,
		avoidMutateOnFailure,
		statistics
	) {
		const cacheEntry = failureCache.get(module);
		if (cacheEntry) {
			statistics.cached++;
			return cacheEntry;
		}

		// Already added?
		if (config.has(module)) {
			statistics.alreadyInConfig++;
			return null;
		}

		// Not possible to add?
		if (!possibleModules.has(module)) {
			statistics.invalidModule++;
			failureCache.set(module, module); // cache failures for performance
			return module;
		}

		// Module must be in the correct chunks
		const missingChunks = Array.from(
			chunkGraph.getModuleChunksIterable(config.rootModule)
		).filter(chunk => !chunkGraph.isModuleInChunk(module, chunk));
		if (missingChunks.length > 0) {
			/**
			 * @param {RequestShortener} requestShortener request shortener
			 * @returns {string} problem description
			 */
			const problem = requestShortener => {
				const missingChunksList = Array.from(
					new Set(missingChunks.map(chunk => chunk.name || "unnamed chunk(s)"))
				).sort();
				const chunks = Array.from(
					new Set(
						Array.from(chunkGraph.getModuleChunksIterable(module)).map(
							chunk => chunk.name || "unnamed chunk(s)"
						)
					)
				).sort();
				return `Module ${module.readableIdentifier(
					requestShortener
				)} is not in the same chunk(s) (expected in chunk(s) ${missingChunksList.join(
					", "
				)}, module is in chunk(s) ${chunks.join(", ")})`;
			};
			statistics.incorrectChunks++;
			failureCache.set(module, problem); // cache failures for performance
			return problem;
		}

		const moduleGraph = compilation.moduleGraph;

		const incomingConnections =
			moduleGraph.getIncomingConnectionsByOriginModule(module);

		const incomingConnectionsFromNonModules =
			incomingConnections.get(null) || incomingConnections.get(undefined);
		if (incomingConnectionsFromNonModules) {
			const activeNonModulesConnections =
				incomingConnectionsFromNonModules.filter(connection => {
					// We are not interested in inactive connections
					// or connections without dependency
					return connection.isActive(runtime);
				});
			if (activeNonModulesConnections.length > 0) {
				/**
				 * @param {RequestShortener} requestShortener request shortener
				 * @returns {string} problem description
				 */
				const problem = requestShortener => {
					const importingExplanations = new Set(
						activeNonModulesConnections.map(c => c.explanation).filter(Boolean)
					);
					const explanations = Array.from(importingExplanations).sort();
					return `Module ${module.readableIdentifier(
						requestShortener
					)} is referenced ${
						explanations.length > 0
							? `by: ${explanations.join(", ")}`
							: "in an unsupported way"
					}`;
				};
				statistics.incorrectDependency++;
				failureCache.set(module, problem); // cache failures for performance
				return problem;
			}
		}

		/** @type {Map<Module, readonly ModuleGraph.ModuleGraphConnection[]>} */
		const incomingConnectionsFromModules = new Map();
		for (const [originModule, connections] of incomingConnections) {
			if (originModule) {
				// Ignore connection from orphan modules
				if (chunkGraph.getNumberOfModuleChunks(originModule) === 0) continue;

				// We don't care for connections from other runtimes
				let originRuntime = undefined;
				for (const r of chunkGraph.getModuleRuntimes(originModule)) {
					originRuntime = mergeRuntimeOwned(originRuntime, r);
				}

				if (!intersectRuntime(runtime, originRuntime)) continue;

				// We are not interested in inactive connections
				const activeConnections = connections.filter(connection =>
					connection.isActive(runtime)
				);
				if (activeConnections.length > 0)
					incomingConnectionsFromModules.set(originModule, activeConnections);
			}
		}

		const incomingModules = Array.from(incomingConnectionsFromModules.keys());

		// Module must be in the same chunks like the referencing module
		const otherChunkModules = incomingModules.filter(originModule => {
			for (const chunk of chunkGraph.getModuleChunksIterable(
				config.rootModule
			)) {
				if (!chunkGraph.isModuleInChunk(originModule, chunk)) {
					return true;
				}
			}
			return false;
		});
		if (otherChunkModules.length > 0) {
			/**
			 * @param {RequestShortener} requestShortener request shortener
			 * @returns {string} problem description
			 */
			const problem = requestShortener => {
				const names = otherChunkModules
					.map(m => m.readableIdentifier(requestShortener))
					.sort();
				return `Module ${module.readableIdentifier(
					requestShortener
				)} is referenced from different chunks by these modules: ${names.join(
					", "
				)}`;
			};
			statistics.incorrectChunksOfImporter++;
			failureCache.set(module, problem); // cache failures for performance
			return problem;
		}

		/** @type {Map<Module, readonly ModuleGraph.ModuleGraphConnection[]>} */
		const nonHarmonyConnections = new Map();
		for (const [originModule, connections] of incomingConnectionsFromModules) {
			const selected = connections.filter(
				connection =>
					!connection.dependency ||
					!(connection.dependency instanceof HarmonyImportDependency)
			);
			if (selected.length > 0)
				nonHarmonyConnections.set(originModule, connections);
		}
		if (nonHarmonyConnections.size > 0) {
			/**
			 * @param {RequestShortener} requestShortener request shortener
			 * @returns {string} problem description
			 */
			const problem = requestShortener => {
				const names = Array.from(nonHarmonyConnections)
					.map(([originModule, connections]) => {
						return `${originModule.readableIdentifier(
							requestShortener
						)} (referenced with ${Array.from(
							new Set(
								connections
									.map(c => c.dependency && c.dependency.type)
									.filter(Boolean)
							)
						)
							.sort()
							.join(", ")})`;
					})
					.sort();
				return `Module ${module.readableIdentifier(
					requestShortener
				)} is referenced from these modules with unsupported syntax: ${names.join(
					", "
				)}`;
			};
			statistics.incorrectModuleDependency++;
			failureCache.set(module, problem); // cache failures for performance
			return problem;
		}

		if (runtime !== undefined && typeof runtime !== "string") {
			// Module must be consistently referenced in the same runtimes
			/** @type {{ originModule: Module, runtimeCondition: RuntimeSpec }[]} */
			const otherRuntimeConnections = [];
			outer: for (const [
				originModule,
				connections
			] of incomingConnectionsFromModules) {
				/** @type {false | RuntimeSpec} */
				let currentRuntimeCondition = false;
				for (const connection of connections) {
					const runtimeCondition = filterRuntime(runtime, runtime => {
						return connection.isTargetActive(runtime);
					});
					if (runtimeCondition === false) continue;
					if (runtimeCondition === true) continue outer;
					if (currentRuntimeCondition !== false) {
						currentRuntimeCondition = mergeRuntime(
							currentRuntimeCondition,
							runtimeCondition
						);
					} else {
						currentRuntimeCondition = runtimeCondition;
					}
				}
				if (currentRuntimeCondition !== false) {
					otherRuntimeConnections.push({
						originModule,
						runtimeCondition: currentRuntimeCondition
					});
				}
			}
			if (otherRuntimeConnections.length > 0) {
				/**
				 * @param {RequestShortener} requestShortener request shortener
				 * @returns {string} problem description
				 */
				const problem = requestShortener => {
					return `Module ${module.readableIdentifier(
						requestShortener
					)} is runtime-dependent referenced by these modules: ${Array.from(
						otherRuntimeConnections,
						({ originModule, runtimeCondition }) =>
							`${originModule.readableIdentifier(
								requestShortener
							)} (expected runtime ${runtimeToString(
								runtime
							)}, module is only referenced in ${runtimeToString(
								/** @type {RuntimeSpec} */ (runtimeCondition)
							)})`
					).join(", ")}`;
				};
				statistics.incorrectRuntimeCondition++;
				failureCache.set(module, problem); // cache failures for performance
				return problem;
			}
		}

		let backup;
		if (avoidMutateOnFailure) {
			backup = config.snapshot();
		}

		// Add the module
		config.add(module);

		incomingModules.sort(compareModulesByIdentifier);

		// Every module which depends on the added module must be in the configuration too.
		for (const originModule of incomingModules) {
			const problem = this._tryToAdd(
				compilation,
				config,
				originModule,
				runtime,
				activeRuntime,
				possibleModules,
				candidates,
				failureCache,
				chunkGraph,
				false,
				statistics
			);
			if (problem) {
				if (backup !== undefined) config.rollback(backup);
				statistics.importerFailed++;
				failureCache.set(module, problem); // cache failures for performance
				return problem;
			}
		}

		// Add imports to possible candidates list
		for (const imp of this._getImports(compilation, module, runtime)) {
			candidates.add(imp);
		}
		statistics.added++;
		return null;
	}
}

class ConcatConfiguration {
	/**
	 * @param {Module} rootModule the root module
	 * @param {RuntimeSpec} runtime the runtime
	 */
	constructor(rootModule, runtime) {
		this.rootModule = rootModule;
		this.runtime = runtime;
		/** @type {Set<Module>} */
		this.modules = new Set();
		this.modules.add(rootModule);
		/** @type {Map<Module, Module | function(RequestShortener): string>} */
		this.warnings = new Map();
	}

	/**
	 * @param {Module} module the module
	 */
	add(module) {
		this.modules.add(module);
	}

	/**
	 * @param {Module} module the module
	 * @returns {boolean} true, when the module is in the module set
	 */
	has(module) {
		return this.modules.has(module);
	}

	isEmpty() {
		return this.modules.size === 1;
	}

	/**
	 * @param {Module} module the module
	 * @param {Module | function(RequestShortener): string} problem the problem
	 */
	addWarning(module, problem) {
		this.warnings.set(module, problem);
	}

	/**
	 * @returns {Map<Module, Module | function(RequestShortener): string>} warnings
	 */
	getWarningsSorted() {
		return new Map(
			Array.from(this.warnings).sort((a, b) => {
				const ai = a[0].identifier();
				const bi = b[0].identifier();
				if (ai < bi) return -1;
				if (ai > bi) return 1;
				return 0;
			})
		);
	}

	/**
	 * @returns {Set<Module>} modules as set
	 */
	getModules() {
		return this.modules;
	}

	snapshot() {
		return this.modules.size;
	}

	/**
	 * @param {number} snapshot snapshot
	 */
	rollback(snapshot) {
		const modules = this.modules;
		for (const m of modules) {
			if (snapshot === 0) {
				modules.delete(m);
			} else {
				snapshot--;
			}
		}
	}
}

module.exports = ModuleConcatenationPlugin;


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

"use strict";

const { SyncBailHook } = require("tapable");
const { RawSource, CachedSource, CompatSource } = require("webpack-sources");
const Compilation = require("../Compilation");
const WebpackError = require("../WebpackError");
const { compareSelect, compareStrings } = require("../util/comparators");
const createHash = require("../util/createHash");

/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../Cache").Etag} Etag */
/** @typedef {import("../Compilation").AssetInfo} AssetInfo */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {typeof import("../util/Hash")} Hash */

const EMPTY_SET = new Set();

/**
 * @template T
 * @param {T | T[]} itemOrItems item or items
 * @param {Set<T>} list list
 */
const addToList = (itemOrItems, list) => {
	if (Array.isArray(itemOrItems)) {
		for (const item of itemOrItems) {
			list.add(item);
		}
	} else if (itemOrItems) {
		list.add(itemOrItems);
	}
};

/**
 * @template T
 * @param {T[]} input list
 * @param {function(T): Buffer} fn map function
 * @returns {Buffer[]} buffers without duplicates
 */
const mapAndDeduplicateBuffers = (input, fn) => {
	// Buffer.equals compares size first so this should be efficient enough
	// If it becomes a performance problem we can use a map and group by size
	// instead of looping over all assets.
	const result = [];
	outer: for (const value of input) {
		const buf = fn(value);
		for (const other of result) {
			if (buf.equals(other)) continue outer;
		}
		result.push(buf);
	}
	return result;
};

/**
 * Escapes regular expression metacharacters
 * @param {string} str String to quote
 * @returns {string} Escaped string
 */
const quoteMeta = str => {
	return str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&");
};

const cachedSourceMap = new WeakMap();

/**
 * @param {Source} source source
 * @returns {CachedSource} cached source
 */
const toCachedSource = source => {
	if (source instanceof CachedSource) {
		return source;
	}
	const entry = cachedSourceMap.get(source);
	if (entry !== undefined) return entry;
	const newSource = new CachedSource(CompatSource.from(source));
	cachedSourceMap.set(source, newSource);
	return newSource;
};

/** @typedef {Set<string>} OwnHashes */
/** @typedef {Set<string>} ReferencedHashes */
/** @typedef {Set<string>} Hashes */

/**
 * @typedef {object} AssetInfoForRealContentHash
 * @property {string} name
 * @property {AssetInfo} info
 * @property {Source} source
 * @property {RawSource | undefined} newSource
 * @property {RawSource | undefined} newSourceWithoutOwn
 * @property {string} content
 * @property {OwnHashes | undefined} ownHashes
 * @property {Promise<void> | undefined} contentComputePromise
 * @property {Promise<void> | undefined} contentComputeWithoutOwnPromise
 * @property {ReferencedHashes | undefined} referencedHashes
 * @property {Hashes} hashes
 */

/**
 * @typedef {object} CompilationHooks
 * @property {SyncBailHook<[Buffer[], string], string>} updateHash
 */

/** @type {WeakMap<Compilation, CompilationHooks>} */
const compilationHooksMap = new WeakMap();

class RealContentHashPlugin {
	/**
	 * @param {Compilation} compilation the compilation
	 * @returns {CompilationHooks} the attached hooks
	 */
	static getCompilationHooks(compilation) {
		if (!(compilation instanceof Compilation)) {
			throw new TypeError(
				"The 'compilation' argument must be an instance of Compilation"
			);
		}
		let hooks = compilationHooksMap.get(compilation);
		if (hooks === undefined) {
			hooks = {
				updateHash: new SyncBailHook(["content", "oldHash"])
			};
			compilationHooksMap.set(compilation, hooks);
		}
		return hooks;
	}

	/**
	 * @param {object} options options object
	 * @param {string | Hash} options.hashFunction the hash function to use
	 * @param {string} options.hashDigest the hash digest to use
	 */
	constructor({ hashFunction, hashDigest }) {
		this._hashFunction = hashFunction;
		this._hashDigest = hashDigest;
	}

	/**
	 * Apply the plugin
	 * @param {Compiler} compiler the compiler instance
	 * @returns {void}
	 */
	apply(compiler) {
		compiler.hooks.compilation.tap("RealContentHashPlugin", compilation => {
			const cacheAnalyse = compilation.getCache(
				"RealContentHashPlugin|analyse"
			);
			const cacheGenerate = compilation.getCache(
				"RealContentHashPlugin|generate"
			);
			const hooks = RealContentHashPlugin.getCompilationHooks(compilation);
			compilation.hooks.processAssets.tapPromise(
				{
					name: "RealContentHashPlugin",
					stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH
				},
				async () => {
					const assets = compilation.getAssets();
					/** @type {AssetInfoForRealContentHash[]} */
					const assetsWithInfo = [];
					/** @type {Map<string, [AssetInfoForRealContentHash]>} */
					const hashToAssets = new Map();
					for (const { source, info, name } of assets) {
						const cachedSource = toCachedSource(source);
						const content = /** @type {string} */ (cachedSource.source());
						/** @type {Hashes} */
						const hashes = new Set();
						addToList(info.contenthash, hashes);
						/** @type {AssetInfoForRealContentHash} */
						const data = {
							name,
							info,
							source: cachedSource,
							newSource: undefined,
							newSourceWithoutOwn: undefined,
							content,
							ownHashes: undefined,
							contentComputePromise: undefined,
							contentComputeWithoutOwnPromise: undefined,
							referencedHashes: undefined,
							hashes
						};
						assetsWithInfo.push(data);
						for (const hash of hashes) {
							const list = hashToAssets.get(hash);
							if (list === undefined) {
								hashToAssets.set(hash, [data]);
							} else {
								list.push(data);
							}
						}
					}
					if (hashToAssets.size === 0) return;
					const hashRegExp = new RegExp(
						Array.from(hashToAssets.keys(), quoteMeta).join("|"),
						"g"
					);
					await Promise.all(
						assetsWithInfo.map(async asset => {
							const { name, source, content, hashes } = asset;
							if (Buffer.isBuffer(content)) {
								asset.referencedHashes = EMPTY_SET;
								asset.ownHashes = EMPTY_SET;
								return;
							}
							const etag = cacheAnalyse.mergeEtags(
								cacheAnalyse.getLazyHashedEtag(source),
								Array.from(hashes).join("|")
							);
							[asset.referencedHashes, asset.ownHashes] =
								await cacheAnalyse.providePromise(name, etag, () => {
									const referencedHashes = new Set();
									let ownHashes = new Set();
									const inContent = content.match(hashRegExp);
									if (inContent) {
										for (const hash of inContent) {
											if (hashes.has(hash)) {
												ownHashes.add(hash);
												continue;
											}
											referencedHashes.add(hash);
										}
									}
									return [referencedHashes, ownHashes];
								});
						})
					);
					/**
					 * @param {string} hash the hash
					 * @returns {undefined | ReferencedHashes} the referenced hashes
					 */
					const getDependencies = hash => {
						const assets = hashToAssets.get(hash);
						if (!assets) {
							const referencingAssets = assetsWithInfo.filter(asset =>
								/** @type {ReferencedHashes} */ (asset.referencedHashes).has(
									hash
								)
							);
							const err = new WebpackError(`RealContentHashPlugin
Some kind of unexpected caching problem occurred.
An asset was cached with a reference to another asset (${hash}) that's not in the compilation anymore.
Either the asset was incorrectly cached, or the referenced asset should also be restored from cache.
Referenced by:
${referencingAssets
	.map(a => {
		const match = new RegExp(`.{0,20}${quoteMeta(hash)}.{0,20}`).exec(
			a.content
		);
		return ` - ${a.name}: ...${match ? match[0] : "???"}...`;
	})
	.join("\n")}`);
							compilation.errors.push(err);
							return undefined;
						}
						const hashes = new Set();
						for (const { referencedHashes, ownHashes } of assets) {
							if (!(/** @type {OwnHashes} */ (ownHashes).has(hash))) {
								for (const hash of /** @type {OwnHashes} */ (ownHashes)) {
									hashes.add(hash);
								}
							}
							for (const hash of /** @type {ReferencedHashes} */ (
								referencedHashes
							)) {
								hashes.add(hash);
							}
						}
						return hashes;
					};
					/**
					 * @param {string} hash the hash
					 * @returns {string} the hash info
					 */
					const hashInfo = hash => {
						const assets = hashToAssets.get(hash);
						return `${hash} (${Array.from(
							/** @type {AssetInfoForRealContentHash[]} */ (assets),
							a => a.name
						)})`;
					};
					const hashesInOrder = new Set();
					for (const hash of hashToAssets.keys()) {
						/**
						 * @param {string} hash the hash
						 * @param {Set<string>} stack stack of hashes
						 */
						const add = (hash, stack) => {
							const deps = getDependencies(hash);
							if (!deps) return;
							stack.add(hash);
							for (const dep of deps) {
								if (hashesInOrder.has(dep)) continue;
								if (stack.has(dep)) {
									throw new Error(
										`Circular hash dependency ${Array.from(
											stack,
											hashInfo
										).join(" -> ")} -> ${hashInfo(dep)}`
									);
								}
								add(dep, stack);
							}
							hashesInOrder.add(hash);
							stack.delete(hash);
						};
						if (hashesInOrder.has(hash)) continue;
						add(hash, new Set());
					}
					const hashToNewHash = new Map();
					/**
					 * @param {AssetInfoForRealContentHash} asset asset info
					 * @returns {Etag} etag
					 */
					const getEtag = asset =>
						cacheGenerate.mergeEtags(
							cacheGenerate.getLazyHashedEtag(asset.source),
							Array.from(
								/** @type {ReferencedHashes} */ (asset.referencedHashes),
								hash => hashToNewHash.get(hash)
							).join("|")
						);
					/**
					 * @param {AssetInfoForRealContentHash} asset asset info
					 * @returns {Promise<void>}
					 */
					const computeNewContent = asset => {
						if (asset.contentComputePromise) return asset.contentComputePromise;
						return (asset.contentComputePromise = (async () => {
							if (
								/** @type {OwnHashes} */ (asset.ownHashes).size > 0 ||
								Array.from(
									/** @type {ReferencedHashes} */
									(asset.referencedHashes)
								).some(hash => hashToNewHash.get(hash) !== hash)
							) {
								const identifier = asset.name;
								const etag = getEtag(asset);
								asset.newSource = await cacheGenerate.providePromise(
									identifier,
									etag,
									() => {
										const newContent = asset.content.replace(hashRegExp, hash =>
											hashToNewHash.get(hash)
										);
										return new RawSource(newContent);
									}
								);
							}
						})());
					};
					/**
					 * @param {AssetInfoForRealContentHash} asset asset info
					 * @returns {Promise<void>}
					 */
					const computeNewContentWithoutOwn = asset => {
						if (asset.contentComputeWithoutOwnPromise)
							return asset.contentComputeWithoutOwnPromise;
						return (asset.contentComputeWithoutOwnPromise = (async () => {
							if (
								/** @type {OwnHashes} */ (asset.ownHashes).size > 0 ||
								Array.from(
									/** @type {ReferencedHashes} */
									(asset.referencedHashes)
								).some(hash => hashToNewHash.get(hash) !== hash)
							) {
								const identifier = asset.name + "|without-own";
								const etag = getEtag(asset);
								asset.newSourceWithoutOwn = await cacheGenerate.providePromise(
									identifier,
									etag,
									() => {
										const newContent = asset.content.replace(
											hashRegExp,
											hash => {
												if (
													/** @type {OwnHashes} */ (asset.ownHashes).has(hash)
												) {
													return "";
												}
												return hashToNewHash.get(hash);
											}
										);
										return new RawSource(newContent);
									}
								);
							}
						})());
					};
					const comparator = compareSelect(a => a.name, compareStrings);
					for (const oldHash of hashesInOrder) {
						const assets =
							/** @type {AssetInfoForRealContentHash[]} */
							(hashToAssets.get(oldHash));
						assets.sort(comparator);
						await Promise.all(
							assets.map(asset =>
								/** @type {OwnHashes} */ (asset.ownHashes).has(oldHash)
									? computeNewContentWithoutOwn(asset)
									: computeNewContent(asset)
							)
						);
						const assetsContent = mapAndDeduplicateBuffers(assets, asset => {
							if (/** @type {OwnHashes} */ (asset.ownHashes).has(oldHash)) {
								return asset.newSourceWithoutOwn
									? asset.newSourceWithoutOwn.buffer()
									: asset.source.buffer();
							} else {
								return asset.newSource
									? asset.newSource.buffer()
									: asset.source.buffer();
							}
						});
						let newHash = hooks.updateHash.call(assetsContent, oldHash);
						if (!newHash) {
							const hash = createHash(this._hashFunction);
							if (compilation.outputOptions.hashSalt) {
								hash.update(compilation.outputOptions.hashSalt);
							}
							for (const content of assetsContent) {
								hash.update(content);
							}
							const digest = hash.digest(this._hashDigest);
							newHash = /** @type {string} */ (digest.slice(0, oldHash.length));
						}
						hashToNewHash.set(oldHash, newHash);
					}
					await Promise.all(
						assetsWithInfo.map(async asset => {
							await computeNewContent(asset);
							const newName = asset.name.replace(hashRegExp, hash =>
								hashToNewHash.get(hash)
							);

							const infoUpdate = {};
							const hash = asset.info.contenthash;
							infoUpdate.contenthash = Array.isArray(hash)
								? hash.map(hash => hashToNewHash.get(hash))
								: hashToNewHash.get(hash);

							if (asset.newSource !== undefined) {
								compilation.updateAsset(
									asset.name,
									asset.newSource,
									infoUpdate
								);
							} else {
								compilation.updateAsset(asset.name, asset.source, infoUpdate);
							}

							if (asset.name !== newName) {
								compilation.renameAsset(asset.name, newName);
							}
						})
					);
				}
			);
		});
	}
}

module.exports = RealContentHashPlugin;


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

"use strict";

const { STAGE_BASIC, STAGE_ADVANCED } = require("../OptimizationStages");

/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Compiler")} Compiler */

class RemoveEmptyChunksPlugin {
	/**
	 * Apply the plugin
	 * @param {Compiler} compiler the compiler instance
	 * @returns {void}
	 */
	apply(compiler) {
		compiler.hooks.compilation.tap("RemoveEmptyChunksPlugin", compilation => {
			/**
			 * @param {Iterable<Chunk>} chunks the chunks array
			 * @returns {void}
			 */
			const handler = chunks => {
				const chunkGraph = compilation.chunkGraph;
				for (const chunk of chunks) {
					if (
						chunkGraph.getNumberOfChunkModules(chunk) === 0 &&
						!chunk.hasRuntime() &&
						chunkGraph.getNumberOfEntryModules(chunk) === 0
					) {
						compilation.chunkGraph.disconnectChunk(chunk);
						compilation.chunks.delete(chunk);
					}
				}
			};

			// TODO do it once
			compilation.hooks.optimizeChunks.tap(
				{
					name: "RemoveEmptyChunksPlugin",
					stage: STAGE_BASIC
				},
				handler
			);
			compilation.hooks.optimizeChunks.tap(
				{
					name: "RemoveEmptyChunksPlugin",
					stage: STAGE_ADVANCED
				},
				handler
			);
		});
	}
}
module.exports = RemoveEmptyChunksPlugin;


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

"use strict";

const { STAGE_BASIC } = require("../OptimizationStages");

/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../ChunkGroup")} ChunkGroup */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Module")} Module */

/**
 * Intersects multiple masks represented as bigints
 * @param {bigint[]} masks The module masks to intersect
 * @returns {bigint} The intersection of all masks
 */
function intersectMasks(masks) {
	let result = masks[0];
	for (let i = masks.length - 1; i >= 1; i--) {
		result &= masks[i];
	}
	return result;
}

const ZERO_BIGINT = BigInt(0);
const ONE_BIGINT = BigInt(1);
const THIRTY_TWO_BIGINT = BigInt(32);

/**
 * Parses the module mask and returns the modules represented by it
 * @param {bigint} mask the module mask
 * @param {Module[]} ordinalModules the modules in the order they were added to the mask (LSB is index 0)
 * @returns {Generator<Module>} the modules represented by the mask
 */
function* getModulesFromMask(mask, ordinalModules) {
	let offset = 31;
	while (mask !== ZERO_BIGINT) {
		// Consider the last 32 bits, since that's what Math.clz32 can handle
		let last32 = Number(BigInt.asUintN(32, mask));
		while (last32 > 0) {
			let last = Math.clz32(last32);
			// The number of trailing zeros is the number trimmed off the input mask + 31 - the number of leading zeros
			// The 32 is baked into the initial value of offset
			const moduleIndex = offset - last;
			// The number of trailing zeros is the index into the array generated by getOrCreateModuleMask
			const module = ordinalModules[moduleIndex];
			yield module;
			// Remove the matched module from the mask
			// Since we can only count leading zeros, not trailing, we can't just downshift the mask
			last32 &= ~(1 << (31 - last));
		}

		// Remove the processed chunk from the mask
		mask >>= THIRTY_TWO_BIGINT;
		offset += 32;
	}
}

class RemoveParentModulesPlugin {
	/**
	 * @param {Compiler} compiler the compiler
	 * @returns {void}
	 */
	apply(compiler) {
		compiler.hooks.compilation.tap("RemoveParentModulesPlugin", compilation => {
			/**
			 * @param {Iterable<Chunk>} chunks the chunks
			 * @param {ChunkGroup[]} chunkGroups the chunk groups
			 */
			const handler = (chunks, chunkGroups) => {
				const chunkGraph = compilation.chunkGraph;
				const queue = new Set();
				const availableModulesMap = new WeakMap();

				let nextModuleMask = ONE_BIGINT;
				const maskByModule = new WeakMap();
				/** @type {Module[]} */
				const ordinalModules = [];

				/**
				 * Gets or creates a unique mask for a module
				 * @param {Module} mod the module to get the mask for
				 * @returns {bigint} the module mask to uniquely identify the module
				 */
				const getOrCreateModuleMask = mod => {
					let id = maskByModule.get(mod);
					if (id === undefined) {
						id = nextModuleMask;
						ordinalModules.push(mod);
						maskByModule.set(mod, id);
						nextModuleMask <<= ONE_BIGINT;
					}
					return id;
				};

				// Initialize masks by chunk and by chunk group for quicker comparisons
				const chunkMasks = new WeakMap();
				for (const chunk of chunks) {
					let mask = ZERO_BIGINT;
					for (const m of chunkGraph.getChunkModulesIterable(chunk)) {
						const id = getOrCreateModuleMask(m);
						mask |= id;
					}
					chunkMasks.set(chunk, mask);
				}

				const chunkGroupMasks = new WeakMap();
				for (const chunkGroup of chunkGroups) {
					let mask = ZERO_BIGINT;
					for (const chunk of chunkGroup.chunks) {
						const chunkMask = chunkMasks.get(chunk);
						if (chunkMask !== undefined) {
							mask |= chunkMask;
						}
					}
					chunkGroupMasks.set(chunkGroup, mask);
				}

				for (const chunkGroup of compilation.entrypoints.values()) {
					// initialize available modules for chunks without parents
					availableModulesMap.set(chunkGroup, ZERO_BIGINT);
					for (const child of chunkGroup.childrenIterable) {
						queue.add(child);
					}
				}
				for (const chunkGroup of compilation.asyncEntrypoints) {
					// initialize available modules for chunks without parents
					availableModulesMap.set(chunkGroup, ZERO_BIGINT);
					for (const child of chunkGroup.childrenIterable) {
						queue.add(child);
					}
				}

				for (const chunkGroup of queue) {
					let availableModulesMask = availableModulesMap.get(chunkGroup);
					let changed = false;
					for (const parent of chunkGroup.parentsIterable) {
						const availableModulesInParent = availableModulesMap.get(parent);
						if (availableModulesInParent !== undefined) {
							const parentMask =
								availableModulesInParent | chunkGroupMasks.get(parent);
							// If we know the available modules in parent: process these
							if (availableModulesMask === undefined) {
								// if we have not own info yet: create new entry
								availableModulesMask = parentMask;
								changed = true;
							} else {
								let newMask = availableModulesMask & parentMask;
								if (newMask !== availableModulesMask) {
									changed = true;
									availableModulesMask = newMask;
								}
							}
						}
					}

					if (changed) {
						availableModulesMap.set(chunkGroup, availableModulesMask);
						// if something changed: enqueue our children
						for (const child of chunkGroup.childrenIterable) {
							// Push the child to the end of the queue
							queue.delete(child);
							queue.add(child);
						}
					}
				}

				// now we have available modules for every chunk
				for (const chunk of chunks) {
					const chunkMask = chunkMasks.get(chunk);
					if (chunkMask === undefined) continue; // No info about this chunk

					const availableModulesSets = Array.from(
						chunk.groupsIterable,
						chunkGroup => availableModulesMap.get(chunkGroup)
					);
					if (availableModulesSets.some(s => s === undefined)) continue; // No info about this chunk group

					const availableModulesMask = intersectMasks(availableModulesSets);
					const toRemoveMask = chunkMask & availableModulesMask;
					if (toRemoveMask !== ZERO_BIGINT) {
						for (const module of getModulesFromMask(
							toRemoveMask,
							ordinalModules
						)) {
							chunkGraph.disconnectChunkAndModule(chunk, module);
						}
					}
				}
			};
			compilation.hooks.optimizeChunks.tap(
				{
					name: "RemoveParentModulesPlugin",
					stage: STAGE_BASIC
				},
				handler
			);
		});
	}
}
module.exports = RemoveParentModulesPlugin;


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

"use strict";

/** @typedef {import("../Compilation").EntryData} EntryData */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Entrypoint")} Entrypoint */

class RuntimeChunkPlugin {
	/**
	 * @param {{ name?: (entrypoint: { name: string }) => string }} options options
	 */
	constructor(options) {
		this.options = {
			/**
			 * @param {Entrypoint} entrypoint entrypoint name
			 * @returns {string} runtime chunk name
			 */
			name: entrypoint => `runtime~${entrypoint.name}`,
			...options
		};
	}

	/**
	 * Apply the plugin
	 * @param {Compiler} compiler the compiler instance
	 * @returns {void}
	 */
	apply(compiler) {
		compiler.hooks.thisCompilation.tap("RuntimeChunkPlugin", compilation => {
			compilation.hooks.addEntry.tap(
				"RuntimeChunkPlugin",
				(_, { name: entryName }) => {
					if (entryName === undefined) return;
					const data =
						/** @type {EntryData} */
						(compilation.entries.get(entryName));
					if (data.options.runtime === undefined && !data.options.dependOn) {
						// Determine runtime chunk name
						let name =
							/** @type {string | ((entrypoint: { name: string }) => string)} */
							(this.options.name);
						if (typeof name === "function") {
							name = name({ name: entryName });
						}
						data.options.runtime = name;
					}
				}
			);
		});
	}
}

module.exports = RuntimeChunkPlugin;


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

"use strict";

const glob2regexp = require("glob-to-regexp");
const {
	JAVASCRIPT_MODULE_TYPE_AUTO,
	JAVASCRIPT_MODULE_TYPE_ESM,
	JAVASCRIPT_MODULE_TYPE_DYNAMIC
} = require("../ModuleTypeConstants");
const { STAGE_DEFAULT } = require("../OptimizationStages");
const HarmonyExportImportedSpecifierDependency = require("../dependencies/HarmonyExportImportedSpecifierDependency");
const HarmonyImportSpecifierDependency = require("../dependencies/HarmonyImportSpecifierDependency");
const formatLocation = require("../formatLocation");

/** @typedef {import("estree").ModuleDeclaration} ModuleDeclaration */
/** @typedef {import("estree").Statement} Statement */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../Module").BuildMeta} BuildMeta */
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
/** @typedef {import("../javascript/JavascriptParser").Range} Range */

/**
 * @typedef {object} ExportInModule
 * @property {Module} module the module
 * @property {string} exportName the name of the export
 * @property {boolean} checked if the export is conditional
 */

/**
 * @typedef {object} ReexportInfo
 * @property {Map<string, ExportInModule[]>} static
 * @property {Map<Module, Set<string>>} dynamic
 */

/** @typedef {Map<string, RegExp>} CacheItem */

/** @type {WeakMap<any, CacheItem>} */
const globToRegexpCache = new WeakMap();

/**
 * @param {string} glob the pattern
 * @param {Map<string, RegExp>} cache the glob to RegExp cache
 * @returns {RegExp} a regular expression
 */
const globToRegexp = (glob, cache) => {
	const cacheEntry = cache.get(glob);
	if (cacheEntry !== undefined) return cacheEntry;
	if (!glob.includes("/")) {
		glob = `**/${glob}`;
	}
	const baseRegexp = glob2regexp(glob, { globstar: true, extended: true });
	const regexpSource = baseRegexp.source;
	const regexp = new RegExp("^(\\./)?" + regexpSource.slice(1));
	cache.set(glob, regexp);
	return regexp;
};

const PLUGIN_NAME = "SideEffectsFlagPlugin";

class SideEffectsFlagPlugin {
	/**
	 * @param {boolean} analyseSource analyse source code for side effects
	 */
	constructor(analyseSource = true) {
		this._analyseSource = analyseSource;
	}
	/**
	 * Apply the plugin
	 * @param {Compiler} compiler the compiler instance
	 * @returns {void}
	 */
	apply(compiler) {
		let cache = globToRegexpCache.get(compiler.root);
		if (cache === undefined) {
			cache = new Map();
			globToRegexpCache.set(compiler.root, cache);
		}
		compiler.hooks.compilation.tap(
			PLUGIN_NAME,
			(compilation, { normalModuleFactory }) => {
				const moduleGraph = compilation.moduleGraph;
				normalModuleFactory.hooks.module.tap(PLUGIN_NAME, (module, data) => {
					const resolveData = data.resourceResolveData;
					if (
						resolveData &&
						resolveData.descriptionFileData &&
						resolveData.relativePath
					) {
						const sideEffects = resolveData.descriptionFileData.sideEffects;
						if (sideEffects !== undefined) {
							if (module.factoryMeta === undefined) {
								module.factoryMeta = {};
							}
							const hasSideEffects = SideEffectsFlagPlugin.moduleHasSideEffects(
								resolveData.relativePath,
								sideEffects,
								/** @type {CacheItem} */ (cache)
							);
							module.factoryMeta.sideEffectFree = !hasSideEffects;
						}
					}

					return module;
				});
				normalModuleFactory.hooks.module.tap(PLUGIN_NAME, (module, data) => {
					if (typeof data.settings.sideEffects === "boolean") {
						if (module.factoryMeta === undefined) {
							module.factoryMeta = {};
						}
						module.factoryMeta.sideEffectFree = !data.settings.sideEffects;
					}
					return module;
				});
				if (this._analyseSource) {
					/**
					 * @param {JavascriptParser} parser the parser
					 * @returns {void}
					 */
					const parserHandler = parser => {
						/** @type {undefined | Statement | ModuleDeclaration} */
						let sideEffectsStatement;
						parser.hooks.program.tap(PLUGIN_NAME, () => {
							sideEffectsStatement = undefined;
						});
						parser.hooks.statement.tap(
							{ name: PLUGIN_NAME, stage: -100 },
							statement => {
								if (sideEffectsStatement) return;
								if (parser.scope.topLevelScope !== true) return;
								switch (statement.type) {
									case "ExpressionStatement":
										if (
											!parser.isPure(
												statement.expression,
												/** @type {Range} */ (statement.range)[0]
											)
										) {
											sideEffectsStatement = statement;
										}
										break;
									case "IfStatement":
									case "WhileStatement":
									case "DoWhileStatement":
										if (
											!parser.isPure(
												statement.test,
												/** @type {Range} */ (statement.range)[0]
											)
										) {
											sideEffectsStatement = statement;
										}
										// statement hook will be called for child statements too
										break;
									case "ForStatement":
										if (
											!parser.isPure(
												statement.init,
												/** @type {Range} */ (statement.range)[0]
											) ||
											!parser.isPure(
												statement.test,
												statement.init
													? /** @type {Range} */ (statement.init.range)[1]
													: /** @type {Range} */ (statement.range)[0]
											) ||
											!parser.isPure(
												statement.update,
												statement.test
													? /** @type {Range} */ (statement.test.range)[1]
													: statement.init
														? /** @type {Range} */ (statement.init.range)[1]
														: /** @type {Range} */ (statement.range)[0]
											)
										) {
											sideEffectsStatement = statement;
										}
										// statement hook will be called for child statements too
										break;
									case "SwitchStatement":
										if (
											!parser.isPure(
												statement.discriminant,
												/** @type {Range} */ (statement.range)[0]
											)
										) {
											sideEffectsStatement = statement;
										}
										// statement hook will be called for child statements too
										break;
									case "VariableDeclaration":
									case "ClassDeclaration":
									case "FunctionDeclaration":
										if (
											!parser.isPure(
												statement,
												/** @type {Range} */ (statement.range)[0]
											)
										) {
											sideEffectsStatement = statement;
										}
										break;
									case "ExportNamedDeclaration":
									case "ExportDefaultDeclaration":
										if (
											!parser.isPure(
												statement.declaration,
												/** @type {Range} */ (statement.range)[0]
											)
										) {
											sideEffectsStatement = statement;
										}
										break;
									case "LabeledStatement":
									case "BlockStatement":
										// statement hook will be called for child statements too
										break;
									case "EmptyStatement":
										break;
									case "ExportAllDeclaration":
									case "ImportDeclaration":
										// imports will be handled by the dependencies
										break;
									default:
										sideEffectsStatement = statement;
										break;
								}
							}
						);
						parser.hooks.finish.tap(PLUGIN_NAME, () => {
							if (sideEffectsStatement === undefined) {
								/** @type {BuildMeta} */
								(parser.state.module.buildMeta).sideEffectFree = true;
							} else {
								const { loc, type } = sideEffectsStatement;
								moduleGraph
									.getOptimizationBailout(parser.state.module)
									.push(
										() =>
											`Statement (${type}) with side effects in source code at ${formatLocation(
												/** @type {DependencyLocation} */ (loc)
											)}`
									);
							}
						});
					};
					for (const key of [
						JAVASCRIPT_MODULE_TYPE_AUTO,
						JAVASCRIPT_MODULE_TYPE_ESM,
						JAVASCRIPT_MODULE_TYPE_DYNAMIC
					]) {
						normalModuleFactory.hooks.parser
							.for(key)
							.tap(PLUGIN_NAME, parserHandler);
					}
				}
				compilation.hooks.optimizeDependencies.tap(
					{
						name: PLUGIN_NAME,
						stage: STAGE_DEFAULT
					},
					modules => {
						const logger = compilation.getLogger(
							"webpack.SideEffectsFlagPlugin"
						);

						logger.time("update dependencies");

						const optimizedModules = new Set();

						/**
						 * @param {Module} module module
						 */
						const optimizeIncomingConnections = module => {
							if (optimizedModules.has(module)) return;
							optimizedModules.add(module);
							if (module.getSideEffectsConnectionState(moduleGraph) === false) {
								const exportsInfo = moduleGraph.getExportsInfo(module);
								for (const connection of moduleGraph.getIncomingConnections(
									module
								)) {
									const dep = connection.dependency;
									let isReexport;
									if (
										(isReexport =
											dep instanceof
											HarmonyExportImportedSpecifierDependency) ||
										(dep instanceof HarmonyImportSpecifierDependency &&
											!dep.namespaceObjectAsContext)
									) {
										if (connection.originModule !== null) {
											optimizeIncomingConnections(connection.originModule);
										}
										// TODO improve for export *
										if (isReexport && dep.name) {
											const exportInfo = moduleGraph.getExportInfo(
												/** @type {Module} */ (connection.originModule),
												dep.name
											);
											exportInfo.moveTarget(
												moduleGraph,
												({ module }) =>
													module.getSideEffectsConnectionState(moduleGraph) ===
													false,
												({ module: newModule, export: exportName }) => {
													moduleGraph.updateModule(dep, newModule);
													moduleGraph.addExplanation(
														dep,
														"(skipped side-effect-free modules)"
													);
													const ids = dep.getIds(moduleGraph);
													dep.setIds(
														moduleGraph,
														exportName
															? [...exportName, ...ids.slice(1)]
															: ids.slice(1)
													);
													return moduleGraph.getConnection(dep);
												}
											);
											continue;
										}
										// TODO improve for nested imports
										const ids = dep.getIds(moduleGraph);
										if (ids.length > 0) {
											const exportInfo = exportsInfo.getExportInfo(ids[0]);
											const target = exportInfo.getTarget(
												moduleGraph,
												({ module }) =>
													module.getSideEffectsConnectionState(moduleGraph) ===
													false
											);
											if (!target) continue;

											moduleGraph.updateModule(dep, target.module);
											moduleGraph.addExplanation(
												dep,
												"(skipped side-effect-free modules)"
											);
											dep.setIds(
												moduleGraph,
												target.export
													? [...target.export, ...ids.slice(1)]
													: ids.slice(1)
											);
										}
									}
								}
							}
						};

						for (const module of modules) {
							optimizeIncomingConnections(module);
						}
						logger.timeEnd("update dependencies");
					}
				);
			}
		);
	}

	/**
	 * @param {string} moduleName the module name
	 * @param {undefined | boolean | string | string[]} flagValue the flag value
	 * @param {Map<string, RegExp>} cache cache for glob to regexp
	 * @returns {boolean | undefined} true, when the module has side effects, undefined or false when not
	 */
	static moduleHasSideEffects(moduleName, flagValue, cache) {
		switch (typeof flagValue) {
			case "undefined":
				return true;
			case "boolean":
				return flagValue;
			case "string":
				return globToRegexp(flagValue, cache).test(moduleName);
			case "object":
				return flagValue.some(glob =>
					SideEffectsFlagPlugin.moduleHasSideEffects(moduleName, glob, cache)
				);
		}
	}
}
module.exports = SideEffectsFlagPlugin;