summaryrefslogtreecommitdiff
path: root/node_modules/webpack/lib/optimize/SplitChunksPlugin.js
diff options
context:
space:
mode:
authorlittleblack111 <littleblack11111@gmail.com>2023-11-05 17:16:09 +0800
committerlittleblack111 <littleblack11111@gmail.com>2023-11-05 17:16:09 +0800
commitd1d9f00b1bd660842addf5e6b1d1b64c852a9eea (patch)
tree60fb37ae73a1109d95301de5ec9b61b5726a4b61 /node_modules/webpack/lib/optimize/SplitChunksPlugin.js
parent7c85166c4cbdd862fc4252007d2df5befcbc2a58 (diff)
added hover prefixer
Diffstat (limited to 'node_modules/webpack/lib/optimize/SplitChunksPlugin.js')
-rw-r--r--node_modules/webpack/lib/optimize/SplitChunksPlugin.js1763
1 files changed, 1763 insertions, 0 deletions
diff --git a/node_modules/webpack/lib/optimize/SplitChunksPlugin.js b/node_modules/webpack/lib/optimize/SplitChunksPlugin.js
new file mode 100644
index 00000000..2e79ab6a
--- /dev/null
+++ b/node_modules/webpack/lib/optimize/SplitChunksPlugin.js
@@ -0,0 +1,1763 @@
+/*
+ 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>} reuseableChunks
+ * @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(),
+ reuseableChunks: 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");
+ }
+ );
+ });
+ }
+};