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

"use strict";

const Chunk = require("../Chunk");
const { STAGE_ADVANCED } = require("../OptimizationStages");
const WebpackError = require("../WebpackError");
const { requestToId } = require("../ids/IdHelpers");
const { isSubset } = require("../util/SetHelpers");
const SortableSet = require("../util/SortableSet");
const {
	compareModulesByIdentifier,
	compareIterables
} = require("../util/comparators");
const createHash = require("../util/createHash");
const deterministicGrouping = require("../util/deterministicGrouping");
const { makePathsRelative } = require("../util/identifier");
const memoize = require("../util/memoize");
const MinMaxSizeWarning = require("./MinMaxSizeWarning");

/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksCacheGroup} OptimizationSplitChunksCacheGroup */
/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksGetCacheGroups} OptimizationSplitChunksGetCacheGroups */
/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksOptions} OptimizationSplitChunksOptions */
/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksSizes} OptimizationSplitChunksSizes */
/** @typedef {import("../../declarations/WebpackOptions").Output} OutputOptions */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../ChunkGroup")} ChunkGroup */
/** @typedef {import("../Compilation").AssetInfo} AssetInfo */
/** @typedef {import("../Compilation").PathData} PathData */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../util/deterministicGrouping").GroupedItems<Module>} DeterministicGroupingGroupedItemsForModule */
/** @typedef {import("../util/deterministicGrouping").Options<Module>} DeterministicGroupingOptionsForModule */

/** @typedef {Record<string, number>} SplitChunksSizes */

/**
 * @callback ChunkFilterFunction
 * @param {Chunk} chunk
 * @returns {boolean | undefined}
 */

/**
 * @callback CombineSizeFunction
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */

/**
 * @typedef {object} CacheGroupSource
 * @property {string=} key
 * @property {number=} priority
 * @property {GetName=} getName
 * @property {ChunkFilterFunction=} chunksFilter
 * @property {boolean=} enforce
 * @property {SplitChunksSizes} minSize
 * @property {SplitChunksSizes} minSizeReduction
 * @property {SplitChunksSizes} minRemainingSize
 * @property {SplitChunksSizes} enforceSizeThreshold
 * @property {SplitChunksSizes} maxAsyncSize
 * @property {SplitChunksSizes} maxInitialSize
 * @property {number=} minChunks
 * @property {number=} maxAsyncRequests
 * @property {number=} maxInitialRequests
 * @property {(string | function(PathData, AssetInfo=): string)=} filename
 * @property {string=} idHint
 * @property {string=} automaticNameDelimiter
 * @property {boolean=} reuseExistingChunk
 * @property {boolean=} usedExports
 */

/**
 * @typedef {object} CacheGroup
 * @property {string} key
 * @property {number=} priority
 * @property {GetName=} getName
 * @property {ChunkFilterFunction=} chunksFilter
 * @property {SplitChunksSizes} minSize
 * @property {SplitChunksSizes} minSizeReduction
 * @property {SplitChunksSizes} minRemainingSize
 * @property {SplitChunksSizes} enforceSizeThreshold
 * @property {SplitChunksSizes} maxAsyncSize
 * @property {SplitChunksSizes} maxInitialSize
 * @property {number=} minChunks
 * @property {number=} maxAsyncRequests
 * @property {number=} maxInitialRequests
 * @property {(string | function(PathData, AssetInfo=): string)=} filename
 * @property {string=} idHint
 * @property {string} automaticNameDelimiter
 * @property {boolean} reuseExistingChunk
 * @property {boolean} usedExports
 * @property {boolean} _validateSize
 * @property {boolean} _validateRemainingSize
 * @property {SplitChunksSizes} _minSizeForMaxSize
 * @property {boolean} _conditionalEnforce
 */

/**
 * @typedef {object} FallbackCacheGroup
 * @property {ChunkFilterFunction} chunksFilter
 * @property {SplitChunksSizes} minSize
 * @property {SplitChunksSizes} maxAsyncSize
 * @property {SplitChunksSizes} maxInitialSize
 * @property {string} automaticNameDelimiter
 */

/**
 * @typedef {object} CacheGroupsContext
 * @property {ModuleGraph} moduleGraph
 * @property {ChunkGraph} chunkGraph
 */

/**
 * @callback GetCacheGroups
 * @param {Module} module
 * @param {CacheGroupsContext} context
 * @returns {CacheGroupSource[]}
 */

/**
 * @callback GetName
 * @param {Module=} module
 * @param {Chunk[]=} chunks
 * @param {string=} key
 * @returns {string=}
 */

/**
 * @typedef {object} SplitChunksOptions
 * @property {ChunkFilterFunction} chunksFilter
 * @property {string[]} defaultSizeTypes
 * @property {SplitChunksSizes} minSize
 * @property {SplitChunksSizes} minSizeReduction
 * @property {SplitChunksSizes} minRemainingSize
 * @property {SplitChunksSizes} enforceSizeThreshold
 * @property {SplitChunksSizes} maxInitialSize
 * @property {SplitChunksSizes} maxAsyncSize
 * @property {number} minChunks
 * @property {number} maxAsyncRequests
 * @property {number} maxInitialRequests
 * @property {boolean} hidePathInfo
 * @property {string | function(PathData, AssetInfo=): string} filename
 * @property {string} automaticNameDelimiter
 * @property {GetCacheGroups} getCacheGroups
 * @property {GetName} getName
 * @property {boolean} usedExports
 * @property {FallbackCacheGroup} fallbackCacheGroup
 */

/**
 * @typedef {object} ChunksInfoItem
 * @property {SortableSet<Module>} modules
 * @property {CacheGroup} cacheGroup
 * @property {number} cacheGroupIndex
 * @property {string} name
 * @property {Record<string, number>} sizes
 * @property {Set<Chunk>} chunks
 * @property {Set<Chunk>} reusableChunks
 * @property {Set<bigint | Chunk>} chunksKeys
 */

const defaultGetName = /** @type {GetName} */ (() => {});

const deterministicGroupingForModules =
	/** @type {function(DeterministicGroupingOptionsForModule): DeterministicGroupingGroupedItemsForModule[]} */ (
		deterministicGrouping
	);

/** @type {WeakMap<Module, string>} */
const getKeyCache = new WeakMap();

/**
 * @param {string} name a filename to hash
 * @param {OutputOptions} outputOptions hash function used
 * @returns {string} hashed filename
 */
const hashFilename = (name, outputOptions) => {
	const digest = /** @type {string} */ (
		createHash(outputOptions.hashFunction)
			.update(name)
			.digest(outputOptions.hashDigest)
	);
	return digest.slice(0, 8);
};

/**
 * @param {Chunk} chunk the chunk
 * @returns {number} the number of requests
 */
const getRequests = chunk => {
	let requests = 0;
	for (const chunkGroup of chunk.groupsIterable) {
		requests = Math.max(requests, chunkGroup.chunks.length);
	}
	return requests;
};

const mapObject = (obj, fn) => {
	const newObj = Object.create(null);
	for (const key of Object.keys(obj)) {
		newObj[key] = fn(obj[key], key);
	}
	return newObj;
};

/**
 * @template T
 * @param {Set<T>} a set
 * @param {Set<T>} b other set
 * @returns {boolean} true if at least one item of a is in b
 */
const isOverlap = (a, b) => {
	for (const item of a) {
		if (b.has(item)) return true;
	}
	return false;
};

const compareModuleIterables = compareIterables(compareModulesByIdentifier);

/**
 * @param {ChunksInfoItem} a item
 * @param {ChunksInfoItem} b item
 * @returns {number} compare result
 */
const compareEntries = (a, b) => {
	// 1. by priority
	const diffPriority = a.cacheGroup.priority - b.cacheGroup.priority;
	if (diffPriority) return diffPriority;
	// 2. by number of chunks
	const diffCount = a.chunks.size - b.chunks.size;
	if (diffCount) return diffCount;
	// 3. by size reduction
	const aSizeReduce = totalSize(a.sizes) * (a.chunks.size - 1);
	const bSizeReduce = totalSize(b.sizes) * (b.chunks.size - 1);
	const diffSizeReduce = aSizeReduce - bSizeReduce;
	if (diffSizeReduce) return diffSizeReduce;
	// 4. by cache group index
	const indexDiff = b.cacheGroupIndex - a.cacheGroupIndex;
	if (indexDiff) return indexDiff;
	// 5. by number of modules (to be able to compare by identifier)
	const modulesA = a.modules;
	const modulesB = b.modules;
	const diff = modulesA.size - modulesB.size;
	if (diff) return diff;
	// 6. by module identifiers
	modulesA.sort();
	modulesB.sort();
	return compareModuleIterables(modulesA, modulesB);
};

/**
 * @param {Chunk} chunk the chunk
 * @returns {boolean} true, if the chunk is an entry chunk
 */
const INITIAL_CHUNK_FILTER = chunk => chunk.canBeInitial();
/**
 * @param {Chunk} chunk the chunk
 * @returns {boolean} true, if the chunk is an async chunk
 */
const ASYNC_CHUNK_FILTER = chunk => !chunk.canBeInitial();
/**
 * @param {Chunk} chunk the chunk
 * @returns {boolean} always true
 */
const ALL_CHUNK_FILTER = chunk => true;

/**
 * @param {OptimizationSplitChunksSizes | undefined} value the sizes
 * @param {string[]} defaultSizeTypes the default size types
 * @returns {SplitChunksSizes} normalized representation
 */
const normalizeSizes = (value, defaultSizeTypes) => {
	if (typeof value === "number") {
		/** @type {Record<string, number>} */
		const o = {};
		for (const sizeType of defaultSizeTypes) o[sizeType] = value;
		return o;
	} else if (typeof value === "object" && value !== null) {
		return { ...value };
	} else {
		return {};
	}
};

/**
 * @param {...SplitChunksSizes} sizes the sizes
 * @returns {SplitChunksSizes} the merged sizes
 */
const mergeSizes = (...sizes) => {
	/** @type {SplitChunksSizes} */
	let merged = {};
	for (let i = sizes.length - 1; i >= 0; i--) {
		merged = Object.assign(merged, sizes[i]);
	}
	return merged;
};

/**
 * @param {SplitChunksSizes} sizes the sizes
 * @returns {boolean} true, if there are sizes > 0
 */
const hasNonZeroSizes = sizes => {
	for (const key of Object.keys(sizes)) {
		if (sizes[key] > 0) return true;
	}
	return false;
};

/**
 * @param {SplitChunksSizes} a first sizes
 * @param {SplitChunksSizes} b second sizes
 * @param {CombineSizeFunction} combine a function to combine sizes
 * @returns {SplitChunksSizes} the combine sizes
 */
const combineSizes = (a, b, combine) => {
	const aKeys = new Set(Object.keys(a));
	const bKeys = new Set(Object.keys(b));
	/** @type {SplitChunksSizes} */
	const result = {};
	for (const key of aKeys) {
		if (bKeys.has(key)) {
			result[key] = combine(a[key], b[key]);
		} else {
			result[key] = a[key];
		}
	}
	for (const key of bKeys) {
		if (!aKeys.has(key)) {
			result[key] = b[key];
		}
	}
	return result;
};

/**
 * @param {SplitChunksSizes} sizes the sizes
 * @param {SplitChunksSizes} minSize the min sizes
 * @returns {boolean} true if there are sizes and all existing sizes are at least `minSize`
 */
const checkMinSize = (sizes, minSize) => {
	for (const key of Object.keys(minSize)) {
		const size = sizes[key];
		if (size === undefined || size === 0) continue;
		if (size < minSize[key]) return false;
	}
	return true;
};

/**
 * @param {SplitChunksSizes} sizes the sizes
 * @param {SplitChunksSizes} minSizeReduction the min sizes
 * @param {number} chunkCount number of chunks
 * @returns {boolean} true if there are sizes and all existing sizes are at least `minSizeReduction`
 */
const checkMinSizeReduction = (sizes, minSizeReduction, chunkCount) => {
	for (const key of Object.keys(minSizeReduction)) {
		const size = sizes[key];
		if (size === undefined || size === 0) continue;
		if (size * chunkCount < minSizeReduction[key]) return false;
	}
	return true;
};

/**
 * @param {SplitChunksSizes} sizes the sizes
 * @param {SplitChunksSizes} minSize the min sizes
 * @returns {undefined | string[]} list of size types that are below min size
 */
const getViolatingMinSizes = (sizes, minSize) => {
	let list;
	for (const key of Object.keys(minSize)) {
		const size = sizes[key];
		if (size === undefined || size === 0) continue;
		if (size < minSize[key]) {
			if (list === undefined) list = [key];
			else list.push(key);
		}
	}
	return list;
};

/**
 * @param {SplitChunksSizes} sizes the sizes
 * @returns {number} the total size
 */
const totalSize = sizes => {
	let size = 0;
	for (const key of Object.keys(sizes)) {
		size += sizes[key];
	}
	return size;
};

/**
 * @param {false|string|Function|undefined} name the chunk name
 * @returns {GetName | undefined} a function to get the name of the chunk
 */
const normalizeName = name => {
	if (typeof name === "string") {
		return () => name;
	}
	if (typeof name === "function") {
		return /** @type {GetName} */ (name);
	}
};

/**
 * @param {OptimizationSplitChunksCacheGroup["chunks"]} chunks the chunk filter option
 * @returns {ChunkFilterFunction} the chunk filter function
 */
const normalizeChunksFilter = chunks => {
	if (chunks === "initial") {
		return INITIAL_CHUNK_FILTER;
	}
	if (chunks === "async") {
		return ASYNC_CHUNK_FILTER;
	}
	if (chunks === "all") {
		return ALL_CHUNK_FILTER;
	}
	if (chunks instanceof RegExp) {
		return chunk => {
			return chunk.name ? chunks.test(chunk.name) : false;
		};
	}
	if (typeof chunks === "function") {
		return chunks;
	}
};

/**
 * @param {GetCacheGroups | Record<string, false|string|RegExp|OptimizationSplitChunksGetCacheGroups|OptimizationSplitChunksCacheGroup>} cacheGroups the cache group options
 * @param {string[]} defaultSizeTypes the default size types
 * @returns {GetCacheGroups} a function to get the cache groups
 */
const normalizeCacheGroups = (cacheGroups, defaultSizeTypes) => {
	if (typeof cacheGroups === "function") {
		return cacheGroups;
	}
	if (typeof cacheGroups === "object" && cacheGroups !== null) {
		/** @type {(function(Module, CacheGroupsContext, CacheGroupSource[]): void)[]} */
		const handlers = [];
		for (const key of Object.keys(cacheGroups)) {
			const option = cacheGroups[key];
			if (option === false) {
				continue;
			}
			if (typeof option === "string" || option instanceof RegExp) {
				const source = createCacheGroupSource({}, key, defaultSizeTypes);
				handlers.push((module, context, results) => {
					if (checkTest(option, module, context)) {
						results.push(source);
					}
				});
			} else if (typeof option === "function") {
				const cache = new WeakMap();
				handlers.push((module, context, results) => {
					const result = option(module);
					if (result) {
						const groups = Array.isArray(result) ? result : [result];
						for (const group of groups) {
							const cachedSource = cache.get(group);
							if (cachedSource !== undefined) {
								results.push(cachedSource);
							} else {
								const source = createCacheGroupSource(
									group,
									key,
									defaultSizeTypes
								);
								cache.set(group, source);
								results.push(source);
							}
						}
					}
				});
			} else {
				const source = createCacheGroupSource(option, key, defaultSizeTypes);
				handlers.push((module, context, results) => {
					if (
						checkTest(option.test, module, context) &&
						checkModuleType(option.type, module) &&
						checkModuleLayer(option.layer, module)
					) {
						results.push(source);
					}
				});
			}
		}
		/**
		 * @param {Module} module the current module
		 * @param {CacheGroupsContext} context the current context
		 * @returns {CacheGroupSource[]} the matching cache groups
		 */
		const fn = (module, context) => {
			/** @type {CacheGroupSource[]} */
			let results = [];
			for (const fn of handlers) {
				fn(module, context, results);
			}
			return results;
		};
		return fn;
	}
	return () => null;
};

/**
 * @param {undefined|boolean|string|RegExp|Function} test test option
 * @param {Module} module the module
 * @param {CacheGroupsContext} context context object
 * @returns {boolean} true, if the module should be selected
 */
const checkTest = (test, module, context) => {
	if (test === undefined) return true;
	if (typeof test === "function") {
		return test(module, context);
	}
	if (typeof test === "boolean") return test;
	if (typeof test === "string") {
		const name = module.nameForCondition();
		return name && name.startsWith(test);
	}
	if (test instanceof RegExp) {
		const name = module.nameForCondition();
		return name && test.test(name);
	}
	return false;
};

/**
 * @param {undefined|string|RegExp|Function} test type option
 * @param {Module} module the module
 * @returns {boolean} true, if the module should be selected
 */
const checkModuleType = (test, module) => {
	if (test === undefined) return true;
	if (typeof test === "function") {
		return test(module.type);
	}
	if (typeof test === "string") {
		const type = module.type;
		return test === type;
	}
	if (test instanceof RegExp) {
		const type = module.type;
		return test.test(type);
	}
	return false;
};

/**
 * @param {undefined|string|RegExp|Function} test type option
 * @param {Module} module the module
 * @returns {boolean} true, if the module should be selected
 */
const checkModuleLayer = (test, module) => {
	if (test === undefined) return true;
	if (typeof test === "function") {
		return test(module.layer);
	}
	if (typeof test === "string") {
		const layer = module.layer;
		return test === "" ? !layer : layer && layer.startsWith(test);
	}
	if (test instanceof RegExp) {
		const layer = module.layer;
		return test.test(layer);
	}
	return false;
};

/**
 * @param {OptimizationSplitChunksCacheGroup} options the group options
 * @param {string} key key of cache group
 * @param {string[]} defaultSizeTypes the default size types
 * @returns {CacheGroupSource} the normalized cached group
 */
const createCacheGroupSource = (options, key, defaultSizeTypes) => {
	const minSize = normalizeSizes(options.minSize, defaultSizeTypes);
	const minSizeReduction = normalizeSizes(
		options.minSizeReduction,
		defaultSizeTypes
	);
	const maxSize = normalizeSizes(options.maxSize, defaultSizeTypes);
	return {
		key,
		priority: options.priority,
		getName: normalizeName(options.name),
		chunksFilter: normalizeChunksFilter(options.chunks),
		enforce: options.enforce,
		minSize,
		minSizeReduction,
		minRemainingSize: mergeSizes(
			normalizeSizes(options.minRemainingSize, defaultSizeTypes),
			minSize
		),
		enforceSizeThreshold: normalizeSizes(
			options.enforceSizeThreshold,
			defaultSizeTypes
		),
		maxAsyncSize: mergeSizes(
			normalizeSizes(options.maxAsyncSize, defaultSizeTypes),
			maxSize
		),
		maxInitialSize: mergeSizes(
			normalizeSizes(options.maxInitialSize, defaultSizeTypes),
			maxSize
		),
		minChunks: options.minChunks,
		maxAsyncRequests: options.maxAsyncRequests,
		maxInitialRequests: options.maxInitialRequests,
		filename: options.filename,
		idHint: options.idHint,
		automaticNameDelimiter: options.automaticNameDelimiter,
		reuseExistingChunk: options.reuseExistingChunk,
		usedExports: options.usedExports
	};
};

module.exports = class SplitChunksPlugin {
	/**
	 * @param {OptimizationSplitChunksOptions=} options plugin options
	 */
	constructor(options = {}) {
		const defaultSizeTypes = options.defaultSizeTypes || [
			"javascript",
			"unknown"
		];
		const fallbackCacheGroup = options.fallbackCacheGroup || {};
		const minSize = normalizeSizes(options.minSize, defaultSizeTypes);
		const minSizeReduction = normalizeSizes(
			options.minSizeReduction,
			defaultSizeTypes
		);
		const maxSize = normalizeSizes(options.maxSize, defaultSizeTypes);

		/** @type {SplitChunksOptions} */
		this.options = {
			chunksFilter: normalizeChunksFilter(options.chunks || "all"),
			defaultSizeTypes,
			minSize,
			minSizeReduction,
			minRemainingSize: mergeSizes(
				normalizeSizes(options.minRemainingSize, defaultSizeTypes),
				minSize
			),
			enforceSizeThreshold: normalizeSizes(
				options.enforceSizeThreshold,
				defaultSizeTypes
			),
			maxAsyncSize: mergeSizes(
				normalizeSizes(options.maxAsyncSize, defaultSizeTypes),
				maxSize
			),
			maxInitialSize: mergeSizes(
				normalizeSizes(options.maxInitialSize, defaultSizeTypes),
				maxSize
			),
			minChunks: options.minChunks || 1,
			maxAsyncRequests: options.maxAsyncRequests || 1,
			maxInitialRequests: options.maxInitialRequests || 1,
			hidePathInfo: options.hidePathInfo || false,
			filename: options.filename || undefined,
			getCacheGroups: normalizeCacheGroups(
				options.cacheGroups,
				defaultSizeTypes
			),
			getName: options.name ? normalizeName(options.name) : defaultGetName,
			automaticNameDelimiter: options.automaticNameDelimiter,
			usedExports: options.usedExports,
			fallbackCacheGroup: {
				chunksFilter: normalizeChunksFilter(
					fallbackCacheGroup.chunks || options.chunks || "all"
				),
				minSize: mergeSizes(
					normalizeSizes(fallbackCacheGroup.minSize, defaultSizeTypes),
					minSize
				),
				maxAsyncSize: mergeSizes(
					normalizeSizes(fallbackCacheGroup.maxAsyncSize, defaultSizeTypes),
					normalizeSizes(fallbackCacheGroup.maxSize, defaultSizeTypes),
					normalizeSizes(options.maxAsyncSize, defaultSizeTypes),
					normalizeSizes(options.maxSize, defaultSizeTypes)
				),
				maxInitialSize: mergeSizes(
					normalizeSizes(fallbackCacheGroup.maxInitialSize, defaultSizeTypes),
					normalizeSizes(fallbackCacheGroup.maxSize, defaultSizeTypes),
					normalizeSizes(options.maxInitialSize, defaultSizeTypes),
					normalizeSizes(options.maxSize, defaultSizeTypes)
				),
				automaticNameDelimiter:
					fallbackCacheGroup.automaticNameDelimiter ||
					options.automaticNameDelimiter ||
					"~"
			}
		};

		/** @type {WeakMap<CacheGroupSource, CacheGroup>} */
		this._cacheGroupCache = new WeakMap();
	}

	/**
	 * @param {CacheGroupSource} cacheGroupSource source
	 * @returns {CacheGroup} the cache group (cached)
	 */
	_getCacheGroup(cacheGroupSource) {
		const cacheEntry = this._cacheGroupCache.get(cacheGroupSource);
		if (cacheEntry !== undefined) return cacheEntry;
		const minSize = mergeSizes(
			cacheGroupSource.minSize,
			cacheGroupSource.enforce ? undefined : this.options.minSize
		);
		const minSizeReduction = mergeSizes(
			cacheGroupSource.minSizeReduction,
			cacheGroupSource.enforce ? undefined : this.options.minSizeReduction
		);
		const minRemainingSize = mergeSizes(
			cacheGroupSource.minRemainingSize,
			cacheGroupSource.enforce ? undefined : this.options.minRemainingSize
		);
		const enforceSizeThreshold = mergeSizes(
			cacheGroupSource.enforceSizeThreshold,
			cacheGroupSource.enforce ? undefined : this.options.enforceSizeThreshold
		);
		const cacheGroup = {
			key: cacheGroupSource.key,
			priority: cacheGroupSource.priority || 0,
			chunksFilter: cacheGroupSource.chunksFilter || this.options.chunksFilter,
			minSize,
			minSizeReduction,
			minRemainingSize,
			enforceSizeThreshold,
			maxAsyncSize: mergeSizes(
				cacheGroupSource.maxAsyncSize,
				cacheGroupSource.enforce ? undefined : this.options.maxAsyncSize
			),
			maxInitialSize: mergeSizes(
				cacheGroupSource.maxInitialSize,
				cacheGroupSource.enforce ? undefined : this.options.maxInitialSize
			),
			minChunks:
				cacheGroupSource.minChunks !== undefined
					? cacheGroupSource.minChunks
					: cacheGroupSource.enforce
						? 1
						: this.options.minChunks,
			maxAsyncRequests:
				cacheGroupSource.maxAsyncRequests !== undefined
					? cacheGroupSource.maxAsyncRequests
					: cacheGroupSource.enforce
						? Infinity
						: this.options.maxAsyncRequests,
			maxInitialRequests:
				cacheGroupSource.maxInitialRequests !== undefined
					? cacheGroupSource.maxInitialRequests
					: cacheGroupSource.enforce
						? Infinity
						: this.options.maxInitialRequests,
			getName:
				cacheGroupSource.getName !== undefined
					? cacheGroupSource.getName
					: this.options.getName,
			usedExports:
				cacheGroupSource.usedExports !== undefined
					? cacheGroupSource.usedExports
					: this.options.usedExports,
			filename:
				cacheGroupSource.filename !== undefined
					? cacheGroupSource.filename
					: this.options.filename,
			automaticNameDelimiter:
				cacheGroupSource.automaticNameDelimiter !== undefined
					? cacheGroupSource.automaticNameDelimiter
					: this.options.automaticNameDelimiter,
			idHint:
				cacheGroupSource.idHint !== undefined
					? cacheGroupSource.idHint
					: cacheGroupSource.key,
			reuseExistingChunk: cacheGroupSource.reuseExistingChunk || false,
			_validateSize: hasNonZeroSizes(minSize),
			_validateRemainingSize: hasNonZeroSizes(minRemainingSize),
			_minSizeForMaxSize: mergeSizes(
				cacheGroupSource.minSize,
				this.options.minSize
			),
			_conditionalEnforce: hasNonZeroSizes(enforceSizeThreshold)
		};
		this._cacheGroupCache.set(cacheGroupSource, cacheGroup);
		return cacheGroup;
	}

	/**
	 * Apply the plugin
	 * @param {Compiler} compiler the compiler instance
	 * @returns {void}
	 */
	apply(compiler) {
		const cachedMakePathsRelative = makePathsRelative.bindContextCache(
			compiler.context,
			compiler.root
		);
		compiler.hooks.thisCompilation.tap("SplitChunksPlugin", compilation => {
			const logger = compilation.getLogger("webpack.SplitChunksPlugin");
			let alreadyOptimized = false;
			compilation.hooks.unseal.tap("SplitChunksPlugin", () => {
				alreadyOptimized = false;
			});
			compilation.hooks.optimizeChunks.tap(
				{
					name: "SplitChunksPlugin",
					stage: STAGE_ADVANCED
				},
				chunks => {
					if (alreadyOptimized) return;
					alreadyOptimized = true;
					logger.time("prepare");
					const chunkGraph = compilation.chunkGraph;
					const moduleGraph = compilation.moduleGraph;
					// Give each selected chunk an index (to create strings from chunks)
					/** @type {Map<Chunk, bigint>} */
					const chunkIndexMap = new Map();
					const ZERO = BigInt("0");
					const ONE = BigInt("1");
					const START = ONE << BigInt("31");
					let index = START;
					for (const chunk of chunks) {
						chunkIndexMap.set(
							chunk,
							index | BigInt((Math.random() * 0x7fffffff) | 0)
						);
						index = index << ONE;
					}
					/**
					 * @param {Iterable<Chunk>} chunks list of chunks
					 * @returns {bigint | Chunk} key of the chunks
					 */
					const getKey = chunks => {
						const iterator = chunks[Symbol.iterator]();
						let result = iterator.next();
						if (result.done) return ZERO;
						const first = result.value;
						result = iterator.next();
						if (result.done) return first;
						let key =
							chunkIndexMap.get(first) | chunkIndexMap.get(result.value);
						while (!(result = iterator.next()).done) {
							const raw = chunkIndexMap.get(result.value);
							key = key ^ raw;
						}
						return key;
					};
					/**
					 * @param {bigint | Chunk} key key of the chunks
					 * @returns {string} stringified key
					 */
					const keyToString = key => {
						if (typeof key === "bigint") return key.toString(16);
						return chunkIndexMap.get(key).toString(16);
					};

					const getChunkSetsInGraph = memoize(() => {
						/** @type {Map<bigint, Set<Chunk>>} */
						const chunkSetsInGraph = new Map();
						/** @type {Set<Chunk>} */
						const singleChunkSets = new Set();
						for (const module of compilation.modules) {
							const chunks = chunkGraph.getModuleChunksIterable(module);
							const chunksKey = getKey(chunks);
							if (typeof chunksKey === "bigint") {
								if (!chunkSetsInGraph.has(chunksKey)) {
									chunkSetsInGraph.set(chunksKey, new Set(chunks));
								}
							} else {
								singleChunkSets.add(chunksKey);
							}
						}
						return { chunkSetsInGraph, singleChunkSets };
					});

					/**
					 * @param {Module} module the module
					 * @returns {Iterable<Chunk[]>} groups of chunks with equal exports
					 */
					const groupChunksByExports = module => {
						const exportsInfo = moduleGraph.getExportsInfo(module);
						const groupedByUsedExports = new Map();
						for (const chunk of chunkGraph.getModuleChunksIterable(module)) {
							const key = exportsInfo.getUsageKey(chunk.runtime);
							const list = groupedByUsedExports.get(key);
							if (list !== undefined) {
								list.push(chunk);
							} else {
								groupedByUsedExports.set(key, [chunk]);
							}
						}
						return groupedByUsedExports.values();
					};

					/** @type {Map<Module, Iterable<Chunk[]>>} */
					const groupedByExportsMap = new Map();

					const getExportsChunkSetsInGraph = memoize(() => {
						/** @type {Map<bigint, Set<Chunk>>} */
						const chunkSetsInGraph = new Map();
						/** @type {Set<Chunk>} */
						const singleChunkSets = new Set();
						for (const module of compilation.modules) {
							const groupedChunks = Array.from(groupChunksByExports(module));
							groupedByExportsMap.set(module, groupedChunks);
							for (const chunks of groupedChunks) {
								if (chunks.length === 1) {
									singleChunkSets.add(chunks[0]);
								} else {
									const chunksKey = /** @type {bigint} */ (getKey(chunks));
									if (!chunkSetsInGraph.has(chunksKey)) {
										chunkSetsInGraph.set(chunksKey, new Set(chunks));
									}
								}
							}
						}
						return { chunkSetsInGraph, singleChunkSets };
					});

					// group these set of chunks by count
					// to allow to check less sets via isSubset
					// (only smaller sets can be subset)
					/**
					 * @param {IterableIterator<Set<Chunk>>} chunkSets set of sets of chunks
					 * @returns {Map<number, Array<Set<Chunk>>>} map of sets of chunks by count
					 */
					const groupChunkSetsByCount = chunkSets => {
						/** @type {Map<number, Array<Set<Chunk>>>} */
						const chunkSetsByCount = new Map();
						for (const chunksSet of chunkSets) {
							const count = chunksSet.size;
							let array = chunkSetsByCount.get(count);
							if (array === undefined) {
								array = [];
								chunkSetsByCount.set(count, array);
							}
							array.push(chunksSet);
						}
						return chunkSetsByCount;
					};
					const getChunkSetsByCount = memoize(() =>
						groupChunkSetsByCount(
							getChunkSetsInGraph().chunkSetsInGraph.values()
						)
					);
					const getExportsChunkSetsByCount = memoize(() =>
						groupChunkSetsByCount(
							getExportsChunkSetsInGraph().chunkSetsInGraph.values()
						)
					);

					// Create a list of possible combinations
					const createGetCombinations = (
						chunkSets,
						singleChunkSets,
						chunkSetsByCount
					) => {
						/** @type {Map<bigint | Chunk, (Set<Chunk> | Chunk)[]>} */
						const combinationsCache = new Map();

						return key => {
							const cacheEntry = combinationsCache.get(key);
							if (cacheEntry !== undefined) return cacheEntry;
							if (key instanceof Chunk) {
								const result = [key];
								combinationsCache.set(key, result);
								return result;
							}
							const chunksSet = chunkSets.get(key);
							/** @type {(Set<Chunk> | Chunk)[]} */
							const array = [chunksSet];
							for (const [count, setArray] of chunkSetsByCount) {
								// "equal" is not needed because they would have been merge in the first step
								if (count < chunksSet.size) {
									for (const set of setArray) {
										if (isSubset(chunksSet, set)) {
											array.push(set);
										}
									}
								}
							}
							for (const chunk of singleChunkSets) {
								if (chunksSet.has(chunk)) {
									array.push(chunk);
								}
							}
							combinationsCache.set(key, array);
							return array;
						};
					};

					const getCombinationsFactory = memoize(() => {
						const { chunkSetsInGraph, singleChunkSets } = getChunkSetsInGraph();
						return createGetCombinations(
							chunkSetsInGraph,
							singleChunkSets,
							getChunkSetsByCount()
						);
					});
					const getCombinations = key => getCombinationsFactory()(key);

					const getExportsCombinationsFactory = memoize(() => {
						const { chunkSetsInGraph, singleChunkSets } =
							getExportsChunkSetsInGraph();
						return createGetCombinations(
							chunkSetsInGraph,
							singleChunkSets,
							getExportsChunkSetsByCount()
						);
					});
					const getExportsCombinations = key =>
						getExportsCombinationsFactory()(key);

					/**
					 * @typedef {object} SelectedChunksResult
					 * @property {Chunk[]} chunks the list of chunks
					 * @property {bigint | Chunk} key a key of the list
					 */

					/** @type {WeakMap<Set<Chunk> | Chunk, WeakMap<ChunkFilterFunction, SelectedChunksResult>>} */
					const selectedChunksCacheByChunksSet = new WeakMap();

					/**
					 * get list and key by applying the filter function to the list
					 * It is cached for performance reasons
					 * @param {Set<Chunk> | Chunk} chunks list of chunks
					 * @param {ChunkFilterFunction} chunkFilter filter function for chunks
					 * @returns {SelectedChunksResult} list and key
					 */
					const getSelectedChunks = (chunks, chunkFilter) => {
						let entry = selectedChunksCacheByChunksSet.get(chunks);
						if (entry === undefined) {
							entry = new WeakMap();
							selectedChunksCacheByChunksSet.set(chunks, entry);
						}
						let entry2 =
							/** @type {SelectedChunksResult} */
							(entry.get(chunkFilter));
						if (entry2 === undefined) {
							/** @type {Chunk[]} */
							const selectedChunks = [];
							if (chunks instanceof Chunk) {
								if (chunkFilter(chunks)) selectedChunks.push(chunks);
							} else {
								for (const chunk of chunks) {
									if (chunkFilter(chunk)) selectedChunks.push(chunk);
								}
							}
							entry2 = {
								chunks: selectedChunks,
								key: getKey(selectedChunks)
							};
							entry.set(chunkFilter, entry2);
						}
						return entry2;
					};

					/** @type {Map<string, boolean>} */
					const alreadyValidatedParents = new Map();
					/** @type {Set<string>} */
					const alreadyReportedErrors = new Set();

					// Map a list of chunks to a list of modules
					// For the key the chunk "index" is used, the value is a SortableSet of modules
					/** @type {Map<string, ChunksInfoItem>} */
					const chunksInfoMap = new Map();

					/**
					 * @param {CacheGroup} cacheGroup the current cache group
					 * @param {number} cacheGroupIndex the index of the cache group of ordering
					 * @param {Chunk[]} selectedChunks chunks selected for this module
					 * @param {bigint | Chunk} selectedChunksKey a key of selectedChunks
					 * @param {Module} module the current module
					 * @returns {void}
					 */
					const addModuleToChunksInfoMap = (
						cacheGroup,
						cacheGroupIndex,
						selectedChunks,
						selectedChunksKey,
						module
					) => {
						// Break if minimum number of chunks is not reached
						if (selectedChunks.length < cacheGroup.minChunks) return;
						// Determine name for split chunk
						const name =
							/** @type {string} */
							(cacheGroup.getName(module, selectedChunks, cacheGroup.key));
						// Check if the name is ok
						const existingChunk = compilation.namedChunks.get(name);
						if (existingChunk) {
							const parentValidationKey = `${name}|${
								typeof selectedChunksKey === "bigint"
									? selectedChunksKey
									: selectedChunksKey.debugId
							}`;
							const valid = alreadyValidatedParents.get(parentValidationKey);
							if (valid === false) return;
							if (valid === undefined) {
								// Module can only be moved into the existing chunk if the existing chunk
								// is a parent of all selected chunks
								let isInAllParents = true;
								/** @type {Set<ChunkGroup>} */
								const queue = new Set();
								for (const chunk of selectedChunks) {
									for (const group of chunk.groupsIterable) {
										queue.add(group);
									}
								}
								for (const group of queue) {
									if (existingChunk.isInGroup(group)) continue;
									let hasParent = false;
									for (const parent of group.parentsIterable) {
										hasParent = true;
										queue.add(parent);
									}
									if (!hasParent) {
										isInAllParents = false;
									}
								}
								const valid = isInAllParents;
								alreadyValidatedParents.set(parentValidationKey, valid);
								if (!valid) {
									if (!alreadyReportedErrors.has(name)) {
										alreadyReportedErrors.add(name);
										compilation.errors.push(
											new WebpackError(
												"SplitChunksPlugin\n" +
													`Cache group "${cacheGroup.key}" conflicts with existing chunk.\n` +
													`Both have the same name "${name}" and existing chunk is not a parent of the selected modules.\n` +
													"Use a different name for the cache group or make sure that the existing chunk is a parent (e. g. via dependOn).\n" +
													'HINT: You can omit "name" to automatically create a name.\n' +
													"BREAKING CHANGE: webpack < 5 used to allow to use an entrypoint as splitChunk. " +
													"This is no longer allowed when the entrypoint is not a parent of the selected modules.\n" +
													"Remove this entrypoint and add modules to cache group's 'test' instead. " +
													"If you need modules to be evaluated on startup, add them to the existing entrypoints (make them arrays). " +
													"See migration guide of more info."
											)
										);
									}
									return;
								}
							}
						}
						// Create key for maps
						// When it has a name we use the name as key
						// Otherwise we create the key from chunks and cache group key
						// This automatically merges equal names
						const key =
							cacheGroup.key +
							(name
								? ` name:${name}`
								: ` chunks:${keyToString(selectedChunksKey)}`);
						// Add module to maps
						let info = /** @type {ChunksInfoItem} */ (chunksInfoMap.get(key));
						if (info === undefined) {
							chunksInfoMap.set(
								key,
								(info = {
									modules: new SortableSet(
										undefined,
										compareModulesByIdentifier
									),
									cacheGroup,
									cacheGroupIndex,
									name,
									sizes: {},
									chunks: new Set(),
									reusableChunks: new Set(),
									chunksKeys: new Set()
								})
							);
						}
						const oldSize = info.modules.size;
						info.modules.add(module);
						if (info.modules.size !== oldSize) {
							for (const type of module.getSourceTypes()) {
								info.sizes[type] = (info.sizes[type] || 0) + module.size(type);
							}
						}
						const oldChunksKeysSize = info.chunksKeys.size;
						info.chunksKeys.add(selectedChunksKey);
						if (oldChunksKeysSize !== info.chunksKeys.size) {
							for (const chunk of selectedChunks) {
								info.chunks.add(chunk);
							}
						}
					};

					const context = {
						moduleGraph,
						chunkGraph
					};

					logger.timeEnd("prepare");

					logger.time("modules");

					// Walk through all modules
					for (const module of compilation.modules) {
						// Get cache group
						let cacheGroups = this.options.getCacheGroups(module, context);
						if (!Array.isArray(cacheGroups) || cacheGroups.length === 0) {
							continue;
						}

						// Prepare some values (usedExports = false)
						const getCombs = memoize(() => {
							const chunks = chunkGraph.getModuleChunksIterable(module);
							const chunksKey = getKey(chunks);
							return getCombinations(chunksKey);
						});

						// Prepare some values (usedExports = true)
						const getCombsByUsedExports = memoize(() => {
							// fill the groupedByExportsMap
							getExportsChunkSetsInGraph();
							/** @type {Set<Set<Chunk> | Chunk>} */
							const set = new Set();
							const groupedByUsedExports =
								/** @type {Iterable<Chunk[]>} */
								(groupedByExportsMap.get(module));
							for (const chunks of groupedByUsedExports) {
								const chunksKey = getKey(chunks);
								for (const comb of getExportsCombinations(chunksKey))
									set.add(comb);
							}
							return set;
						});

						let cacheGroupIndex = 0;
						for (const cacheGroupSource of cacheGroups) {
							const cacheGroup = this._getCacheGroup(cacheGroupSource);

							const combs = cacheGroup.usedExports
								? getCombsByUsedExports()
								: getCombs();
							// For all combination of chunk selection
							for (const chunkCombination of combs) {
								// Break if minimum number of chunks is not reached
								const count =
									chunkCombination instanceof Chunk ? 1 : chunkCombination.size;
								if (count < cacheGroup.minChunks) continue;
								// Select chunks by configuration
								const { chunks: selectedChunks, key: selectedChunksKey } =
									getSelectedChunks(
										chunkCombination,
										/** @type {ChunkFilterFunction} */ (cacheGroup.chunksFilter)
									);

								addModuleToChunksInfoMap(
									cacheGroup,
									cacheGroupIndex,
									selectedChunks,
									selectedChunksKey,
									module
								);
							}
							cacheGroupIndex++;
						}
					}

					logger.timeEnd("modules");

					logger.time("queue");

					/**
					 * @param {ChunksInfoItem} info entry
					 * @param {string[]} sourceTypes source types to be removed
					 */
					const removeModulesWithSourceType = (info, sourceTypes) => {
						for (const module of info.modules) {
							const types = module.getSourceTypes();
							if (sourceTypes.some(type => types.has(type))) {
								info.modules.delete(module);
								for (const type of types) {
									info.sizes[type] -= module.size(type);
								}
							}
						}
					};

					/**
					 * @param {ChunksInfoItem} info entry
					 * @returns {boolean} true, if entry become empty
					 */
					const removeMinSizeViolatingModules = info => {
						if (!info.cacheGroup._validateSize) return false;
						const violatingSizes = getViolatingMinSizes(
							info.sizes,
							info.cacheGroup.minSize
						);
						if (violatingSizes === undefined) return false;
						removeModulesWithSourceType(info, violatingSizes);
						return info.modules.size === 0;
					};

					// Filter items were size < minSize
					for (const [key, info] of chunksInfoMap) {
						if (removeMinSizeViolatingModules(info)) {
							chunksInfoMap.delete(key);
						} else if (
							!checkMinSizeReduction(
								info.sizes,
								info.cacheGroup.minSizeReduction,
								info.chunks.size
							)
						) {
							chunksInfoMap.delete(key);
						}
					}

					/**
					 * @typedef {object} MaxSizeQueueItem
					 * @property {SplitChunksSizes} minSize
					 * @property {SplitChunksSizes} maxAsyncSize
					 * @property {SplitChunksSizes} maxInitialSize
					 * @property {string} automaticNameDelimiter
					 * @property {string[]} keys
					 */

					/** @type {Map<Chunk, MaxSizeQueueItem>} */
					const maxSizeQueueMap = new Map();

					while (chunksInfoMap.size > 0) {
						// Find best matching entry
						let bestEntryKey;
						let bestEntry;
						for (const pair of chunksInfoMap) {
							const key = pair[0];
							const info = pair[1];
							if (
								bestEntry === undefined ||
								compareEntries(bestEntry, info) < 0
							) {
								bestEntry = info;
								bestEntryKey = key;
							}
						}

						const item = /** @type {ChunksInfoItem} */ (bestEntry);
						chunksInfoMap.delete(/** @type {string} */ (bestEntryKey));

						/** @type {Chunk["name"] | undefined} */
						let chunkName = item.name;
						// Variable for the new chunk (lazy created)
						/** @type {Chunk | undefined} */
						let newChunk;
						// When no chunk name, check if we can reuse a chunk instead of creating a new one
						let isExistingChunk = false;
						let isReusedWithAllModules = false;
						if (chunkName) {
							const chunkByName = compilation.namedChunks.get(chunkName);
							if (chunkByName !== undefined) {
								newChunk = chunkByName;
								const oldSize = item.chunks.size;
								item.chunks.delete(newChunk);
								isExistingChunk = item.chunks.size !== oldSize;
							}
						} else if (item.cacheGroup.reuseExistingChunk) {
							outer: for (const chunk of item.chunks) {
								if (
									chunkGraph.getNumberOfChunkModules(chunk) !==
									item.modules.size
								) {
									continue;
								}
								if (
									item.chunks.size > 1 &&
									chunkGraph.getNumberOfEntryModules(chunk) > 0
								) {
									continue;
								}
								for (const module of item.modules) {
									if (!chunkGraph.isModuleInChunk(module, chunk)) {
										continue outer;
									}
								}
								if (!newChunk || !newChunk.name) {
									newChunk = chunk;
								} else if (
									chunk.name &&
									chunk.name.length < newChunk.name.length
								) {
									newChunk = chunk;
								} else if (
									chunk.name &&
									chunk.name.length === newChunk.name.length &&
									chunk.name < newChunk.name
								) {
									newChunk = chunk;
								}
							}
							if (newChunk) {
								item.chunks.delete(newChunk);
								chunkName = undefined;
								isExistingChunk = true;
								isReusedWithAllModules = true;
							}
						}

						const enforced =
							item.cacheGroup._conditionalEnforce &&
							checkMinSize(item.sizes, item.cacheGroup.enforceSizeThreshold);

						const usedChunks = new Set(item.chunks);

						// Check if maxRequests condition can be fulfilled
						if (
							!enforced &&
							(Number.isFinite(item.cacheGroup.maxInitialRequests) ||
								Number.isFinite(item.cacheGroup.maxAsyncRequests))
						) {
							for (const chunk of usedChunks) {
								// respect max requests
								const maxRequests = /** @type {number} */ (
									chunk.isOnlyInitial()
										? item.cacheGroup.maxInitialRequests
										: chunk.canBeInitial()
											? Math.min(
													/** @type {number} */
													(item.cacheGroup.maxInitialRequests),
													/** @type {number} */
													(item.cacheGroup.maxAsyncRequests)
												)
											: item.cacheGroup.maxAsyncRequests
								);
								if (
									isFinite(maxRequests) &&
									getRequests(chunk) >= maxRequests
								) {
									usedChunks.delete(chunk);
								}
							}
						}

						outer: for (const chunk of usedChunks) {
							for (const module of item.modules) {
								if (chunkGraph.isModuleInChunk(module, chunk)) continue outer;
							}
							usedChunks.delete(chunk);
						}

						// Were some (invalid) chunks removed from usedChunks?
						// => readd all modules to the queue, as things could have been changed
						if (usedChunks.size < item.chunks.size) {
							if (isExistingChunk)
								usedChunks.add(/** @type {Chunk} */ (newChunk));
							if (
								/** @type {number} */ (usedChunks.size) >=
								/** @type {number} */ (item.cacheGroup.minChunks)
							) {
								const chunksArr = Array.from(usedChunks);
								for (const module of item.modules) {
									addModuleToChunksInfoMap(
										item.cacheGroup,
										item.cacheGroupIndex,
										chunksArr,
										getKey(usedChunks),
										module
									);
								}
							}
							continue;
						}

						// Validate minRemainingSize constraint when a single chunk is left over
						if (
							!enforced &&
							item.cacheGroup._validateRemainingSize &&
							usedChunks.size === 1
						) {
							const [chunk] = usedChunks;
							let chunkSizes = Object.create(null);
							for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
								if (!item.modules.has(module)) {
									for (const type of module.getSourceTypes()) {
										chunkSizes[type] =
											(chunkSizes[type] || 0) + module.size(type);
									}
								}
							}
							const violatingSizes = getViolatingMinSizes(
								chunkSizes,
								item.cacheGroup.minRemainingSize
							);
							if (violatingSizes !== undefined) {
								const oldModulesSize = item.modules.size;
								removeModulesWithSourceType(item, violatingSizes);
								if (
									item.modules.size > 0 &&
									item.modules.size !== oldModulesSize
								) {
									// queue this item again to be processed again
									// without violating modules
									chunksInfoMap.set(/** @type {string} */ (bestEntryKey), item);
								}
								continue;
							}
						}

						// Create the new chunk if not reusing one
						if (newChunk === undefined) {
							newChunk = compilation.addChunk(chunkName);
						}
						// Walk through all chunks
						for (const chunk of usedChunks) {
							// Add graph connections for splitted chunk
							chunk.split(newChunk);
						}

						// Add a note to the chunk
						newChunk.chunkReason =
							(newChunk.chunkReason ? newChunk.chunkReason + ", " : "") +
							(isReusedWithAllModules
								? "reused as split chunk"
								: "split chunk");
						if (item.cacheGroup.key) {
							newChunk.chunkReason += ` (cache group: ${item.cacheGroup.key})`;
						}
						if (chunkName) {
							newChunk.chunkReason += ` (name: ${chunkName})`;
						}
						if (item.cacheGroup.filename) {
							newChunk.filenameTemplate = item.cacheGroup.filename;
						}
						if (item.cacheGroup.idHint) {
							newChunk.idNameHints.add(item.cacheGroup.idHint);
						}
						if (!isReusedWithAllModules) {
							// Add all modules to the new chunk
							for (const module of item.modules) {
								if (!module.chunkCondition(newChunk, compilation)) continue;
								// Add module to new chunk
								chunkGraph.connectChunkAndModule(newChunk, module);
								// Remove module from used chunks
								for (const chunk of usedChunks) {
									chunkGraph.disconnectChunkAndModule(chunk, module);
								}
							}
						} else {
							// Remove all modules from used chunks
							for (const module of item.modules) {
								for (const chunk of usedChunks) {
									chunkGraph.disconnectChunkAndModule(chunk, module);
								}
							}
						}

						if (
							Object.keys(item.cacheGroup.maxAsyncSize).length > 0 ||
							Object.keys(item.cacheGroup.maxInitialSize).length > 0
						) {
							const oldMaxSizeSettings = maxSizeQueueMap.get(newChunk);
							maxSizeQueueMap.set(newChunk, {
								minSize: oldMaxSizeSettings
									? combineSizes(
											oldMaxSizeSettings.minSize,
											item.cacheGroup._minSizeForMaxSize,
											Math.max
										)
									: item.cacheGroup.minSize,
								maxAsyncSize: oldMaxSizeSettings
									? combineSizes(
											oldMaxSizeSettings.maxAsyncSize,
											item.cacheGroup.maxAsyncSize,
											Math.min
										)
									: item.cacheGroup.maxAsyncSize,
								maxInitialSize: oldMaxSizeSettings
									? combineSizes(
											oldMaxSizeSettings.maxInitialSize,
											item.cacheGroup.maxInitialSize,
											Math.min
										)
									: item.cacheGroup.maxInitialSize,
								automaticNameDelimiter: item.cacheGroup.automaticNameDelimiter,
								keys: oldMaxSizeSettings
									? oldMaxSizeSettings.keys.concat(item.cacheGroup.key)
									: [item.cacheGroup.key]
							});
						}

						// remove all modules from other entries and update size
						for (const [key, info] of chunksInfoMap) {
							if (isOverlap(info.chunks, usedChunks)) {
								// update modules and total size
								// may remove it from the map when < minSize
								let updated = false;
								for (const module of item.modules) {
									if (info.modules.has(module)) {
										// remove module
										info.modules.delete(module);
										// update size
										for (const key of module.getSourceTypes()) {
											info.sizes[key] -= module.size(key);
										}
										updated = true;
									}
								}
								if (updated) {
									if (info.modules.size === 0) {
										chunksInfoMap.delete(key);
										continue;
									}
									if (
										removeMinSizeViolatingModules(info) ||
										!checkMinSizeReduction(
											info.sizes,
											info.cacheGroup.minSizeReduction,
											info.chunks.size
										)
									) {
										chunksInfoMap.delete(key);
										continue;
									}
								}
							}
						}
					}

					logger.timeEnd("queue");

					logger.time("maxSize");

					/** @type {Set<string>} */
					const incorrectMinMaxSizeSet = new Set();

					const { outputOptions } = compilation;

					// Make sure that maxSize is fulfilled
					const { fallbackCacheGroup } = this.options;
					for (const chunk of Array.from(compilation.chunks)) {
						const chunkConfig = maxSizeQueueMap.get(chunk);
						const {
							minSize,
							maxAsyncSize,
							maxInitialSize,
							automaticNameDelimiter
						} = chunkConfig || fallbackCacheGroup;
						if (!chunkConfig && !fallbackCacheGroup.chunksFilter(chunk))
							continue;
						/** @type {SplitChunksSizes} */
						let maxSize;
						if (chunk.isOnlyInitial()) {
							maxSize = maxInitialSize;
						} else if (chunk.canBeInitial()) {
							maxSize = combineSizes(maxAsyncSize, maxInitialSize, Math.min);
						} else {
							maxSize = maxAsyncSize;
						}
						if (Object.keys(maxSize).length === 0) {
							continue;
						}
						for (const key of Object.keys(maxSize)) {
							const maxSizeValue = maxSize[key];
							const minSizeValue = minSize[key];
							if (
								typeof minSizeValue === "number" &&
								minSizeValue > maxSizeValue
							) {
								const keys = chunkConfig && chunkConfig.keys;
								const warningKey = `${
									keys && keys.join()
								} ${minSizeValue} ${maxSizeValue}`;
								if (!incorrectMinMaxSizeSet.has(warningKey)) {
									incorrectMinMaxSizeSet.add(warningKey);
									compilation.warnings.push(
										new MinMaxSizeWarning(keys, minSizeValue, maxSizeValue)
									);
								}
							}
						}
						const results = deterministicGroupingForModules({
							minSize,
							maxSize: mapObject(maxSize, (value, key) => {
								const minSizeValue = minSize[key];
								return typeof minSizeValue === "number"
									? Math.max(value, minSizeValue)
									: value;
							}),
							items: chunkGraph.getChunkModulesIterable(chunk),
							getKey(module) {
								const cache = getKeyCache.get(module);
								if (cache !== undefined) return cache;
								const ident = cachedMakePathsRelative(module.identifier());
								const nameForCondition =
									module.nameForCondition && module.nameForCondition();
								const name = nameForCondition
									? cachedMakePathsRelative(nameForCondition)
									: ident.replace(/^.*!|\?[^?!]*$/g, "");
								const fullKey =
									name +
									automaticNameDelimiter +
									hashFilename(ident, outputOptions);
								const key = requestToId(fullKey);
								getKeyCache.set(module, key);
								return key;
							},
							getSize(module) {
								const size = Object.create(null);
								for (const key of module.getSourceTypes()) {
									size[key] = module.size(key);
								}
								return size;
							}
						});
						if (results.length <= 1) {
							continue;
						}
						for (let i = 0; i < results.length; i++) {
							const group = results[i];
							const key = this.options.hidePathInfo
								? hashFilename(group.key, outputOptions)
								: group.key;
							let name = chunk.name
								? chunk.name + automaticNameDelimiter + key
								: null;
							if (name && name.length > 100) {
								name =
									name.slice(0, 100) +
									automaticNameDelimiter +
									hashFilename(name, outputOptions);
							}
							if (i !== results.length - 1) {
								const newPart = compilation.addChunk(
									/** @type {Chunk["name"]} */ (name)
								);
								chunk.split(newPart);
								newPart.chunkReason = chunk.chunkReason;
								// Add all modules to the new chunk
								for (const module of group.items) {
									if (!module.chunkCondition(newPart, compilation)) {
										continue;
									}
									// Add module to new chunk
									chunkGraph.connectChunkAndModule(newPart, module);
									// Remove module from used chunks
									chunkGraph.disconnectChunkAndModule(chunk, module);
								}
							} else {
								// change the chunk to be a part
								chunk.name = /** @type {Chunk["name"]} */ (name);
							}
						}
					}
					logger.timeEnd("maxSize");
				}
			);
		});
	}
};


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Sean Larkin @thelarkinn
*/

"use strict";

const { formatSize } = require("../SizeFormatHelpers");
const WebpackError = require("../WebpackError");

/** @typedef {import("./SizeLimitsPlugin").AssetDetails} AssetDetails */

module.exports = class AssetsOverSizeLimitWarning extends WebpackError {
	/**
	 * @param {AssetDetails[]} assetsOverSizeLimit the assets
	 * @param {number} assetLimit the size limit
	 */
	constructor(assetsOverSizeLimit, assetLimit) {
		const assetLists = assetsOverSizeLimit
			.map(asset => `\n  ${asset.name} (${formatSize(asset.size)})`)
			.join("");

		super(`asset size limit: The following asset(s) exceed the recommended size limit (${formatSize(
			assetLimit
		)}).
This can impact web performance.
Assets: ${assetLists}`);

		this.name = "AssetsOverSizeLimitWarning";
		this.assets = assetsOverSizeLimit;
	}
};

license: mit


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Sean Larkin @thelarkinn
*/

"use strict";

const { formatSize } = require("../SizeFormatHelpers");
const WebpackError = require("../WebpackError");

/** @typedef {import("./SizeLimitsPlugin").EntrypointDetails} EntrypointDetails */

module.exports = class EntrypointsOverSizeLimitWarning extends WebpackError {
	/**
	 * @param {EntrypointDetails[]} entrypoints the entrypoints
	 * @param {number} entrypointLimit the size limit
	 */
	constructor(entrypoints, entrypointLimit) {
		const entrypointList = entrypoints
			.map(
				entrypoint =>
					`\n  ${entrypoint.name} (${formatSize(
						entrypoint.size
					)})\n${entrypoint.files.map(asset => `      ${asset}`).join("\n")}`
			)
			.join("");
		super(`entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (${formatSize(
			entrypointLimit
		)}). This can impact web performance.
Entrypoints:${entrypointList}\n`);

		this.name = "EntrypointsOverSizeLimitWarning";
		this.entrypoints = entrypoints;
	}
};


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Sean Larkin @thelarkinn
*/

"use strict";

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

module.exports = class NoAsyncChunksWarning extends WebpackError {
	constructor() {
		super(
			"webpack performance recommendations: \n" +
				"You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.\n" +
				"For more info visit https://webpack.js.org/guides/code-splitting/"
		);

		this.name = "NoAsyncChunksWarning";
	}
};


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Sean Larkin @thelarkinn
*/

"use strict";

const { find } = require("../util/SetHelpers");
const AssetsOverSizeLimitWarning = require("./AssetsOverSizeLimitWarning");
const EntrypointsOverSizeLimitWarning = require("./EntrypointsOverSizeLimitWarning");
const NoAsyncChunksWarning = require("./NoAsyncChunksWarning");

/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../../declarations/WebpackOptions").PerformanceOptions} PerformanceOptions */
/** @typedef {import("../ChunkGroup")} ChunkGroup */
/** @typedef {import("../Compilation").Asset} Asset */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Entrypoint")} Entrypoint */
/** @typedef {import("../WebpackError")} WebpackError */

/**
 * @typedef {object} AssetDetails
 * @property {string} name
 * @property {number} size
 */

/**
 * @typedef {object} EntrypointDetails
 * @property {string} name
 * @property {number} size
 * @property {string[]} files
 */

const isOverSizeLimitSet = new WeakSet();

/**
 * @param {Asset["name"]} name the name
 * @param {Asset["source"]} source the source
 * @param {Asset["info"]} info the info
 * @returns {boolean} result
 */
const excludeSourceMap = (name, source, info) => !info.development;

module.exports = class SizeLimitsPlugin {
	/**
	 * @param {PerformanceOptions} options the plugin options
	 */
	constructor(options) {
		this.hints = options.hints;
		this.maxAssetSize = options.maxAssetSize;
		this.maxEntrypointSize = options.maxEntrypointSize;
		this.assetFilter = options.assetFilter;
	}

	/**
	 * @param {ChunkGroup | Source} thing the resource to test
	 * @returns {boolean} true if over the limit
	 */
	static isOverSizeLimit(thing) {
		return isOverSizeLimitSet.has(thing);
	}

	/**
	 * Apply the plugin
	 * @param {Compiler} compiler the compiler instance
	 * @returns {void}
	 */
	apply(compiler) {
		const entrypointSizeLimit = this.maxEntrypointSize;
		const assetSizeLimit = this.maxAssetSize;
		const hints = this.hints;
		const assetFilter = this.assetFilter || excludeSourceMap;

		compiler.hooks.afterEmit.tap("SizeLimitsPlugin", compilation => {
			/** @type {WebpackError[]} */
			const warnings = [];

			/**
			 * @param {Entrypoint} entrypoint an entrypoint
			 * @returns {number} the size of the entrypoint
			 */
			const getEntrypointSize = entrypoint => {
				let size = 0;
				for (const file of entrypoint.getFiles()) {
					const asset = compilation.getAsset(file);
					if (
						asset &&
						assetFilter(asset.name, asset.source, asset.info) &&
						asset.source
					) {
						size += asset.info.size || asset.source.size();
					}
				}
				return size;
			};

			/** @type {AssetDetails[]} */
			const assetsOverSizeLimit = [];
			for (const { name, source, info } of compilation.getAssets()) {
				if (!assetFilter(name, source, info) || !source) {
					continue;
				}

				const size = info.size || source.size();
				if (size > /** @type {number} */ (assetSizeLimit)) {
					assetsOverSizeLimit.push({
						name,
						size
					});
					isOverSizeLimitSet.add(source);
				}
			}

			/**
			 * @param {Asset["name"]} name the name
			 * @returns {boolean | undefined} result
			 */
			const fileFilter = name => {
				const asset = compilation.getAsset(name);
				return asset && assetFilter(asset.name, asset.source, asset.info);
			};

			/** @type {EntrypointDetails[]} */
			const entrypointsOverLimit = [];
			for (const [name, entry] of compilation.entrypoints) {
				const size = getEntrypointSize(entry);

				if (size > /** @type {number} */ (entrypointSizeLimit)) {
					entrypointsOverLimit.push({
						name: name,
						size: size,
						files: entry.getFiles().filter(fileFilter)
					});
					isOverSizeLimitSet.add(entry);
				}
			}

			if (hints) {
				// 1. Individual Chunk: Size < 250kb
				// 2. Collective Initial Chunks [entrypoint] (Each Set?): Size < 250kb
				// 3. No Async Chunks
				// if !1, then 2, if !2 return
				if (assetsOverSizeLimit.length > 0) {
					warnings.push(
						new AssetsOverSizeLimitWarning(
							assetsOverSizeLimit,
							/** @type {number} */ (assetSizeLimit)
						)
					);
				}
				if (entrypointsOverLimit.length > 0) {
					warnings.push(
						new EntrypointsOverSizeLimitWarning(
							entrypointsOverLimit,
							/** @type {number} */ (entrypointSizeLimit)
						)
					);
				}

				if (warnings.length > 0) {
					const someAsyncChunk = find(
						compilation.chunks,
						chunk => !chunk.canBeInitial()
					);

					if (!someAsyncChunk) {
						warnings.push(new NoAsyncChunksWarning());
					}

					if (hints === "error") {
						compilation.errors.push(...warnings);
					} else {
						compilation.warnings.push(...warnings);
					}
				}
			}
		});
	}
};


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
*/

"use strict";

const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");

/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */

class ChunkPrefetchFunctionRuntimeModule extends RuntimeModule {
	/**
	 * @param {string} childType TODO
	 * @param {string} runtimeFunction TODO
	 * @param {string} runtimeHandlers TODO
	 */
	constructor(childType, runtimeFunction, runtimeHandlers) {
		super(`chunk ${childType} function`);
		this.childType = childType;
		this.runtimeFunction = runtimeFunction;
		this.runtimeHandlers = runtimeHandlers;
	}

	/**
	 * @returns {string | null} runtime code
	 */
	generate() {
		const { runtimeFunction, runtimeHandlers } = this;
		const compilation = /** @type {Compilation} */ (this.compilation);
		const { runtimeTemplate } = compilation;
		return Template.asString([
			`${runtimeHandlers} = {};`,
			`${runtimeFunction} = ${runtimeTemplate.basicFunction("chunkId", [
				// map is shorter than forEach
				`Object.keys(${runtimeHandlers}).map(${runtimeTemplate.basicFunction(
					"key",
					`${runtimeHandlers}[key](chunkId);`
				)});`
			])}`
		]);
	}
}

module.exports = ChunkPrefetchFunctionRuntimeModule;


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

"use strict";

const RuntimeGlobals = require("../RuntimeGlobals");
const ChunkPrefetchFunctionRuntimeModule = require("./ChunkPrefetchFunctionRuntimeModule");
const ChunkPrefetchStartupRuntimeModule = require("./ChunkPrefetchStartupRuntimeModule");
const ChunkPrefetchTriggerRuntimeModule = require("./ChunkPrefetchTriggerRuntimeModule");
const ChunkPreloadTriggerRuntimeModule = require("./ChunkPreloadTriggerRuntimeModule");

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

class ChunkPrefetchPreloadPlugin {
	/**
	 * @param {Compiler} compiler the compiler
	 * @returns {void}
	 */
	apply(compiler) {
		compiler.hooks.compilation.tap(
			"ChunkPrefetchPreloadPlugin",
			compilation => {
				compilation.hooks.additionalChunkRuntimeRequirements.tap(
					"ChunkPrefetchPreloadPlugin",
					(chunk, set, { chunkGraph }) => {
						if (chunkGraph.getNumberOfEntryModules(chunk) === 0) return;
						const startupChildChunks = chunk.getChildrenOfTypeInOrder(
							chunkGraph,
							"prefetchOrder"
						);
						if (startupChildChunks) {
							set.add(RuntimeGlobals.prefetchChunk);
							set.add(RuntimeGlobals.onChunksLoaded);
							compilation.addRuntimeModule(
								chunk,
								new ChunkPrefetchStartupRuntimeModule(startupChildChunks)
							);
						}
					}
				);
				compilation.hooks.additionalTreeRuntimeRequirements.tap(
					"ChunkPrefetchPreloadPlugin",
					(chunk, set, { chunkGraph }) => {
						const chunkMap = chunk.getChildIdsByOrdersMap(chunkGraph);

						if (chunkMap.prefetch) {
							set.add(RuntimeGlobals.prefetchChunk);
							compilation.addRuntimeModule(
								chunk,
								new ChunkPrefetchTriggerRuntimeModule(chunkMap.prefetch)
							);
						}
						if (chunkMap.preload) {
							set.add(RuntimeGlobals.preloadChunk);
							compilation.addRuntimeModule(
								chunk,
								new ChunkPreloadTriggerRuntimeModule(chunkMap.preload)
							);
						}
					}
				);
				compilation.hooks.runtimeRequirementInTree
					.for(RuntimeGlobals.prefetchChunk)
					.tap("ChunkPrefetchPreloadPlugin", (chunk, set) => {
						compilation.addRuntimeModule(
							chunk,
							new ChunkPrefetchFunctionRuntimeModule(
								"prefetch",
								RuntimeGlobals.prefetchChunk,
								RuntimeGlobals.prefetchChunkHandlers
							)
						);
						set.add(RuntimeGlobals.prefetchChunkHandlers);
					});
				compilation.hooks.runtimeRequirementInTree
					.for(RuntimeGlobals.preloadChunk)
					.tap("ChunkPrefetchPreloadPlugin", (chunk, set) => {
						compilation.addRuntimeModule(
							chunk,
							new ChunkPrefetchFunctionRuntimeModule(
								"preload",
								RuntimeGlobals.preloadChunk,
								RuntimeGlobals.preloadChunkHandlers
							)
						);
						set.add(RuntimeGlobals.preloadChunkHandlers);
					});
			}
		);
	}
}

module.exports = ChunkPrefetchPreloadPlugin;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
*/

"use strict";

const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");

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

class ChunkPrefetchStartupRuntimeModule extends RuntimeModule {
	/**
	 * @param {{ onChunks: Chunk[], chunks: Set<Chunk> }[]} startupChunks chunk ids to trigger when chunks are loaded
	 */
	constructor(startupChunks) {
		super("startup prefetch", RuntimeModule.STAGE_TRIGGER);
		this.startupChunks = startupChunks;
	}

	/**
	 * @returns {string | null} runtime code
	 */
	generate() {
		const { startupChunks } = this;
		const compilation = /** @type {Compilation} */ (this.compilation);
		const chunk = /** @type {Chunk} */ (this.chunk);
		const { runtimeTemplate } = compilation;
		return Template.asString(
			startupChunks.map(
				({ onChunks, chunks }) =>
					`${RuntimeGlobals.onChunksLoaded}(0, ${JSON.stringify(
						// This need to include itself to delay execution after this chunk has been fully loaded
						onChunks.filter(c => c === chunk).map(c => c.id)
					)}, ${runtimeTemplate.basicFunction(
						"",
						chunks.size < 3
							? Array.from(
									chunks,
									c =>
										`${RuntimeGlobals.prefetchChunk}(${JSON.stringify(c.id)});`
								)
							: `${JSON.stringify(Array.from(chunks, c => c.id))}.map(${
									RuntimeGlobals.prefetchChunk
								});`
					)}, 5);`
			)
		);
	}
}

module.exports = ChunkPrefetchStartupRuntimeModule;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
*/

"use strict";

const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");

/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */

class ChunkPrefetchTriggerRuntimeModule extends RuntimeModule {
	/**
	 * @param {Record<string|number, (string|number)[]>} chunkMap map from chunk to
	 */
	constructor(chunkMap) {
		super(`chunk prefetch trigger`, RuntimeModule.STAGE_TRIGGER);
		this.chunkMap = chunkMap;
	}

	/**
	 * @returns {string | null} runtime code
	 */
	generate() {
		const { chunkMap } = this;
		const compilation = /** @type {Compilation} */ (this.compilation);
		const { runtimeTemplate } = compilation;
		const body = [
			"var chunks = chunkToChildrenMap[chunkId];",
			`Array.isArray(chunks) && chunks.map(${RuntimeGlobals.prefetchChunk});`
		];
		return Template.asString([
			Template.asString([
				`var chunkToChildrenMap = ${JSON.stringify(chunkMap, null, "\t")};`,
				`${
					RuntimeGlobals.ensureChunkHandlers
				}.prefetch = ${runtimeTemplate.expressionFunction(
					`Promise.all(promises).then(${runtimeTemplate.basicFunction(
						"",
						body
					)})`,
					"chunkId, promises"
				)};`
			])
		]);
	}
}

module.exports = ChunkPrefetchTriggerRuntimeModule;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
*/

"use strict";

const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");

/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */

class ChunkPreloadTriggerRuntimeModule extends RuntimeModule {
	/**
	 * @param {Record<string|number, (string|number)[]>} chunkMap map from chunk to chunks
	 */
	constructor(chunkMap) {
		super(`chunk preload trigger`, RuntimeModule.STAGE_TRIGGER);
		this.chunkMap = chunkMap;
	}

	/**
	 * @returns {string | null} runtime code
	 */
	generate() {
		const { chunkMap } = this;
		const compilation = /** @type {Compilation} */ (this.compilation);
		const { runtimeTemplate } = compilation;
		const body = [
			"var chunks = chunkToChildrenMap[chunkId];",
			`Array.isArray(chunks) && chunks.map(${RuntimeGlobals.preloadChunk});`
		];
		return Template.asString([
			Template.asString([
				`var chunkToChildrenMap = ${JSON.stringify(chunkMap, null, "\t")};`,
				`${
					RuntimeGlobals.ensureChunkHandlers
				}.preload = ${runtimeTemplate.basicFunction("chunkId", body)};`
			])
		]);
	}
}

module.exports = ChunkPreloadTriggerRuntimeModule;


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

"use strict";

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

class BasicEffectRulePlugin {
	/**
	 * @param {string} ruleProperty the rule property
	 * @param {string=} effectType the effect type
	 */
	constructor(ruleProperty, effectType) {
		this.ruleProperty = ruleProperty;
		this.effectType = effectType || ruleProperty;
	}

	/**
	 * @param {RuleSetCompiler} ruleSetCompiler the rule set compiler
	 * @returns {void}
	 */
	apply(ruleSetCompiler) {
		ruleSetCompiler.hooks.rule.tap(
			"BasicEffectRulePlugin",
			(path, rule, unhandledProperties, result, references) => {
				if (unhandledProperties.has(this.ruleProperty)) {
					unhandledProperties.delete(this.ruleProperty);

					const value = rule[this.ruleProperty];

					result.effects.push({
						type: this.effectType,
						value
					});
				}
			}
		);
	}
}

module.exports = BasicEffectRulePlugin;


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

"use strict";

/** @typedef {import("./RuleSetCompiler")} RuleSetCompiler */
/** @typedef {import("./RuleSetCompiler").RuleCondition} RuleCondition */

class BasicMatcherRulePlugin {
	/**
	 * @param {string} ruleProperty the rule property
	 * @param {string=} dataProperty the data property
	 * @param {boolean=} invert if true, inverts the condition
	 */
	constructor(ruleProperty, dataProperty, invert) {
		this.ruleProperty = ruleProperty;
		this.dataProperty = dataProperty || ruleProperty;
		this.invert = invert || false;
	}

	/**
	 * @param {RuleSetCompiler} ruleSetCompiler the rule set compiler
	 * @returns {void}
	 */
	apply(ruleSetCompiler) {
		ruleSetCompiler.hooks.rule.tap(
			"BasicMatcherRulePlugin",
			(path, rule, unhandledProperties, result) => {
				if (unhandledProperties.has(this.ruleProperty)) {
					unhandledProperties.delete(this.ruleProperty);
					const value = rule[this.ruleProperty];
					const condition = ruleSetCompiler.compileCondition(
						`${path}.${this.ruleProperty}`,
						value
					);
					const fn = condition.fn;
					result.conditions.push({
						property: this.dataProperty,
						matchWhenEmpty: this.invert
							? !condition.matchWhenEmpty
							: condition.matchWhenEmpty,
						fn: this.invert ? v => !fn(v) : fn
					});
				}
			}
		);
	}
}

module.exports = BasicMatcherRulePlugin;


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

"use strict";

/** @typedef {import("./RuleSetCompiler")} RuleSetCompiler */
/** @typedef {import("./RuleSetCompiler").RuleCondition} RuleCondition */
/** @typedef {import("./RuleSetCompiler").RuleConditionFunction} RuleConditionFunction */

class ObjectMatcherRulePlugin {
	/**
	 * @param {string} ruleProperty the rule property
	 * @param {string=} dataProperty the data property
	 * @param {RuleConditionFunction=} additionalConditionFunction need to check
	 */
	constructor(ruleProperty, dataProperty, additionalConditionFunction) {
		this.ruleProperty = ruleProperty;
		this.dataProperty = dataProperty || ruleProperty;
		this.additionalConditionFunction = additionalConditionFunction;
	}

	/**
	 * @param {RuleSetCompiler} ruleSetCompiler the rule set compiler
	 * @returns {void}
	 */
	apply(ruleSetCompiler) {
		const { ruleProperty, dataProperty } = this;
		ruleSetCompiler.hooks.rule.tap(
			"ObjectMatcherRulePlugin",
			(path, rule, unhandledProperties, result) => {
				if (unhandledProperties.has(ruleProperty)) {
					unhandledProperties.delete(ruleProperty);
					const value = rule[ruleProperty];
					for (const property of Object.keys(value)) {
						const nestedDataProperties = property.split(".");
						const condition = ruleSetCompiler.compileCondition(
							`${path}.${ruleProperty}.${property}`,
							value[property]
						);
						if (this.additionalConditionFunction) {
							result.conditions.push({
								property: [dataProperty],
								matchWhenEmpty: condition.matchWhenEmpty,
								fn: this.additionalConditionFunction
							});
						}
						result.conditions.push({
							property: [dataProperty, ...nestedDataProperties],
							matchWhenEmpty: condition.matchWhenEmpty,
							fn: condition.fn
						});
					}
				}
			}
		);
	}
}

module.exports = ObjectMatcherRulePlugin;


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

"use strict";

const { SyncHook } = require("tapable");

/** @typedef {function(string): boolean} RuleConditionFunction */

/**
 * @typedef {object} RuleCondition
 * @property {string | string[]} property
 * @property {boolean} matchWhenEmpty
 * @property {RuleConditionFunction} fn
 */

/**
 * @typedef {object} Condition
 * @property {boolean} matchWhenEmpty
 * @property {RuleConditionFunction} fn
 */

/**
 * @typedef {object} CompiledRule
 * @property {RuleCondition[]} conditions
 * @property {(Effect|function(object): Effect[])[]} effects
 * @property {CompiledRule[]=} rules
 * @property {CompiledRule[]=} oneOf
 */

/**
 * @typedef {object} Effect
 * @property {string} type
 * @property {any} value
 */

/**
 * @typedef {object} RuleSet
 * @property {Map<string, any>} references map of references in the rule set (may grow over time)
 * @property {function(object): Effect[]} exec execute the rule set
 */

class RuleSetCompiler {
	constructor(plugins) {
		this.hooks = Object.freeze({
			/** @type {SyncHook<[string, object, Set<string>, CompiledRule, Map<string, any>]>} */
			rule: new SyncHook([
				"path",
				"rule",
				"unhandledProperties",
				"compiledRule",
				"references"
			])
		});
		if (plugins) {
			for (const plugin of plugins) {
				plugin.apply(this);
			}
		}
	}

	/**
	 * @param {object[]} ruleSet raw user provided rules
	 * @returns {RuleSet} compiled RuleSet
	 */
	compile(ruleSet) {
		const refs = new Map();
		const rules = this.compileRules("ruleSet", ruleSet, refs);

		/**
		 * @param {object} data data passed in
		 * @param {CompiledRule} rule the compiled rule
		 * @param {Effect[]} effects an array where effects are pushed to
		 * @returns {boolean} true, if the rule has matched
		 */
		const execRule = (data, rule, effects) => {
			for (const condition of rule.conditions) {
				const p = condition.property;
				if (Array.isArray(p)) {
					let current = data;
					for (const subProperty of p) {
						if (
							current &&
							typeof current === "object" &&
							Object.prototype.hasOwnProperty.call(current, subProperty)
						) {
							current = current[subProperty];
						} else {
							current = undefined;
							break;
						}
					}
					if (current !== undefined) {
						if (!condition.fn(current)) return false;
						continue;
					}
				} else if (p in data) {
					const value = data[p];
					if (value !== undefined) {
						if (!condition.fn(value)) return false;
						continue;
					}
				}
				if (!condition.matchWhenEmpty) {
					return false;
				}
			}
			for (const effect of rule.effects) {
				if (typeof effect === "function") {
					const returnedEffects = effect(data);
					for (const effect of returnedEffects) {
						effects.push(effect);
					}
				} else {
					effects.push(effect);
				}
			}
			if (rule.rules) {
				for (const childRule of rule.rules) {
					execRule(data, childRule, effects);
				}
			}
			if (rule.oneOf) {
				for (const childRule of rule.oneOf) {
					if (execRule(data, childRule, effects)) {
						break;
					}
				}
			}
			return true;
		};

		return {
			references: refs,
			exec: data => {
				/** @type {Effect[]} */
				const effects = [];
				for (const rule of rules) {
					execRule(data, rule, effects);
				}
				return effects;
			}
		};
	}

	/**
	 * @param {string} path current path
	 * @param {object[]} rules the raw rules provided by user
	 * @param {Map<string, any>} refs references
	 * @returns {CompiledRule[]} rules
	 */
	compileRules(path, rules, refs) {
		return rules
			.filter(Boolean)
			.map((rule, i) => this.compileRule(`${path}[${i}]`, rule, refs));
	}

	/**
	 * @param {string} path current path
	 * @param {object} rule the raw rule provided by user
	 * @param {Map<string, any>} refs references
	 * @returns {CompiledRule} normalized and compiled rule for processing
	 */
	compileRule(path, rule, refs) {
		const unhandledProperties = new Set(
			Object.keys(rule).filter(key => rule[key] !== undefined)
		);

		/** @type {CompiledRule} */
		const compiledRule = {
			conditions: [],
			effects: [],
			rules: undefined,
			oneOf: undefined
		};

		this.hooks.rule.call(path, rule, unhandledProperties, compiledRule, refs);

		if (unhandledProperties.has("rules")) {
			unhandledProperties.delete("rules");
			const rules = rule.rules;
			if (!Array.isArray(rules))
				throw this.error(path, rules, "Rule.rules must be an array of rules");
			compiledRule.rules = this.compileRules(`${path}.rules`, rules, refs);
		}

		if (unhandledProperties.has("oneOf")) {
			unhandledProperties.delete("oneOf");
			const oneOf = rule.oneOf;
			if (!Array.isArray(oneOf))
				throw this.error(path, oneOf, "Rule.oneOf must be an array of rules");
			compiledRule.oneOf = this.compileRules(`${path}.oneOf`, oneOf, refs);
		}

		if (unhandledProperties.size > 0) {
			throw this.error(
				path,
				rule,
				`Properties ${Array.from(unhandledProperties).join(", ")} are unknown`
			);
		}

		return compiledRule;
	}

	/**
	 * @param {string} path current path
	 * @param {any} condition user provided condition value
	 * @returns {Condition} compiled condition
	 */
	compileCondition(path, condition) {
		if (condition === "") {
			return {
				matchWhenEmpty: true,
				fn: str => str === ""
			};
		}
		if (!condition) {
			throw this.error(
				path,
				condition,
				"Expected condition but got falsy value"
			);
		}
		if (typeof condition === "string") {
			return {
				matchWhenEmpty: condition.length === 0,
				fn: str => typeof str === "string" && str.startsWith(condition)
			};
		}
		if (typeof condition === "function") {
			try {
				return {
					matchWhenEmpty: condition(""),
					fn: condition
				};
			} catch (err) {
				throw this.error(
					path,
					condition,
					"Evaluation of condition function threw error"
				);
			}
		}
		if (condition instanceof RegExp) {
			return {
				matchWhenEmpty: condition.test(""),
				fn: v => typeof v === "string" && condition.test(v)
			};
		}
		if (Array.isArray(condition)) {
			const items = condition.map((c, i) =>
				this.compileCondition(`${path}[${i}]`, c)
			);
			return this.combineConditionsOr(items);
		}

		if (typeof condition !== "object") {
			throw this.error(
				path,
				condition,
				`Unexpected ${typeof condition} when condition was expected`
			);
		}

		const conditions = [];
		for (const key of Object.keys(condition)) {
			const value = condition[key];
			switch (key) {
				case "or":
					if (value) {
						if (!Array.isArray(value)) {
							throw this.error(
								`${path}.or`,
								condition.or,
								"Expected array of conditions"
							);
						}
						conditions.push(this.compileCondition(`${path}.or`, value));
					}
					break;
				case "and":
					if (value) {
						if (!Array.isArray(value)) {
							throw this.error(
								`${path}.and`,
								condition.and,
								"Expected array of conditions"
							);
						}
						let i = 0;
						for (const item of value) {
							conditions.push(this.compileCondition(`${path}.and[${i}]`, item));
							i++;
						}
					}
					break;
				case "not":
					if (value) {
						const matcher = this.compileCondition(`${path}.not`, value);
						const fn = matcher.fn;
						conditions.push({
							matchWhenEmpty: !matcher.matchWhenEmpty,
							fn: v => !fn(v)
						});
					}
					break;
				default:
					throw this.error(
						`${path}.${key}`,
						condition[key],
						`Unexpected property ${key} in condition`
					);
			}
		}
		if (conditions.length === 0) {
			throw this.error(
				path,
				condition,
				"Expected condition, but got empty thing"
			);
		}
		return this.combineConditionsAnd(conditions);
	}

	/**
	 * @param {Condition[]} conditions some conditions
	 * @returns {Condition} merged condition
	 */
	combineConditionsOr(conditions) {
		if (conditions.length === 0) {
			return {
				matchWhenEmpty: false,
				fn: () => false
			};
		} else if (conditions.length === 1) {
			return conditions[0];
		} else {
			return {
				matchWhenEmpty: conditions.some(c => c.matchWhenEmpty),
				fn: v => conditions.some(c => c.fn(v))
			};
		}
	}

	/**
	 * @param {Condition[]} conditions some conditions
	 * @returns {Condition} merged condition
	 */
	combineConditionsAnd(conditions) {
		if (conditions.length === 0) {
			return {
				matchWhenEmpty: false,
				fn: () => false
			};
		} else if (conditions.length === 1) {
			return conditions[0];
		} else {
			return {
				matchWhenEmpty: conditions.every(c => c.matchWhenEmpty),
				fn: v => conditions.every(c => c.fn(v))
			};
		}
	}

	/**
	 * @param {string} path current path
	 * @param {any} value value at the error location
	 * @param {string} message message explaining the problem
	 * @returns {Error} an error object
	 */
	error(path, value, message) {
		return new Error(
			`Compiling RuleSet failed: ${message} (at ${path}: ${value})`
		);
	}
}

module.exports = RuleSetCompiler;


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

"use strict";

const util = require("util");

/** @typedef {import("./RuleSetCompiler")} RuleSetCompiler */
/** @typedef {import("./RuleSetCompiler").Effect} Effect */

class UseEffectRulePlugin {
	/**
	 * @param {RuleSetCompiler} ruleSetCompiler the rule set compiler
	 * @returns {void}
	 */
	apply(ruleSetCompiler) {
		ruleSetCompiler.hooks.rule.tap(
			"UseEffectRulePlugin",
			(path, rule, unhandledProperties, result, references) => {
				const conflictWith = (property, correctProperty) => {
					if (unhandledProperties.has(property)) {
						throw ruleSetCompiler.error(
							`${path}.${property}`,
							rule[property],
							`A Rule must not have a '${property}' property when it has a '${correctProperty}' property`
						);
					}
				};

				if (unhandledProperties.has("use")) {
					unhandledProperties.delete("use");
					unhandledProperties.delete("enforce");

					conflictWith("loader", "use");
					conflictWith("options", "use");

					const use = rule.use;
					const enforce = rule.enforce;

					const type = enforce ? `use-${enforce}` : "use";

					/**
					 *
					 * @param {string} path options path
					 * @param {string} defaultIdent default ident when none is provided
					 * @param {object} item user provided use value
					 * @returns {Effect|function(any): Effect[]} effect
					 */
					const useToEffect = (path, defaultIdent, item) => {
						if (typeof item === "function") {
							return data => useToEffectsWithoutIdent(path, item(data));
						} else {
							return useToEffectRaw(path, defaultIdent, item);
						}
					};

					/**
					 *
					 * @param {string} path options path
					 * @param {string} defaultIdent default ident when none is provided
					 * @param {object} item user provided use value
					 * @returns {Effect} effect
					 */
					const useToEffectRaw = (path, defaultIdent, item) => {
						if (typeof item === "string") {
							return {
								type,
								value: {
									loader: item,
									options: undefined,
									ident: undefined
								}
							};
						} else {
							const loader = item.loader;
							const options = item.options;
							let ident = item.ident;
							if (options && typeof options === "object") {
								if (!ident) ident = defaultIdent;
								references.set(ident, options);
							}
							if (typeof options === "string") {
								util.deprecate(
									() => {},
									`Using a string as loader options is deprecated (${path}.options)`,
									"DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING"
								)();
							}
							return {
								type: enforce ? `use-${enforce}` : "use",
								value: {
									loader,
									options,
									ident
								}
							};
						}
					};

					/**
					 * @param {string} path options path
					 * @param {any} items user provided use value
					 * @returns {Effect[]} effects
					 */
					const useToEffectsWithoutIdent = (path, items) => {
						if (Array.isArray(items)) {
							return items
								.filter(Boolean)
								.map((item, idx) =>
									useToEffectRaw(`${path}[${idx}]`, "[[missing ident]]", item)
								);
						}
						return [useToEffectRaw(path, "[[missing ident]]", items)];
					};

					/**
					 * @param {string} path current path
					 * @param {any} items user provided use value
					 * @returns {(Effect|function(any): Effect[])[]} effects
					 */
					const useToEffects = (path, items) => {
						if (Array.isArray(items)) {
							return items.filter(Boolean).map((item, idx) => {
								const subPath = `${path}[${idx}]`;
								return useToEffect(subPath, subPath, item);
							});
						}
						return [useToEffect(path, path, items)];
					};

					if (typeof use === "function") {
						result.effects.push(data =>
							useToEffectsWithoutIdent(`${path}.use`, use(data))
						);
					} else {
						for (const effect of useToEffects(`${path}.use`, use)) {
							result.effects.push(effect);
						}
					}
				}

				if (unhandledProperties.has("loader")) {
					unhandledProperties.delete("loader");
					unhandledProperties.delete("options");
					unhandledProperties.delete("enforce");

					const loader = rule.loader;
					const options = rule.options;
					const enforce = rule.enforce;

					if (loader.includes("!")) {
						throw ruleSetCompiler.error(
							`${path}.loader`,
							loader,
							"Exclamation mark separated loader lists has been removed in favor of the 'use' property with arrays"
						);
					}

					if (loader.includes("?")) {
						throw ruleSetCompiler.error(
							`${path}.loader`,
							loader,
							"Query arguments on 'loader' has been removed in favor of the 'options' property"
						);
					}

					if (typeof options === "string") {
						util.deprecate(
							() => {},
							`Using a string as loader options is deprecated (${path}.options)`,
							"DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING"
						)();
					}

					const ident =
						options && typeof options === "object" ? path : undefined;
					references.set(ident, options);
					result.effects.push({
						type: enforce ? `use-${enforce}` : "use",
						value: {
							loader,
							options,
							ident
						}
					});
				}
			}
		);
	}

	useItemToEffects(path, item) {}
}

module.exports = UseEffectRulePlugin;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
*/

"use strict";

const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const HelperRuntimeModule = require("./HelperRuntimeModule");

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

class AsyncModuleRuntimeModule extends HelperRuntimeModule {
	constructor() {
		super("async module");
	}

	/**
	 * @returns {string | null} runtime code
	 */
	generate() {
		const compilation = /** @type {Compilation} */ (this.compilation);
		const { runtimeTemplate } = compilation;
		const fn = RuntimeGlobals.asyncModule;
		return Template.asString([
			'var webpackQueues = typeof Symbol === "function" ? Symbol("webpack queues") : "__webpack_queues__";',
			`var webpackExports = typeof Symbol === "function" ? Symbol("webpack exports") : "${RuntimeGlobals.exports}";`,
			'var webpackError = typeof Symbol === "function" ? Symbol("webpack error") : "__webpack_error__";',
			`var resolveQueue = ${runtimeTemplate.basicFunction("queue", [
				"if(queue && queue.d < 1) {",
				Template.indent([
					"queue.d = 1;",
					`queue.forEach(${runtimeTemplate.expressionFunction(
						"fn.r--",
						"fn"
					)});`,
					`queue.forEach(${runtimeTemplate.expressionFunction(
						"fn.r-- ? fn.r++ : fn()",
						"fn"
					)});`
				]),
				"}"
			])}`,
			`var wrapDeps = ${runtimeTemplate.returningFunction(
				`deps.map(${runtimeTemplate.basicFunction("dep", [
					'if(dep !== null && typeof dep === "object") {',
					Template.indent([
						"if(dep[webpackQueues]) return dep;",
						"if(dep.then) {",
						Template.indent([
							"var queue = [];",
							"queue.d = 0;",
							`dep.then(${runtimeTemplate.basicFunction("r", [
								"obj[webpackExports] = r;",
								"resolveQueue(queue);"
							])}, ${runtimeTemplate.basicFunction("e", [
								"obj[webpackError] = e;",
								"resolveQueue(queue);"
							])});`,
							"var obj = {};",
							`obj[webpackQueues] = ${runtimeTemplate.expressionFunction(
								`fn(queue)`,
								"fn"
							)};`,
							"return obj;"
						]),
						"}"
					]),
					"}",
					"var ret = {};",
					`ret[webpackQueues] = ${runtimeTemplate.emptyFunction()};`,
					"ret[webpackExports] = dep;",
					"return ret;"
				])})`,
				"deps"
			)};`,
			`${fn} = ${runtimeTemplate.basicFunction("module, body, hasAwait", [
				"var queue;",
				"hasAwait && ((queue = []).d = -1);",
				"var depQueues = new Set();",
				"var exports = module.exports;",
				"var currentDeps;",
				"var outerResolve;",
				"var reject;",
				`var promise = new Promise(${runtimeTemplate.basicFunction(
					"resolve, rej",
					["reject = rej;", "outerResolve = resolve;"]
				)});`,
				"promise[webpackExports] = exports;",
				`promise[webpackQueues] = ${runtimeTemplate.expressionFunction(
					`queue && fn(queue), depQueues.forEach(fn), promise["catch"](${runtimeTemplate.emptyFunction()})`,
					"fn"
				)};`,
				"module.exports = promise;",
				`body(${runtimeTemplate.basicFunction("deps", [
					"currentDeps = wrapDeps(deps);",
					"var fn;",
					`var getResult = ${runtimeTemplate.returningFunction(
						`currentDeps.map(${runtimeTemplate.basicFunction("d", [
							"if(d[webpackError]) throw d[webpackError];",
							"return d[webpackExports];"
						])})`
					)}`,
					`var promise = new Promise(${runtimeTemplate.basicFunction(
						"resolve",
						[
							`fn = ${runtimeTemplate.expressionFunction(
								"resolve(getResult)",
								""
							)};`,
							"fn.r = 0;",
							`var fnQueue = ${runtimeTemplate.expressionFunction(
								"q !== queue && !depQueues.has(q) && (depQueues.add(q), q && !q.d && (fn.r++, q.push(fn)))",
								"q"
							)};`,
							`currentDeps.map(${runtimeTemplate.expressionFunction(
								"dep[webpackQueues](fnQueue)",
								"dep"
							)});`
						]
					)});`,
					"return fn.r ? promise : getResult();"
				])}, ${runtimeTemplate.expressionFunction(
					"(err ? reject(promise[webpackError] = err) : outerResolve(exports)), resolveQueue(queue)",
					"err"
				)});`,
				"queue && queue.d < 0 && (queue.d = 0);"
			])};`
		]);
	}
}

module.exports = AsyncModuleRuntimeModule;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
*/

"use strict";

const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");
const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
const { getUndoPath } = require("../util/identifier");

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

class AutoPublicPathRuntimeModule extends RuntimeModule {
	constructor() {
		super("publicPath", RuntimeModule.STAGE_BASIC);
	}

	/**
	 * @returns {string | null} runtime code
	 */
	generate() {
		const compilation = /** @type {Compilation} */ (this.compilation);
		const { scriptType, importMetaName, path } = compilation.outputOptions;
		const chunkName = compilation.getPath(
			JavascriptModulesPlugin.getChunkFilenameTemplate(
				this.chunk,
				compilation.outputOptions
			),
			{
				chunk: this.chunk,
				contentHashType: "javascript"
			}
		);
		const undoPath = getUndoPath(
			chunkName,
			/** @type {string} */ (path),
			false
		);

		return Template.asString([
			"var scriptUrl;",
			scriptType === "module"
				? `if (typeof ${importMetaName}.url === "string") scriptUrl = ${importMetaName}.url`
				: Template.asString([
						`if (${RuntimeGlobals.global}.importScripts) scriptUrl = ${RuntimeGlobals.global}.location + "";`,
						`var document = ${RuntimeGlobals.global}.document;`,
						"if (!scriptUrl && document) {",
						Template.indent([
							`if (document.currentScript)`,
							Template.indent(`scriptUrl = document.currentScript.src;`),
							"if (!scriptUrl) {",
							Template.indent([
								'var scripts = document.getElementsByTagName("script");',
								"if(scripts.length) {",
								Template.indent([
									"var i = scripts.length - 1;",
									"while (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;"
								]),
								"}"
							]),
							"}"
						]),
						"}"
					]),
			"// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration",
			'// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.',
			'if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");',
			'scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\\?.*$/, "").replace(/\\/[^\\/]+$/, "/");',
			!undoPath
				? `${RuntimeGlobals.publicPath} = scriptUrl;`
				: `${RuntimeGlobals.publicPath} = scriptUrl + ${JSON.stringify(
						undoPath
					)};`
		]);
	}
}

module.exports = AutoPublicPathRuntimeModule;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Ivan Kopeykin @vankop
*/

"use strict";

const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");

/** @typedef {import("../../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescriptionNormalized */
/** @typedef {import("../Chunk")} Chunk */

class BaseUriRuntimeModule extends RuntimeModule {
	constructor() {
		super("base uri", RuntimeModule.STAGE_ATTACH);
	}

	/**
	 * @returns {string | null} runtime code
	 */
	generate() {
		const chunk = /** @type {Chunk} */ (this.chunk);
		const options =
			/** @type {EntryDescriptionNormalized} */
			(chunk.getEntryOptions());
		return `${RuntimeGlobals.baseURI} = ${
			options.baseUri === undefined
				? "undefined"
				: JSON.stringify(options.baseUri)
		};`;
	}
}

module.exports = BaseUriRuntimeModule;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
*/

"use strict";

const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");

class ChunkNameRuntimeModule extends RuntimeModule {
	/**
	 * @param {string} chunkName the chunk's name
	 */
	constructor(chunkName) {
		super("chunkName");
		this.chunkName = chunkName;
	}

	/**
	 * @returns {string | null} runtime code
	 */
	generate() {
		return `${RuntimeGlobals.chunkName} = ${JSON.stringify(this.chunkName)};`;
	}
}

module.exports = ChunkNameRuntimeModule;