source: frontend/node_modules/webpack/lib/optimize/SplitChunksPlugin.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 59.1 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const Chunk = require("../Chunk");
9const { STAGE_ADVANCED } = require("../OptimizationStages");
10const WebpackError = require("../errors/WebpackError");
11const { requestToId } = require("../ids/IdHelpers");
12const { isSubset } = require("../util/SetHelpers");
13const SortableSet = require("../util/SortableSet");
14const {
15 compareIterables,
16 compareModulesByIdentifier
17} = require("../util/comparators");
18const createHash = require("../util/createHash");
19const deterministicGrouping = require("../util/deterministicGrouping");
20const { makePathsRelative } = require("../util/identifier");
21const memoize = require("../util/memoize");
22const MinMaxSizeWarning = require("./MinMaxSizeWarning");
23
24/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksCacheGroup} OptimizationSplitChunksCacheGroup */
25/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksOptions} OptimizationSplitChunksOptions */
26/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksSizes} OptimizationSplitChunksSizes */
27/** @typedef {import("../config/defaults").OutputNormalizedWithDefaults} OutputOptions */
28/** @typedef {import("../Chunk").ChunkName} ChunkName */
29/** @typedef {import("../ChunkGraph")} ChunkGraph */
30/** @typedef {import("../ChunkGroup")} ChunkGroup */
31/** @typedef {import("../Compiler")} Compiler */
32/** @typedef {import("../Module")} Module */
33/** @typedef {import("../Module").SourceType} SourceType */
34/** @typedef {import("../ModuleGraph")} ModuleGraph */
35/** @typedef {import("../Chunk").ChunkFilenameTemplate} ChunkFilenameTemplate */
36/** @typedef {import("../util/deterministicGrouping").GroupedItems<Module>} DeterministicGroupingGroupedItemsForModule */
37/** @typedef {import("../util/deterministicGrouping").Options<Module>} DeterministicGroupingOptionsForModule */
38/** @typedef {import("../util/deterministicGrouping").Sizes} Sizes */
39
40/**
41 * Defines the chunk filter fn callback.
42 * @callback ChunkFilterFn
43 * @param {Chunk} chunk
44 * @returns {boolean | undefined}
45 */
46
47/** @typedef {number} Priority */
48/** @typedef {number} Size */
49/** @typedef {number} CountOfChunk */
50/** @typedef {number} CountOfRequest */
51
52/**
53 * Defines the combine size function callback.
54 * @callback CombineSizeFunction
55 * @param {Size} a
56 * @param {Size} b
57 * @returns {Size}
58 */
59
60/** @typedef {SourceType[]} SourceTypes */
61/** @typedef {SourceType[]} DefaultSizeTypes */
62/** @typedef {Record<SourceType, Size>} SplitChunksSizes */
63
64/**
65 * Defines the cache group source type used by this module.
66 * @typedef {object} CacheGroupSource
67 * @property {string} key
68 * @property {Priority=} priority
69 * @property {GetNameFn=} getName
70 * @property {ChunkFilterFn=} chunksFilter
71 * @property {boolean=} enforce
72 * @property {SplitChunksSizes} minSize
73 * @property {SplitChunksSizes} minSizeReduction
74 * @property {SplitChunksSizes} minRemainingSize
75 * @property {SplitChunksSizes} enforceSizeThreshold
76 * @property {SplitChunksSizes} maxAsyncSize
77 * @property {SplitChunksSizes} maxInitialSize
78 * @property {CountOfChunk=} minChunks
79 * @property {CountOfRequest=} maxAsyncRequests
80 * @property {CountOfRequest=} maxInitialRequests
81 * @property {ChunkFilenameTemplate=} filename
82 * @property {string=} idHint
83 * @property {string=} automaticNameDelimiter
84 * @property {boolean=} reuseExistingChunk
85 * @property {boolean=} usedExports
86 */
87
88/**
89 * Defines the cache group type used by this module.
90 * @typedef {object} CacheGroup
91 * @property {string} key
92 * @property {Priority} priority
93 * @property {GetNameFn=} getName
94 * @property {ChunkFilterFn} chunksFilter
95 * @property {SplitChunksSizes} minSize
96 * @property {SplitChunksSizes} minSizeReduction
97 * @property {SplitChunksSizes} minRemainingSize
98 * @property {SplitChunksSizes} enforceSizeThreshold
99 * @property {SplitChunksSizes} maxAsyncSize
100 * @property {SplitChunksSizes} maxInitialSize
101 * @property {CountOfChunk} minChunks
102 * @property {CountOfRequest} maxAsyncRequests
103 * @property {CountOfRequest} maxInitialRequests
104 * @property {ChunkFilenameTemplate=} filename
105 * @property {string} idHint
106 * @property {string} automaticNameDelimiter
107 * @property {boolean} reuseExistingChunk
108 * @property {boolean} usedExports
109 * @property {boolean} _validateSize
110 * @property {boolean} _validateRemainingSize
111 * @property {SplitChunksSizes} _minSizeForMaxSize
112 * @property {boolean} _conditionalEnforce
113 */
114
115/**
116 * Defines the fallback cache group type used by this module.
117 * @typedef {object} FallbackCacheGroup
118 * @property {ChunkFilterFn} chunksFilter
119 * @property {SplitChunksSizes} minSize
120 * @property {SplitChunksSizes} maxAsyncSize
121 * @property {SplitChunksSizes} maxInitialSize
122 * @property {string} automaticNameDelimiter
123 */
124
125/**
126 * Defines the cache groups context type used by this module.
127 * @typedef {object} CacheGroupsContext
128 * @property {ModuleGraph} moduleGraph
129 * @property {ChunkGraph} chunkGraph
130 */
131
132/** @typedef {(module: Module) => OptimizationSplitChunksCacheGroup | OptimizationSplitChunksCacheGroup[] | void} RawGetCacheGroups */
133
134/**
135 * Defines the get cache groups callback.
136 * @callback GetCacheGroups
137 * @param {Module} module
138 * @param {CacheGroupsContext} context
139 * @returns {CacheGroupSource[] | null}
140 */
141
142/**
143 * Defines the get name fn callback.
144 * @callback GetNameFn
145 * @param {Module} module
146 * @param {Chunk[]} chunks
147 * @param {string} key
148 * @returns {string | undefined}
149 */
150
151/**
152 * Defines the split chunks options type used by this module.
153 * @typedef {object} SplitChunksOptions
154 * @property {ChunkFilterFn} chunksFilter
155 * @property {DefaultSizeTypes} defaultSizeTypes
156 * @property {SplitChunksSizes} minSize
157 * @property {SplitChunksSizes} minSizeReduction
158 * @property {SplitChunksSizes} minRemainingSize
159 * @property {SplitChunksSizes} enforceSizeThreshold
160 * @property {SplitChunksSizes} maxInitialSize
161 * @property {SplitChunksSizes} maxAsyncSize
162 * @property {CountOfChunk} minChunks
163 * @property {CountOfRequest} maxAsyncRequests
164 * @property {CountOfRequest} maxInitialRequests
165 * @property {boolean} hidePathInfo
166 * @property {ChunkFilenameTemplate=} filename
167 * @property {string} automaticNameDelimiter
168 * @property {GetCacheGroups} getCacheGroups
169 * @property {GetNameFn} getName
170 * @property {boolean} usedExports
171 * @property {FallbackCacheGroup} fallbackCacheGroup
172 */
173
174/** @typedef {Set<Chunk>} ChunkSet */
175
176/**
177 * Defines the chunks info item type used by this module.
178 * @typedef {object} ChunksInfoItem
179 * @property {SortableSet<Module>} modules
180 * @property {CacheGroup} cacheGroup
181 * @property {number} cacheGroupIndex
182 * @property {string=} name
183 * @property {SplitChunksSizes} sizes
184 * @property {ChunkSet} chunks
185 * @property {ChunkSet} reusableChunks
186 * @property {Set<bigint | Chunk>} chunksKeys
187 */
188
189/** @type {GetNameFn} */
190const defaultGetName = () => undefined;
191
192const deterministicGroupingForModules =
193 /** @type {(options: DeterministicGroupingOptionsForModule) => DeterministicGroupingGroupedItemsForModule[]} */
194 (deterministicGrouping);
195
196/** @type {WeakMap<Module, string>} */
197const getKeyCache = new WeakMap();
198
199/**
200 * Returns hashed filename.
201 * @param {string} name a filename to hash
202 * @param {OutputOptions} outputOptions hash function used
203 * @returns {string} hashed filename
204 */
205const hashFilename = (name, outputOptions) => {
206 const digest =
207 /** @type {string} */
208 (
209 createHash(outputOptions.hashFunction)
210 .update(name)
211 .digest(outputOptions.hashDigest)
212 );
213 return digest.slice(0, 8);
214};
215
216/**
217 * Returns the number of requests.
218 * @param {Chunk} chunk the chunk
219 * @returns {CountOfRequest} the number of requests
220 */
221const getRequests = (chunk) => {
222 let requests = 0;
223 for (const chunkGroup of chunk.groupsIterable) {
224 requests = Math.max(requests, chunkGroup.chunks.length);
225 }
226 return requests;
227};
228
229/**
230 * Returns result.
231 * @template {object} T
232 * @template {object} R
233 * @param {T} obj obj an object
234 * @param {(obj: T[keyof T], key: keyof T) => T[keyof T]} fn fn
235 * @returns {T} result
236 */
237const mapObject = (obj, fn) => {
238 /** @type {T} */
239 const newObj = Object.create(null);
240 for (const key of Object.keys(obj)) {
241 newObj[/** @type {keyof T} */ (key)] = fn(
242 obj[/** @type {keyof T} */ (key)],
243 /** @type {keyof T} */
244 (key)
245 );
246 }
247 return newObj;
248};
249
250/**
251 * Checks whether this object is overlap.
252 * @template T
253 * @param {Set<T>} a set
254 * @param {Set<T>} b other set
255 * @returns {boolean} true if at least one item of a is in b
256 */
257const isOverlap = (a, b) => {
258 for (const item of a) {
259 if (b.has(item)) return true;
260 }
261 return false;
262};
263
264const compareModuleIterables = compareIterables(compareModulesByIdentifier);
265
266/**
267 * Compares the provided values and returns their ordering.
268 * @param {ChunksInfoItem} a item
269 * @param {ChunksInfoItem} b item
270 * @returns {number} compare result
271 */
272const compareEntries = (a, b) => {
273 // 1. by priority
274 const diffPriority = a.cacheGroup.priority - b.cacheGroup.priority;
275 if (diffPriority) return diffPriority;
276 // 2. by number of chunks
277 const diffCount = a.chunks.size - b.chunks.size;
278 if (diffCount) return diffCount;
279 // 3. by size reduction
280 const aSizeReduce = totalSize(a.sizes) * (a.chunks.size - 1);
281 const bSizeReduce = totalSize(b.sizes) * (b.chunks.size - 1);
282 const diffSizeReduce = aSizeReduce - bSizeReduce;
283 if (diffSizeReduce) return diffSizeReduce;
284 // 4. by cache group index
285 const indexDiff = b.cacheGroupIndex - a.cacheGroupIndex;
286 if (indexDiff) return indexDiff;
287 // 5. by number of modules (to be able to compare by identifier)
288 const modulesA = a.modules;
289 const modulesB = b.modules;
290 const diff = modulesA.size - modulesB.size;
291 if (diff) return diff;
292 // 6. by module identifiers
293 modulesA.sort();
294 modulesB.sort();
295 return compareModuleIterables(modulesA, modulesB);
296};
297
298/**
299 * Initial chunk filter.
300 * @param {Chunk} chunk the chunk
301 * @returns {boolean} true, if the chunk is an entry chunk
302 */
303const INITIAL_CHUNK_FILTER = (chunk) => chunk.canBeInitial();
304/**
305 * Async chunk filter.
306 * @param {Chunk} chunk the chunk
307 * @returns {boolean} true, if the chunk is an async chunk
308 */
309const ASYNC_CHUNK_FILTER = (chunk) => !chunk.canBeInitial();
310/**
311 * Returns always true.
312 * @param {Chunk} _chunk the chunk
313 * @returns {boolean} always true
314 */
315const ALL_CHUNK_FILTER = (_chunk) => true;
316
317/**
318 * Returns normalized representation.
319 * @param {OptimizationSplitChunksSizes | undefined} value the sizes
320 * @param {DefaultSizeTypes} defaultSizeTypes the default size types
321 * @returns {SplitChunksSizes} normalized representation
322 */
323const normalizeSizes = (value, defaultSizeTypes) => {
324 if (typeof value === "number") {
325 /** @type {SplitChunksSizes} */
326 const o = {};
327 for (const sizeType of defaultSizeTypes) o[sizeType] = value;
328 return o;
329 } else if (typeof value === "object" && value !== null) {
330 return { ...value };
331 }
332 return {};
333};
334
335/**
336 * Merges the provided values into a single result.
337 * @param {...(SplitChunksSizes | undefined)} sizes the sizes
338 * @returns {SplitChunksSizes} the merged sizes
339 */
340const mergeSizes = (...sizes) => {
341 /** @type {SplitChunksSizes} */
342 let merged = {};
343 for (let i = sizes.length - 1; i >= 0; i--) {
344 merged = Object.assign(merged, sizes[i]);
345 }
346 return merged;
347};
348
349/**
350 * Checks whether this object contains the size.
351 * @param {SplitChunksSizes} sizes the sizes
352 * @returns {boolean} true, if there are sizes > 0
353 */
354const hasNonZeroSizes = (sizes) => {
355 for (const key of /** @type {SourceType[]} */ (Object.keys(sizes))) {
356 if (sizes[key] > 0) return true;
357 }
358 return false;
359};
360
361/**
362 * Returns the combine sizes.
363 * @param {SplitChunksSizes} a first sizes
364 * @param {SplitChunksSizes} b second sizes
365 * @param {CombineSizeFunction} combine a function to combine sizes
366 * @returns {SplitChunksSizes} the combine sizes
367 */
368const combineSizes = (a, b, combine) => {
369 const aKeys = /** @type {Set<SourceType>} */ (new Set(Object.keys(a)));
370 const bKeys = /** @type {Set<SourceType>} */ (new Set(Object.keys(b)));
371 /** @type {SplitChunksSizes} */
372 const result = {};
373 for (const key of aKeys) {
374 result[key] = bKeys.has(key) ? combine(a[key], b[key]) : a[key];
375 }
376 for (const key of bKeys) {
377 if (!aKeys.has(key)) {
378 result[key] = b[key];
379 }
380 }
381 return result;
382};
383
384/**
385 * Checks true if there are sizes and all existing sizes are at least minSize.
386 * @param {SplitChunksSizes} sizes the sizes
387 * @param {SplitChunksSizes} minSize the min sizes
388 * @returns {boolean} true if there are sizes and all existing sizes are at least `minSize`
389 */
390const checkMinSize = (sizes, minSize) => {
391 for (const key of /** @type {SourceType[]} */ (Object.keys(minSize))) {
392 const size = sizes[key];
393 if (size === undefined || size === 0) continue;
394 if (size < minSize[key]) return false;
395 }
396 return true;
397};
398
399/**
400 * Checks min size reduction.
401 * @param {SplitChunksSizes} sizes the sizes
402 * @param {SplitChunksSizes} minSizeReduction the min sizes
403 * @param {CountOfChunk} chunkCount number of chunks
404 * @returns {boolean} true if there are sizes and all existing sizes are at least `minSizeReduction`
405 */
406const checkMinSizeReduction = (sizes, minSizeReduction, chunkCount) => {
407 for (const key of /** @type {SourceType[]} */ (
408 Object.keys(minSizeReduction)
409 )) {
410 const size = sizes[key];
411 if (size === undefined || size === 0) continue;
412 if (size * chunkCount < minSizeReduction[key]) return false;
413 }
414 return true;
415};
416
417/**
418 * Gets violating min sizes.
419 * @param {SplitChunksSizes} sizes the sizes
420 * @param {SplitChunksSizes} minSize the min sizes
421 * @returns {undefined | SourceTypes} list of size types that are below min size
422 */
423const getViolatingMinSizes = (sizes, minSize) => {
424 /** @type {SourceTypes | undefined} */
425 let list;
426 for (const key of /** @type {SourceType[]} */ (Object.keys(minSize))) {
427 const size = sizes[key];
428 if (size === undefined || size === 0) continue;
429 if (size < minSize[key]) {
430 if (list === undefined) list = [key];
431 else list.push(key);
432 }
433 }
434 return list;
435};
436
437/**
438 * Returns the total size.
439 * @param {SplitChunksSizes} sizes the sizes
440 * @returns {Size} the total size
441 */
442const totalSize = (sizes) => {
443 let size = 0;
444 for (const key of /** @type {SourceType[]} */ (Object.keys(sizes))) {
445 size += sizes[key];
446 }
447 return size;
448};
449
450/**
451 * Returns a function to get the name of the chunk.
452 * @param {OptimizationSplitChunksCacheGroup["name"]} name the chunk name
453 * @returns {GetNameFn | undefined} a function to get the name of the chunk
454 */
455const normalizeName = (name) => {
456 if (typeof name === "string") {
457 return () => name;
458 }
459 if (typeof name === "function") {
460 return /** @type {GetNameFn} */ (name);
461 }
462};
463
464/**
465 * Normalizes chunks filter.
466 * @param {OptimizationSplitChunksCacheGroup["chunks"]} chunks the chunk filter option
467 * @returns {ChunkFilterFn | undefined} the chunk filter function
468 */
469const normalizeChunksFilter = (chunks) => {
470 if (chunks === "initial") {
471 return INITIAL_CHUNK_FILTER;
472 }
473 if (chunks === "async") {
474 return ASYNC_CHUNK_FILTER;
475 }
476 if (chunks === "all") {
477 return ALL_CHUNK_FILTER;
478 }
479 if (chunks instanceof RegExp) {
480 return (chunk) => (chunk.name ? chunks.test(chunk.name) : false);
481 }
482 if (typeof chunks === "function") {
483 return chunks;
484 }
485};
486
487/**
488 * Normalizes cache groups.
489 * @param {undefined | GetCacheGroups | Record<string, false | string | RegExp | RawGetCacheGroups | OptimizationSplitChunksCacheGroup>} cacheGroups the cache group options
490 * @param {DefaultSizeTypes} defaultSizeTypes the default size types
491 * @returns {GetCacheGroups} a function to get the cache groups
492 */
493const normalizeCacheGroups = (cacheGroups, defaultSizeTypes) => {
494 if (typeof cacheGroups === "function") {
495 return cacheGroups;
496 }
497 if (typeof cacheGroups === "object" && cacheGroups !== null) {
498 /** @type {((module: Module, context: CacheGroupsContext, results: CacheGroupSource[]) => void)[]} */
499 const handlers = [];
500 for (const key of Object.keys(cacheGroups)) {
501 const option = cacheGroups[key];
502 if (option === false) {
503 continue;
504 }
505 if (typeof option === "string" || option instanceof RegExp) {
506 const source = createCacheGroupSource({}, key, defaultSizeTypes);
507 handlers.push((module, context, results) => {
508 if (checkTest(option, module, context)) {
509 results.push(source);
510 }
511 });
512 } else if (typeof option === "function") {
513 /** @type {WeakMap<OptimizationSplitChunksCacheGroup, CacheGroupSource>} */
514 const cache = new WeakMap();
515 handlers.push((module, context, results) => {
516 const result = option(module);
517 if (result) {
518 const groups = Array.isArray(result) ? result : [result];
519 for (const group of groups) {
520 const cachedSource = cache.get(group);
521 if (cachedSource !== undefined) {
522 results.push(cachedSource);
523 } else {
524 const source = createCacheGroupSource(
525 group,
526 key,
527 defaultSizeTypes
528 );
529 cache.set(group, source);
530 results.push(source);
531 }
532 }
533 }
534 });
535 } else {
536 const source = createCacheGroupSource(option, key, defaultSizeTypes);
537 handlers.push((module, context, results) => {
538 if (
539 checkTest(option.test, module, context) &&
540 checkModuleType(option.type, module) &&
541 checkModuleLayer(option.layer, module)
542 ) {
543 results.push(source);
544 }
545 });
546 }
547 }
548 /**
549 * Returns the matching cache groups.
550 * @param {Module} module the current module
551 * @param {CacheGroupsContext} context the current context
552 * @returns {CacheGroupSource[]} the matching cache groups
553 */
554 const fn = (module, context) => {
555 /** @type {CacheGroupSource[]} */
556 const results = [];
557 for (const fn of handlers) {
558 fn(module, context, results);
559 }
560 return results;
561 };
562 return fn;
563 }
564 return () => null;
565};
566
567/** @typedef {(module: Module, context: CacheGroupsContext) => boolean} CheckTestFn */
568
569/**
570 * Checks true, if the module should be selected.
571 * @param {OptimizationSplitChunksCacheGroup["test"]} test test option
572 * @param {Module} module the module
573 * @param {CacheGroupsContext} context context object
574 * @returns {boolean} true, if the module should be selected
575 */
576const checkTest = (test, module, context) => {
577 if (test === undefined) return true;
578 if (typeof test === "function") {
579 return test(module, context);
580 }
581 if (typeof test === "boolean") return test;
582 if (typeof test === "string") {
583 const name = module.nameForCondition();
584 return name ? name.startsWith(test) : false;
585 }
586 if (test instanceof RegExp) {
587 const name = module.nameForCondition();
588 return name ? test.test(name) : false;
589 }
590 return false;
591};
592
593/** @typedef {(type: string) => boolean} CheckModuleTypeFn */
594
595/**
596 * Checks module type.
597 * @param {OptimizationSplitChunksCacheGroup["type"]} test type option
598 * @param {Module} module the module
599 * @returns {boolean} true, if the module should be selected
600 */
601const checkModuleType = (test, module) => {
602 if (test === undefined) return true;
603 if (typeof test === "function") {
604 return test(module.type);
605 }
606 if (typeof test === "string") {
607 const type = module.type;
608 return test === type;
609 }
610 if (test instanceof RegExp) {
611 const type = module.type;
612 return test.test(type);
613 }
614 return false;
615};
616
617/** @typedef {(layer: string | null) => boolean} CheckModuleLayerFn */
618
619/**
620 * Checks module layer.
621 * @param {OptimizationSplitChunksCacheGroup["layer"]} test type option
622 * @param {Module} module the module
623 * @returns {boolean} true, if the module should be selected
624 */
625const checkModuleLayer = (test, module) => {
626 if (test === undefined) return true;
627 if (typeof test === "function") {
628 return test(module.layer);
629 }
630 if (typeof test === "string") {
631 const layer = module.layer;
632 return test === "" ? !layer : layer ? layer.startsWith(test) : false;
633 }
634 if (test instanceof RegExp) {
635 const layer = module.layer;
636 return layer ? test.test(layer) : false;
637 }
638 return false;
639};
640
641/**
642 * Creates a cache group source.
643 * @param {OptimizationSplitChunksCacheGroup} options the group options
644 * @param {string} key key of cache group
645 * @param {DefaultSizeTypes} defaultSizeTypes the default size types
646 * @returns {CacheGroupSource} the normalized cached group
647 */
648const createCacheGroupSource = (options, key, defaultSizeTypes) => {
649 const minSize = normalizeSizes(options.minSize, defaultSizeTypes);
650 const minSizeReduction = normalizeSizes(
651 options.minSizeReduction,
652 defaultSizeTypes
653 );
654 const maxSize = normalizeSizes(options.maxSize, defaultSizeTypes);
655 return {
656 key,
657 priority: options.priority,
658 getName: normalizeName(options.name),
659 chunksFilter: normalizeChunksFilter(options.chunks),
660 enforce: options.enforce,
661 minSize,
662 minSizeReduction,
663 minRemainingSize: mergeSizes(
664 normalizeSizes(options.minRemainingSize, defaultSizeTypes),
665 minSize
666 ),
667 enforceSizeThreshold: normalizeSizes(
668 options.enforceSizeThreshold,
669 defaultSizeTypes
670 ),
671 maxAsyncSize: mergeSizes(
672 normalizeSizes(options.maxAsyncSize, defaultSizeTypes),
673 maxSize
674 ),
675 maxInitialSize: mergeSizes(
676 normalizeSizes(options.maxInitialSize, defaultSizeTypes),
677 maxSize
678 ),
679 minChunks: options.minChunks,
680 maxAsyncRequests: options.maxAsyncRequests,
681 maxInitialRequests: options.maxInitialRequests,
682 filename: options.filename,
683 idHint: options.idHint,
684 automaticNameDelimiter: options.automaticNameDelimiter,
685 reuseExistingChunk: options.reuseExistingChunk,
686 usedExports: options.usedExports
687 };
688};
689
690const PLUGIN_NAME = "SplitChunksPlugin";
691
692module.exports = class SplitChunksPlugin {
693 /**
694 * Creates an instance of SplitChunksPlugin.
695 * @param {OptimizationSplitChunksOptions=} options plugin options
696 */
697 constructor(options = {}) {
698 const defaultSizeTypes = options.defaultSizeTypes || [
699 "javascript",
700 "unknown"
701 ];
702 const fallbackCacheGroup = options.fallbackCacheGroup || {};
703 const minSize = normalizeSizes(options.minSize, defaultSizeTypes);
704 const minSizeReduction = normalizeSizes(
705 options.minSizeReduction,
706 defaultSizeTypes
707 );
708 const maxSize = normalizeSizes(options.maxSize, defaultSizeTypes);
709
710 /** @type {SplitChunksOptions} */
711 this.options = {
712 chunksFilter:
713 /** @type {ChunkFilterFn} */
714 (normalizeChunksFilter(options.chunks || "all")),
715 defaultSizeTypes,
716 minSize,
717 minSizeReduction,
718 minRemainingSize: mergeSizes(
719 normalizeSizes(options.minRemainingSize, defaultSizeTypes),
720 minSize
721 ),
722 enforceSizeThreshold: normalizeSizes(
723 options.enforceSizeThreshold,
724 defaultSizeTypes
725 ),
726 maxAsyncSize: mergeSizes(
727 normalizeSizes(options.maxAsyncSize, defaultSizeTypes),
728 maxSize
729 ),
730 maxInitialSize: mergeSizes(
731 normalizeSizes(options.maxInitialSize, defaultSizeTypes),
732 maxSize
733 ),
734 minChunks: options.minChunks || 1,
735 maxAsyncRequests: options.maxAsyncRequests || 1,
736 maxInitialRequests: options.maxInitialRequests || 1,
737 hidePathInfo: options.hidePathInfo || false,
738 filename: options.filename || undefined,
739 getCacheGroups: normalizeCacheGroups(
740 options.cacheGroups,
741 defaultSizeTypes
742 ),
743 getName: options.name
744 ? /** @type {GetNameFn} */ (normalizeName(options.name))
745 : defaultGetName,
746 automaticNameDelimiter: options.automaticNameDelimiter || "-",
747 usedExports: options.usedExports || false,
748 fallbackCacheGroup: {
749 chunksFilter:
750 /** @type {ChunkFilterFn} */
751 (
752 normalizeChunksFilter(
753 fallbackCacheGroup.chunks || options.chunks || "all"
754 )
755 ),
756 minSize: mergeSizes(
757 normalizeSizes(fallbackCacheGroup.minSize, defaultSizeTypes),
758 minSize
759 ),
760 maxAsyncSize: mergeSizes(
761 normalizeSizes(fallbackCacheGroup.maxAsyncSize, defaultSizeTypes),
762 normalizeSizes(fallbackCacheGroup.maxSize, defaultSizeTypes),
763 normalizeSizes(options.maxAsyncSize, defaultSizeTypes),
764 normalizeSizes(options.maxSize, defaultSizeTypes)
765 ),
766 maxInitialSize: mergeSizes(
767 normalizeSizes(fallbackCacheGroup.maxInitialSize, defaultSizeTypes),
768 normalizeSizes(fallbackCacheGroup.maxSize, defaultSizeTypes),
769 normalizeSizes(options.maxInitialSize, defaultSizeTypes),
770 normalizeSizes(options.maxSize, defaultSizeTypes)
771 ),
772 automaticNameDelimiter:
773 fallbackCacheGroup.automaticNameDelimiter ||
774 options.automaticNameDelimiter ||
775 "~"
776 }
777 };
778
779 /** @type {WeakMap<CacheGroupSource, CacheGroup>} */
780 this._cacheGroupCache = new WeakMap();
781 }
782
783 /**
784 * Returns the cache group (cached).
785 * @param {CacheGroupSource} cacheGroupSource source
786 * @returns {CacheGroup} the cache group (cached)
787 */
788 _getCacheGroup(cacheGroupSource) {
789 const cacheEntry = this._cacheGroupCache.get(cacheGroupSource);
790 if (cacheEntry !== undefined) return cacheEntry;
791 const minSize = mergeSizes(
792 cacheGroupSource.minSize,
793 cacheGroupSource.enforce ? undefined : this.options.minSize
794 );
795 const minSizeReduction = mergeSizes(
796 cacheGroupSource.minSizeReduction,
797 cacheGroupSource.enforce ? undefined : this.options.minSizeReduction
798 );
799 const minRemainingSize = mergeSizes(
800 cacheGroupSource.minRemainingSize,
801 cacheGroupSource.enforce ? undefined : this.options.minRemainingSize
802 );
803 const enforceSizeThreshold = mergeSizes(
804 cacheGroupSource.enforceSizeThreshold,
805 cacheGroupSource.enforce ? undefined : this.options.enforceSizeThreshold
806 );
807 /** @type {CacheGroup} */
808 const cacheGroup = {
809 key: cacheGroupSource.key,
810 priority: cacheGroupSource.priority || 0,
811 chunksFilter: cacheGroupSource.chunksFilter || this.options.chunksFilter,
812 minSize,
813 minSizeReduction,
814 minRemainingSize,
815 enforceSizeThreshold,
816 maxAsyncSize: mergeSizes(
817 cacheGroupSource.maxAsyncSize,
818 cacheGroupSource.enforce ? undefined : this.options.maxAsyncSize
819 ),
820 maxInitialSize: mergeSizes(
821 cacheGroupSource.maxInitialSize,
822 cacheGroupSource.enforce ? undefined : this.options.maxInitialSize
823 ),
824 minChunks:
825 cacheGroupSource.minChunks !== undefined
826 ? cacheGroupSource.minChunks
827 : cacheGroupSource.enforce
828 ? 1
829 : this.options.minChunks,
830 maxAsyncRequests:
831 cacheGroupSource.maxAsyncRequests !== undefined
832 ? cacheGroupSource.maxAsyncRequests
833 : cacheGroupSource.enforce
834 ? Infinity
835 : this.options.maxAsyncRequests,
836 maxInitialRequests:
837 cacheGroupSource.maxInitialRequests !== undefined
838 ? cacheGroupSource.maxInitialRequests
839 : cacheGroupSource.enforce
840 ? Infinity
841 : this.options.maxInitialRequests,
842 getName:
843 cacheGroupSource.getName !== undefined
844 ? cacheGroupSource.getName
845 : this.options.getName,
846 usedExports:
847 cacheGroupSource.usedExports !== undefined
848 ? cacheGroupSource.usedExports
849 : this.options.usedExports,
850 filename:
851 cacheGroupSource.filename !== undefined
852 ? cacheGroupSource.filename
853 : this.options.filename,
854 automaticNameDelimiter:
855 cacheGroupSource.automaticNameDelimiter !== undefined
856 ? cacheGroupSource.automaticNameDelimiter
857 : this.options.automaticNameDelimiter,
858 idHint:
859 cacheGroupSource.idHint !== undefined
860 ? cacheGroupSource.idHint
861 : cacheGroupSource.key,
862 reuseExistingChunk: cacheGroupSource.reuseExistingChunk || false,
863 _validateSize: hasNonZeroSizes(minSize),
864 _validateRemainingSize: hasNonZeroSizes(minRemainingSize),
865 _minSizeForMaxSize: mergeSizes(
866 cacheGroupSource.minSize,
867 this.options.minSize
868 ),
869 _conditionalEnforce: hasNonZeroSizes(enforceSizeThreshold)
870 };
871 this._cacheGroupCache.set(cacheGroupSource, cacheGroup);
872 return cacheGroup;
873 }
874
875 /**
876 * Applies the plugin by registering its hooks on the compiler.
877 * @param {Compiler} compiler the compiler instance
878 * @returns {void}
879 */
880 apply(compiler) {
881 const cachedMakePathsRelative = makePathsRelative.bindContextCache(
882 compiler.context,
883 compiler.root
884 );
885 compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
886 const logger = compilation.getLogger(`webpack.${PLUGIN_NAME}`);
887 let alreadyOptimized = false;
888 compilation.hooks.unseal.tap(PLUGIN_NAME, () => {
889 alreadyOptimized = false;
890 });
891 compilation.hooks.optimizeChunks.tap(
892 {
893 name: PLUGIN_NAME,
894 stage: STAGE_ADVANCED
895 },
896 (chunks) => {
897 if (alreadyOptimized) return;
898 alreadyOptimized = true;
899 logger.time("prepare");
900 const chunkGraph = compilation.chunkGraph;
901 const moduleGraph = compilation.moduleGraph;
902 // Give each selected chunk an index (to create strings from chunks)
903 /** @type {Map<Chunk, bigint>} */
904 const chunkIndexMap = new Map();
905 const ZERO = BigInt("0");
906 const ONE = BigInt("1");
907 const START = ONE << BigInt("31");
908 let index = START;
909 for (const chunk of chunks) {
910 chunkIndexMap.set(
911 chunk,
912 index | BigInt((Math.random() * 0x7fffffff) | 0)
913 );
914 index <<= ONE;
915 }
916 /**
917 * Returns key of the chunks.
918 * @param {Iterable<Chunk, undefined, undefined>} chunks list of chunks
919 * @returns {bigint | Chunk} key of the chunks
920 */
921 const getKey = (chunks) => {
922 const iterator = chunks[Symbol.iterator]();
923 let result = iterator.next();
924 if (result.done) return ZERO;
925 const first = result.value;
926 result = iterator.next();
927 if (result.done) return first;
928 let key =
929 /** @type {bigint} */ (chunkIndexMap.get(first)) |
930 /** @type {bigint} */ (chunkIndexMap.get(result.value));
931 while (!(result = iterator.next()).done) {
932 const raw = chunkIndexMap.get(result.value);
933 key ^= /** @type {bigint} */ (raw);
934 }
935 return key;
936 };
937 /**
938 * Returns stringified key.
939 * @param {bigint | Chunk} key key of the chunks
940 * @returns {string} stringified key
941 */
942 const keyToString = (key) => {
943 if (typeof key === "bigint") return key.toString(16);
944 return /** @type {bigint} */ (chunkIndexMap.get(key)).toString(16);
945 };
946
947 const getChunkSetsInGraph = memoize(() => {
948 /** @type {Map<bigint, ChunkSet>} */
949 const chunkSetsInGraph = new Map();
950 /** @type {ChunkSet} */
951 const singleChunkSets = new Set();
952 for (const module of compilation.modules) {
953 const chunks = chunkGraph.getModuleChunksIterable(module);
954 const chunksKey = getKey(chunks);
955 if (typeof chunksKey === "bigint") {
956 if (!chunkSetsInGraph.has(chunksKey)) {
957 chunkSetsInGraph.set(chunksKey, new Set(chunks));
958 }
959 } else {
960 singleChunkSets.add(chunksKey);
961 }
962 }
963 return { chunkSetsInGraph, singleChunkSets };
964 });
965
966 /**
967 * Group chunks by exports.
968 * @param {Module} module the module
969 * @returns {Iterable<Chunk[]>} groups of chunks with equal exports
970 */
971 const groupChunksByExports = (module) => {
972 const exportsInfo = moduleGraph.getExportsInfo(module);
973 /** @type {Map<string, Chunk[]>} */
974 const groupedByUsedExports = new Map();
975 for (const chunk of chunkGraph.getModuleChunksIterable(module)) {
976 const key = exportsInfo.getUsageKey(chunk.runtime);
977 const list = groupedByUsedExports.get(key);
978 if (list !== undefined) {
979 list.push(chunk);
980 } else {
981 groupedByUsedExports.set(key, [chunk]);
982 }
983 }
984 return groupedByUsedExports.values();
985 };
986
987 /** @type {Map<Module, Iterable<Chunk[]>>} */
988 const groupedByExportsMap = new Map();
989
990 /** @typedef {Map<bigint | Chunk, ChunkSet>} ChunkSetsInGraph */
991
992 const getExportsChunkSetsInGraph = memoize(() => {
993 /** @type {ChunkSetsInGraph} */
994 const chunkSetsInGraph = new Map();
995 /** @type {ChunkSet} */
996 const singleChunkSets = new Set();
997 for (const module of compilation.modules) {
998 const groupedChunks = [...groupChunksByExports(module)];
999 groupedByExportsMap.set(module, groupedChunks);
1000 for (const chunks of groupedChunks) {
1001 if (chunks.length === 1) {
1002 singleChunkSets.add(chunks[0]);
1003 } else {
1004 const chunksKey = getKey(chunks);
1005 if (!chunkSetsInGraph.has(chunksKey)) {
1006 chunkSetsInGraph.set(chunksKey, new Set(chunks));
1007 }
1008 }
1009 }
1010 }
1011 return { chunkSetsInGraph, singleChunkSets };
1012 });
1013
1014 /** @typedef {Map<CountOfChunk, ChunkSet[]>} ChunkSetsByCount */
1015
1016 // group these set of chunks by count
1017 // to allow to check less sets via isSubset
1018 // (only smaller sets can be subset)
1019 /**
1020 * Group chunk sets by count.
1021 * @param {IterableIterator<ChunkSet>} chunkSets set of sets of chunks
1022 * @returns {ChunkSetsByCount} map of sets of chunks by count
1023 */
1024 const groupChunkSetsByCount = (chunkSets) => {
1025 /** @type {ChunkSetsByCount} */
1026 const chunkSetsByCount = new Map();
1027 for (const chunksSet of chunkSets) {
1028 const count = chunksSet.size;
1029 let array = chunkSetsByCount.get(count);
1030 if (array === undefined) {
1031 array = [];
1032 chunkSetsByCount.set(count, array);
1033 }
1034 array.push(chunksSet);
1035 }
1036 return chunkSetsByCount;
1037 };
1038 const getChunkSetsByCount = memoize(() =>
1039 groupChunkSetsByCount(
1040 getChunkSetsInGraph().chunkSetsInGraph.values()
1041 )
1042 );
1043 const getExportsChunkSetsByCount = memoize(() =>
1044 groupChunkSetsByCount(
1045 getExportsChunkSetsInGraph().chunkSetsInGraph.values()
1046 )
1047 );
1048
1049 /** @typedef {(ChunkSet | Chunk)[]} Combinations */
1050
1051 // Create a list of possible combinations
1052 /**
1053 * Creates a get combinations.
1054 * @param {ChunkSetsInGraph} chunkSets chunk sets
1055 * @param {ChunkSet} singleChunkSets single chunks sets
1056 * @param {ChunkSetsByCount} chunkSetsByCount chunk sets by count
1057 * @returns {(key: bigint | Chunk) => Combinations} combinations
1058 */
1059 const createGetCombinations = (
1060 chunkSets,
1061 singleChunkSets,
1062 chunkSetsByCount
1063 ) => {
1064 /** @type {Map<bigint | Chunk, Combinations>} */
1065 const combinationsCache = new Map();
1066
1067 return (key) => {
1068 const cacheEntry = combinationsCache.get(key);
1069 if (cacheEntry !== undefined) return cacheEntry;
1070 if (key instanceof Chunk) {
1071 const result = [key];
1072 combinationsCache.set(key, result);
1073 return result;
1074 }
1075 const chunksSet =
1076 /** @type {ChunkSet} */
1077 (chunkSets.get(key));
1078 /** @type {Combinations} */
1079 const array = [chunksSet];
1080 for (const [count, setArray] of chunkSetsByCount) {
1081 // "equal" is not needed because they would have been merge in the first step
1082 if (count < chunksSet.size) {
1083 for (const set of setArray) {
1084 if (isSubset(chunksSet, set)) {
1085 array.push(set);
1086 }
1087 }
1088 }
1089 }
1090 for (const chunk of singleChunkSets) {
1091 if (chunksSet.has(chunk)) {
1092 array.push(chunk);
1093 }
1094 }
1095 combinationsCache.set(key, array);
1096 return array;
1097 };
1098 };
1099
1100 const getCombinationsFactory = memoize(() => {
1101 const { chunkSetsInGraph, singleChunkSets } = getChunkSetsInGraph();
1102 return createGetCombinations(
1103 chunkSetsInGraph,
1104 singleChunkSets,
1105 getChunkSetsByCount()
1106 );
1107 });
1108
1109 /**
1110 * Returns combinations by key.
1111 * @param {bigint | Chunk} key key
1112 * @returns {Combinations} combinations by key
1113 */
1114 const getCombinations = (key) => getCombinationsFactory()(key);
1115
1116 const getExportsCombinationsFactory = memoize(() => {
1117 const { chunkSetsInGraph, singleChunkSets } =
1118 getExportsChunkSetsInGraph();
1119 return createGetCombinations(
1120 chunkSetsInGraph,
1121 singleChunkSets,
1122 getExportsChunkSetsByCount()
1123 );
1124 });
1125 /**
1126 * Gets exports combinations.
1127 * @param {bigint | Chunk} key key
1128 * @returns {Combinations} exports combinations by key
1129 */
1130 const getExportsCombinations = (key) =>
1131 getExportsCombinationsFactory()(key);
1132
1133 /**
1134 * Defines the selected chunks result type used by this module.
1135 * @typedef {object} SelectedChunksResult
1136 * @property {Chunk[]} chunks the list of chunks
1137 * @property {bigint | Chunk} key a key of the list
1138 */
1139
1140 /** @typedef {WeakMap<ChunkFilterFn, SelectedChunksResult>} ChunkMap */
1141 /** @type {WeakMap<ChunkSet | Chunk, ChunkMap>} */
1142 const selectedChunksCacheByChunksSet = new WeakMap();
1143
1144 /**
1145 * get list and key by applying the filter function to the list
1146 * It is cached for performance reasons
1147 * @param {ChunkSet | Chunk} chunks list of chunks
1148 * @param {ChunkFilterFn} chunkFilter filter function for chunks
1149 * @returns {SelectedChunksResult} list and key
1150 */
1151 const getSelectedChunks = (chunks, chunkFilter) => {
1152 let entry = selectedChunksCacheByChunksSet.get(chunks);
1153 if (entry === undefined) {
1154 /** @type {ChunkMap} */
1155 entry = new WeakMap();
1156 selectedChunksCacheByChunksSet.set(chunks, entry);
1157 }
1158 let entry2 =
1159 /** @type {SelectedChunksResult} */
1160 (entry.get(chunkFilter));
1161 if (entry2 === undefined) {
1162 /** @type {Chunk[]} */
1163 const selectedChunks = [];
1164 if (chunks instanceof Chunk) {
1165 if (chunkFilter(chunks)) selectedChunks.push(chunks);
1166 } else {
1167 for (const chunk of chunks) {
1168 if (chunkFilter(chunk)) selectedChunks.push(chunk);
1169 }
1170 }
1171 entry2 = {
1172 chunks: selectedChunks,
1173 key: getKey(selectedChunks)
1174 };
1175 entry.set(chunkFilter, entry2);
1176 }
1177 return entry2;
1178 };
1179
1180 /** @type {Map<string, boolean>} */
1181 const alreadyValidatedParents = new Map();
1182 /** @type {Set<string>} */
1183 const alreadyReportedErrors = new Set();
1184
1185 // Map a list of chunks to a list of modules
1186 // For the key the chunk "index" is used, the value is a SortableSet of modules
1187 /** @type {Map<string, ChunksInfoItem>} */
1188 const chunksInfoMap = new Map();
1189
1190 /**
1191 * Adds module to chunks info map.
1192 * @param {CacheGroup} cacheGroup the current cache group
1193 * @param {number} cacheGroupIndex the index of the cache group of ordering
1194 * @param {Chunk[]} selectedChunks chunks selected for this module
1195 * @param {bigint | Chunk} selectedChunksKey a key of selectedChunks
1196 * @param {Module} module the current module
1197 * @returns {void}
1198 */
1199 const addModuleToChunksInfoMap = (
1200 cacheGroup,
1201 cacheGroupIndex,
1202 selectedChunks,
1203 selectedChunksKey,
1204 module
1205 ) => {
1206 // Break if minimum number of chunks is not reached
1207 if (selectedChunks.length < cacheGroup.minChunks) return;
1208 // Determine name for split chunk
1209
1210 const name =
1211 /** @type {GetNameFn} */
1212 (cacheGroup.getName)(module, selectedChunks, cacheGroup.key);
1213 // Check if the name is ok
1214 const existingChunk = name && compilation.namedChunks.get(name);
1215 if (existingChunk) {
1216 const parentValidationKey = `${name}|${
1217 typeof selectedChunksKey === "bigint"
1218 ? selectedChunksKey
1219 : selectedChunksKey.debugId
1220 }`;
1221 const valid = alreadyValidatedParents.get(parentValidationKey);
1222 if (valid === false) return;
1223 if (valid === undefined) {
1224 // Module can only be moved into the existing chunk if the existing chunk
1225 // is a parent of all selected chunks
1226 let isInAllParents = true;
1227 /** @type {Set<ChunkGroup>} */
1228 const queue = new Set();
1229 for (const chunk of selectedChunks) {
1230 for (const group of chunk.groupsIterable) {
1231 queue.add(group);
1232 }
1233 }
1234 for (const group of queue) {
1235 if (existingChunk.isInGroup(group)) continue;
1236 let hasParent = false;
1237 for (const parent of group.parentsIterable) {
1238 hasParent = true;
1239 queue.add(parent);
1240 }
1241 if (!hasParent) {
1242 isInAllParents = false;
1243 }
1244 }
1245 const valid = isInAllParents;
1246 alreadyValidatedParents.set(parentValidationKey, valid);
1247 if (!valid) {
1248 if (!alreadyReportedErrors.has(name)) {
1249 alreadyReportedErrors.add(name);
1250 compilation.errors.push(
1251 new WebpackError(
1252 `${PLUGIN_NAME}\n` +
1253 `Cache group "${cacheGroup.key}" conflicts with existing chunk.\n` +
1254 `Both have the same name "${name}" and existing chunk is not a parent of the selected modules.\n` +
1255 "Use a different name for the cache group or make sure that the existing chunk is a parent (e. g. via dependOn).\n" +
1256 'HINT: You can omit "name" to automatically create a name.\n' +
1257 "BREAKING CHANGE: webpack < 5 used to allow to use an entrypoint as splitChunk. " +
1258 "This is no longer allowed when the entrypoint is not a parent of the selected modules.\n" +
1259 "Remove this entrypoint and add modules to cache group's 'test' instead. " +
1260 "If you need modules to be evaluated on startup, add them to the existing entrypoints (make them arrays). " +
1261 "See migration guide of more info."
1262 )
1263 );
1264 }
1265 return;
1266 }
1267 }
1268 }
1269 // Create key for maps
1270 // When it has a name we use the name as key
1271 // Otherwise we create the key from chunks and cache group key
1272 // This automatically merges equal names
1273 const key =
1274 cacheGroup.key +
1275 (name
1276 ? ` name:${name}`
1277 : ` chunks:${keyToString(selectedChunksKey)}`);
1278 // Add module to maps
1279 let info = chunksInfoMap.get(key);
1280 if (info === undefined) {
1281 chunksInfoMap.set(
1282 key,
1283 (info = {
1284 modules: new SortableSet(
1285 undefined,
1286 compareModulesByIdentifier
1287 ),
1288 cacheGroup,
1289 cacheGroupIndex,
1290 name,
1291 sizes: {},
1292 chunks: new Set(),
1293 reusableChunks: new Set(),
1294 chunksKeys: new Set()
1295 })
1296 );
1297 }
1298 const oldSize = info.modules.size;
1299 info.modules.add(module);
1300 if (info.modules.size !== oldSize) {
1301 for (const type of module.getSourceTypes()) {
1302 info.sizes[type] = (info.sizes[type] || 0) + module.size(type);
1303 }
1304 }
1305 const oldChunksKeysSize = info.chunksKeys.size;
1306 info.chunksKeys.add(selectedChunksKey);
1307 if (oldChunksKeysSize !== info.chunksKeys.size) {
1308 for (const chunk of selectedChunks) {
1309 info.chunks.add(chunk);
1310 }
1311 }
1312 };
1313
1314 const context = {
1315 moduleGraph,
1316 chunkGraph
1317 };
1318
1319 logger.timeEnd("prepare");
1320
1321 logger.time("modules");
1322
1323 // Walk through all modules
1324 for (const module of compilation.modules) {
1325 // Get cache group
1326 const cacheGroups = this.options.getCacheGroups(module, context);
1327 if (!Array.isArray(cacheGroups) || cacheGroups.length === 0) {
1328 continue;
1329 }
1330
1331 // Prepare some values (usedExports = false)
1332 const getCombs = memoize(() => {
1333 const chunks = chunkGraph.getModuleChunksIterable(module);
1334 const chunksKey = getKey(chunks);
1335 return getCombinations(chunksKey);
1336 });
1337
1338 // Prepare some values (usedExports = true)
1339 const getCombsByUsedExports = memoize(() => {
1340 // fill the groupedByExportsMap
1341 getExportsChunkSetsInGraph();
1342 /** @type {Set<ChunkSet | Chunk>} */
1343 const set = new Set();
1344 const groupedByUsedExports =
1345 /** @type {Iterable<Chunk[]>} */
1346 (groupedByExportsMap.get(module));
1347 for (const chunks of groupedByUsedExports) {
1348 const chunksKey = getKey(chunks);
1349 for (const comb of getExportsCombinations(chunksKey)) {
1350 set.add(comb);
1351 }
1352 }
1353 return set;
1354 });
1355
1356 let cacheGroupIndex = 0;
1357 for (const cacheGroupSource of cacheGroups) {
1358 const cacheGroup = this._getCacheGroup(cacheGroupSource);
1359
1360 const combs = cacheGroup.usedExports
1361 ? getCombsByUsedExports()
1362 : getCombs();
1363 // For all combination of chunk selection
1364 for (const chunkCombination of combs) {
1365 // Break if minimum number of chunks is not reached
1366 const count =
1367 chunkCombination instanceof Chunk ? 1 : chunkCombination.size;
1368 if (count < cacheGroup.minChunks) continue;
1369 // Select chunks by configuration
1370 const { chunks: selectedChunks, key: selectedChunksKey } =
1371 getSelectedChunks(
1372 chunkCombination,
1373 /** @type {ChunkFilterFn} */
1374 (cacheGroup.chunksFilter)
1375 );
1376
1377 addModuleToChunksInfoMap(
1378 cacheGroup,
1379 cacheGroupIndex,
1380 selectedChunks,
1381 selectedChunksKey,
1382 module
1383 );
1384 }
1385 cacheGroupIndex++;
1386 }
1387 }
1388
1389 logger.timeEnd("modules");
1390
1391 logger.time("queue");
1392
1393 /**
1394 * Removes modules with source type.
1395 * @param {ChunksInfoItem} info entry
1396 * @param {SourceTypes} sourceTypes source types to be removed
1397 */
1398 const removeModulesWithSourceType = (info, sourceTypes) => {
1399 for (const module of info.modules) {
1400 const types = module.getSourceTypes();
1401 if (sourceTypes.some((type) => types.has(type))) {
1402 info.modules.delete(module);
1403 for (const type of types) {
1404 info.sizes[type] -= module.size(type);
1405 }
1406 }
1407 }
1408 };
1409
1410 /**
1411 * Removes min size violating modules.
1412 * @param {ChunksInfoItem} info entry
1413 * @returns {boolean} true, if entry become empty
1414 */
1415 const removeMinSizeViolatingModules = (info) => {
1416 if (!info.cacheGroup._validateSize) return false;
1417 const violatingSizes = getViolatingMinSizes(
1418 info.sizes,
1419 info.cacheGroup.minSize
1420 );
1421 if (violatingSizes === undefined) return false;
1422 removeModulesWithSourceType(info, violatingSizes);
1423 return info.modules.size === 0;
1424 };
1425
1426 // Filter items were size < minSize
1427 for (const [key, info] of chunksInfoMap) {
1428 if (removeMinSizeViolatingModules(info)) {
1429 chunksInfoMap.delete(key);
1430 } else if (
1431 !checkMinSizeReduction(
1432 info.sizes,
1433 info.cacheGroup.minSizeReduction,
1434 info.chunks.size
1435 )
1436 ) {
1437 chunksInfoMap.delete(key);
1438 }
1439 }
1440
1441 /**
1442 * Defines the max size queue item type used by this module.
1443 * @typedef {object} MaxSizeQueueItem
1444 * @property {SplitChunksSizes} minSize
1445 * @property {SplitChunksSizes} maxAsyncSize
1446 * @property {SplitChunksSizes} maxInitialSize
1447 * @property {string} automaticNameDelimiter
1448 * @property {string[]} keys
1449 */
1450
1451 /** @type {Map<Chunk, MaxSizeQueueItem>} */
1452 const maxSizeQueueMap = new Map();
1453
1454 while (chunksInfoMap.size > 0) {
1455 // Find best matching entry
1456 /** @type {undefined | string} */
1457 let bestEntryKey;
1458 /** @type {undefined | ChunksInfoItem} */
1459 let bestEntry;
1460 for (const pair of chunksInfoMap) {
1461 const key = pair[0];
1462 const info = pair[1];
1463 if (
1464 bestEntry === undefined ||
1465 compareEntries(bestEntry, info) < 0
1466 ) {
1467 bestEntry = info;
1468 bestEntryKey = key;
1469 }
1470 }
1471
1472 const item = /** @type {ChunksInfoItem} */ (bestEntry);
1473 chunksInfoMap.delete(/** @type {string} */ (bestEntryKey));
1474
1475 /** @type {ChunkName | undefined} */
1476 let chunkName = item.name;
1477 // Variable for the new chunk (lazy created)
1478 /** @type {Chunk | undefined} */
1479 let newChunk;
1480 // When no chunk name, check if we can reuse a chunk instead of creating a new one
1481 let isExistingChunk = false;
1482 let isReusedWithAllModules = false;
1483 if (chunkName) {
1484 const chunkByName = compilation.namedChunks.get(chunkName);
1485 if (chunkByName !== undefined) {
1486 newChunk = chunkByName;
1487 const oldSize = item.chunks.size;
1488 item.chunks.delete(newChunk);
1489 isExistingChunk = item.chunks.size !== oldSize;
1490 }
1491 } else if (item.cacheGroup.reuseExistingChunk) {
1492 outer: for (const chunk of item.chunks) {
1493 if (
1494 chunkGraph.getNumberOfChunkModules(chunk) !==
1495 item.modules.size
1496 ) {
1497 continue;
1498 }
1499 if (
1500 item.chunks.size > 1 &&
1501 chunkGraph.getNumberOfEntryModules(chunk) > 0
1502 ) {
1503 continue;
1504 }
1505 for (const module of item.modules) {
1506 if (!chunkGraph.isModuleInChunk(module, chunk)) {
1507 continue outer;
1508 }
1509 }
1510 if (!newChunk || !newChunk.name) {
1511 newChunk = chunk;
1512 } else if (
1513 chunk.name &&
1514 chunk.name.length < newChunk.name.length
1515 ) {
1516 newChunk = chunk;
1517 } else if (
1518 chunk.name &&
1519 chunk.name.length === newChunk.name.length &&
1520 chunk.name < newChunk.name
1521 ) {
1522 newChunk = chunk;
1523 }
1524 }
1525 if (newChunk) {
1526 item.chunks.delete(newChunk);
1527 chunkName = undefined;
1528 isExistingChunk = true;
1529 isReusedWithAllModules = true;
1530 }
1531 }
1532
1533 const enforced =
1534 item.cacheGroup._conditionalEnforce &&
1535 checkMinSize(item.sizes, item.cacheGroup.enforceSizeThreshold);
1536
1537 /** @type {Set<Chunk>} */
1538 const usedChunks = new Set(item.chunks);
1539
1540 // Check if maxRequests condition can be fulfilled
1541 if (
1542 !enforced &&
1543 (Number.isFinite(item.cacheGroup.maxInitialRequests) ||
1544 Number.isFinite(item.cacheGroup.maxAsyncRequests))
1545 ) {
1546 for (const chunk of usedChunks) {
1547 // respect max requests
1548 const maxRequests = chunk.isOnlyInitial()
1549 ? item.cacheGroup.maxInitialRequests
1550 : chunk.canBeInitial()
1551 ? Math.min(
1552 item.cacheGroup.maxInitialRequests,
1553 item.cacheGroup.maxAsyncRequests
1554 )
1555 : item.cacheGroup.maxAsyncRequests;
1556 if (
1557 Number.isFinite(maxRequests) &&
1558 getRequests(chunk) >= maxRequests
1559 ) {
1560 usedChunks.delete(chunk);
1561 }
1562 }
1563 }
1564
1565 outer: for (const chunk of usedChunks) {
1566 for (const module of item.modules) {
1567 if (chunkGraph.isModuleInChunk(module, chunk)) continue outer;
1568 }
1569 usedChunks.delete(chunk);
1570 }
1571
1572 // Were some (invalid) chunks removed from usedChunks?
1573 // => readd all modules to the queue, as things could have been changed
1574 if (usedChunks.size < item.chunks.size) {
1575 if (isExistingChunk) {
1576 usedChunks.add(/** @type {Chunk} */ (newChunk));
1577 }
1578 if (usedChunks.size >= item.cacheGroup.minChunks) {
1579 const chunksArr = [...usedChunks];
1580 for (const module of item.modules) {
1581 addModuleToChunksInfoMap(
1582 item.cacheGroup,
1583 item.cacheGroupIndex,
1584 chunksArr,
1585 getKey(usedChunks),
1586 module
1587 );
1588 }
1589 }
1590 continue;
1591 }
1592
1593 // Validate minRemainingSize constraint when a single chunk is left over
1594 if (
1595 !enforced &&
1596 item.cacheGroup._validateRemainingSize &&
1597 usedChunks.size === 1
1598 ) {
1599 const [chunk] = usedChunks;
1600 /** @type {SplitChunksSizes} */
1601 const chunkSizes = Object.create(null);
1602 for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
1603 if (!item.modules.has(module)) {
1604 for (const type of module.getSourceTypes()) {
1605 chunkSizes[type] =
1606 (chunkSizes[type] || 0) + module.size(type);
1607 }
1608 }
1609 }
1610 const violatingSizes = getViolatingMinSizes(
1611 chunkSizes,
1612 item.cacheGroup.minRemainingSize
1613 );
1614 if (violatingSizes !== undefined) {
1615 const oldModulesSize = item.modules.size;
1616 removeModulesWithSourceType(item, violatingSizes);
1617 if (
1618 item.modules.size > 0 &&
1619 item.modules.size !== oldModulesSize
1620 ) {
1621 // queue this item again to be processed again
1622 // without violating modules
1623 chunksInfoMap.set(/** @type {string} */ (bestEntryKey), item);
1624 }
1625 continue;
1626 }
1627 }
1628
1629 // Create the new chunk if not reusing one
1630 if (newChunk === undefined) {
1631 newChunk = compilation.addChunk(chunkName);
1632 }
1633 // Walk through all chunks
1634 for (const chunk of usedChunks) {
1635 // Add graph connections for splitted chunk
1636 chunk.split(newChunk);
1637 }
1638
1639 // Add a note to the chunk
1640 newChunk.chunkReason =
1641 (newChunk.chunkReason ? `${newChunk.chunkReason}, ` : "") +
1642 (isReusedWithAllModules
1643 ? "reused as split chunk"
1644 : "split chunk");
1645 if (item.cacheGroup.key) {
1646 newChunk.chunkReason += ` (cache group: ${item.cacheGroup.key})`;
1647 }
1648 if (chunkName) {
1649 newChunk.chunkReason += ` (name: ${chunkName})`;
1650 }
1651 if (item.cacheGroup.filename) {
1652 newChunk.filenameTemplate = item.cacheGroup.filename;
1653 }
1654 if (item.cacheGroup.idHint) {
1655 newChunk.idNameHints.add(item.cacheGroup.idHint);
1656 }
1657 if (!isReusedWithAllModules) {
1658 // Add all modules to the new chunk
1659 for (const module of item.modules) {
1660 if (!module.chunkCondition(newChunk, compilation)) continue;
1661 // Add module to new chunk
1662 chunkGraph.connectChunkAndModule(newChunk, module);
1663 // Remove module from used chunks
1664 for (const chunk of usedChunks) {
1665 chunkGraph.disconnectChunkAndModule(chunk, module);
1666 }
1667 }
1668 } else {
1669 // Remove all modules from used chunks
1670 for (const module of item.modules) {
1671 for (const chunk of usedChunks) {
1672 chunkGraph.disconnectChunkAndModule(chunk, module);
1673 }
1674 }
1675 }
1676
1677 if (
1678 Object.keys(item.cacheGroup.maxAsyncSize).length > 0 ||
1679 Object.keys(item.cacheGroup.maxInitialSize).length > 0
1680 ) {
1681 const oldMaxSizeSettings = maxSizeQueueMap.get(newChunk);
1682 maxSizeQueueMap.set(newChunk, {
1683 minSize: oldMaxSizeSettings
1684 ? combineSizes(
1685 oldMaxSizeSettings.minSize,
1686 item.cacheGroup._minSizeForMaxSize,
1687 Math.max
1688 )
1689 : item.cacheGroup.minSize,
1690 maxAsyncSize: oldMaxSizeSettings
1691 ? combineSizes(
1692 oldMaxSizeSettings.maxAsyncSize,
1693 item.cacheGroup.maxAsyncSize,
1694 Math.min
1695 )
1696 : item.cacheGroup.maxAsyncSize,
1697 maxInitialSize: oldMaxSizeSettings
1698 ? combineSizes(
1699 oldMaxSizeSettings.maxInitialSize,
1700 item.cacheGroup.maxInitialSize,
1701 Math.min
1702 )
1703 : item.cacheGroup.maxInitialSize,
1704 automaticNameDelimiter: item.cacheGroup.automaticNameDelimiter,
1705 keys: oldMaxSizeSettings
1706 ? [...oldMaxSizeSettings.keys, item.cacheGroup.key]
1707 : [item.cacheGroup.key]
1708 });
1709 }
1710
1711 // remove all modules from other entries and update size
1712 for (const [key, info] of chunksInfoMap) {
1713 if (isOverlap(info.chunks, usedChunks)) {
1714 // update modules and total size
1715 // may remove it from the map when < minSize
1716 let updated = false;
1717 for (const module of item.modules) {
1718 if (info.modules.has(module)) {
1719 // remove module
1720 info.modules.delete(module);
1721 // update size
1722 for (const key of module.getSourceTypes()) {
1723 info.sizes[key] -= module.size(key);
1724 }
1725 updated = true;
1726 }
1727 }
1728 if (updated) {
1729 if (info.modules.size === 0) {
1730 chunksInfoMap.delete(key);
1731 continue;
1732 }
1733 if (
1734 removeMinSizeViolatingModules(info) ||
1735 !checkMinSizeReduction(
1736 info.sizes,
1737 info.cacheGroup.minSizeReduction,
1738 info.chunks.size
1739 )
1740 ) {
1741 chunksInfoMap.delete(key);
1742 continue;
1743 }
1744 }
1745 }
1746 }
1747 }
1748
1749 logger.timeEnd("queue");
1750
1751 logger.time("maxSize");
1752
1753 /** @type {Set<string>} */
1754 const incorrectMinMaxSizeSet = new Set();
1755
1756 const { outputOptions } = compilation;
1757
1758 // Make sure that maxSize is fulfilled
1759 const { fallbackCacheGroup } = this.options;
1760 for (const chunk of compilation.chunks) {
1761 const chunkConfig = maxSizeQueueMap.get(chunk);
1762 const {
1763 minSize,
1764 maxAsyncSize,
1765 maxInitialSize,
1766 automaticNameDelimiter
1767 } = chunkConfig || fallbackCacheGroup;
1768 if (!chunkConfig && !fallbackCacheGroup.chunksFilter(chunk)) {
1769 continue;
1770 }
1771 /** @type {SplitChunksSizes} */
1772 let maxSize;
1773 if (chunk.isOnlyInitial()) {
1774 maxSize = maxInitialSize;
1775 } else if (chunk.canBeInitial()) {
1776 maxSize = combineSizes(maxAsyncSize, maxInitialSize, Math.min);
1777 } else {
1778 maxSize = maxAsyncSize;
1779 }
1780 if (Object.keys(maxSize).length === 0) {
1781 continue;
1782 }
1783 for (const key of /** @type {SourceType[]} */ (
1784 Object.keys(maxSize)
1785 )) {
1786 const maxSizeValue = maxSize[key];
1787 const minSizeValue = minSize[key];
1788 if (
1789 typeof minSizeValue === "number" &&
1790 minSizeValue > maxSizeValue
1791 ) {
1792 const keys = chunkConfig && chunkConfig.keys;
1793 const warningKey = `${
1794 keys && keys.join()
1795 } ${minSizeValue} ${maxSizeValue}`;
1796 if (!incorrectMinMaxSizeSet.has(warningKey)) {
1797 incorrectMinMaxSizeSet.add(warningKey);
1798 compilation.warnings.push(
1799 new MinMaxSizeWarning(keys, minSizeValue, maxSizeValue)
1800 );
1801 }
1802 }
1803 }
1804 const results = deterministicGroupingForModules({
1805 minSize,
1806 maxSize: mapObject(maxSize, (value, key) => {
1807 const minSizeValue = minSize[key];
1808 return typeof minSizeValue === "number"
1809 ? Math.max(value, minSizeValue)
1810 : value;
1811 }),
1812 items: chunkGraph.getChunkModulesIterable(chunk),
1813 getKey(module) {
1814 const cache = getKeyCache.get(module);
1815 if (cache !== undefined) return cache;
1816 const ident = cachedMakePathsRelative(module.identifier());
1817 const nameForCondition =
1818 module.nameForCondition && module.nameForCondition();
1819 const name = nameForCondition
1820 ? cachedMakePathsRelative(nameForCondition)
1821 : ident.replace(/^.*!|\?[^?!]*$/g, "");
1822 const fullKey =
1823 name +
1824 automaticNameDelimiter +
1825 hashFilename(ident, outputOptions);
1826 const key = requestToId(fullKey);
1827 getKeyCache.set(module, key);
1828 return key;
1829 },
1830 getSize(module) {
1831 /** @type {Sizes} */
1832 const size = Object.create(null);
1833 for (const key of module.getSourceTypes()) {
1834 size[key] = module.size(key);
1835 }
1836 return size;
1837 }
1838 });
1839 if (results.length <= 1) {
1840 continue;
1841 }
1842 for (let i = 0; i < results.length; i++) {
1843 const group = results[i];
1844 const key = this.options.hidePathInfo
1845 ? hashFilename(group.key, outputOptions)
1846 : group.key;
1847 let name = chunk.name
1848 ? chunk.name + automaticNameDelimiter + key
1849 : null;
1850 if (name && name.length > 100) {
1851 name =
1852 name.slice(0, 100) +
1853 automaticNameDelimiter +
1854 hashFilename(name, outputOptions);
1855 }
1856 if (i !== results.length - 1) {
1857 const newPart = compilation.addChunk(name);
1858 chunk.split(newPart);
1859 newPart.chunkReason = chunk.chunkReason;
1860 if (chunk.filenameTemplate) {
1861 newPart.filenameTemplate = chunk.filenameTemplate;
1862 }
1863 // Add all modules to the new chunk
1864 for (const module of group.items) {
1865 if (!module.chunkCondition(newPart, compilation)) {
1866 continue;
1867 }
1868 // Add module to new chunk
1869 chunkGraph.connectChunkAndModule(newPart, module);
1870 // Remove module from used chunks
1871 chunkGraph.disconnectChunkAndModule(chunk, module);
1872 }
1873 } else {
1874 // change the chunk to be a part
1875 chunk.name = name;
1876 }
1877 }
1878 }
1879 logger.timeEnd("maxSize");
1880 }
1881 );
1882 });
1883 }
1884};
Note: See TracBrowser for help on using the repository browser.