source: trip-planner-front/node_modules/webpack/lib/optimize/SplitChunksPlugin.js@ 8d391a1

Last change on this file since 8d391a1 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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