| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 | 
							- /*
 
- 	MIT License http://www.opensource.org/licenses/mit-license.php
 
- 	Author Tobias Koppers @sokra
 
- */
 
- "use strict";
 
- const validateOptions = require("schema-utils");
 
- const schema = require("../../schemas/plugins/optimize/LimitChunkCountPlugin.json");
 
- const LazyBucketSortedSet = require("../util/LazyBucketSortedSet");
 
- /** @typedef {import("../../declarations/plugins/optimize/LimitChunkCountPlugin").LimitChunkCountPluginOptions} LimitChunkCountPluginOptions */
 
- /** @typedef {import("../Chunk")} Chunk */
 
- /** @typedef {import("../Compiler")} Compiler */
 
- /**
 
-  * @typedef {Object} ChunkCombination
 
-  * @property {boolean} deleted this is set to true when combination was removed
 
-  * @property {number} sizeDiff
 
-  * @property {number} integratedSize
 
-  * @property {Chunk} a
 
-  * @property {Chunk} b
 
-  * @property {number} aIdx
 
-  * @property {number} bIdx
 
-  * @property {number} aSize
 
-  * @property {number} bSize
 
-  */
 
- const addToSetMap = (map, key, value) => {
 
- 	const set = map.get(key);
 
- 	if (set === undefined) {
 
- 		map.set(key, new Set([value]));
 
- 	} else {
 
- 		set.add(value);
 
- 	}
 
- };
 
- class LimitChunkCountPlugin {
 
- 	/**
 
- 	 * @param {LimitChunkCountPluginOptions=} options options object
 
- 	 */
 
- 	constructor(options) {
 
- 		if (!options) options = {};
 
- 		validateOptions(schema, options, "Limit Chunk Count Plugin");
 
- 		this.options = options;
 
- 	}
 
- 	/**
 
- 	 * @param {Compiler} compiler the webpack compiler
 
- 	 * @returns {void}
 
- 	 */
 
- 	apply(compiler) {
 
- 		const options = this.options;
 
- 		compiler.hooks.compilation.tap("LimitChunkCountPlugin", compilation => {
 
- 			compilation.hooks.optimizeChunksAdvanced.tap(
 
- 				"LimitChunkCountPlugin",
 
- 				chunks => {
 
- 					const maxChunks = options.maxChunks;
 
- 					if (!maxChunks) return;
 
- 					if (maxChunks < 1) return;
 
- 					if (chunks.length <= maxChunks) return;
 
- 					let remainingChunksToMerge = chunks.length - maxChunks;
 
- 					// order chunks in a deterministic way
 
- 					const orderedChunks = chunks.slice().sort((a, b) => a.compareTo(b));
 
- 					// create a lazy sorted data structure to keep all combinations
 
- 					// this is large. Size = chunks * (chunks - 1) / 2
 
- 					// It uses a multi layer bucket sort plus normal sort in the last layer
 
- 					// It's also lazy so only accessed buckets are sorted
 
- 					const combinations = new LazyBucketSortedSet(
 
- 						// Layer 1: ordered by largest size benefit
 
- 						c => c.sizeDiff,
 
- 						(a, b) => b - a,
 
- 						// Layer 2: ordered by smallest combined size
 
- 						c => c.integratedSize,
 
- 						(a, b) => a - b,
 
- 						// Layer 3: ordered by position difference in orderedChunk (-> to be deterministic)
 
- 						c => c.bIdx - c.aIdx,
 
- 						(a, b) => a - b,
 
- 						// Layer 4: ordered by position in orderedChunk (-> to be deterministic)
 
- 						(a, b) => a.bIdx - b.bIdx
 
- 					);
 
- 					// we keep a mappng from chunk to all combinations
 
- 					// but this mapping is not kept up-to-date with deletions
 
- 					// so `deleted` flag need to be considered when iterating this
 
- 					/** @type {Map<Chunk, Set<ChunkCombination>>} */
 
- 					const combinationsByChunk = new Map();
 
- 					orderedChunks.forEach((b, bIdx) => {
 
- 						// create combination pairs with size and integrated size
 
- 						for (let aIdx = 0; aIdx < bIdx; aIdx++) {
 
- 							const a = orderedChunks[aIdx];
 
- 							const integratedSize = a.integratedSize(b, options);
 
- 							// filter pairs that do not have an integratedSize
 
- 							// meaning they can NOT be integrated!
 
- 							if (integratedSize === false) continue;
 
- 							const aSize = a.size(options);
 
- 							const bSize = b.size(options);
 
- 							const c = {
 
- 								deleted: false,
 
- 								sizeDiff: aSize + bSize - integratedSize,
 
- 								integratedSize,
 
- 								a,
 
- 								b,
 
- 								aIdx,
 
- 								bIdx,
 
- 								aSize,
 
- 								bSize
 
- 							};
 
- 							combinations.add(c);
 
- 							addToSetMap(combinationsByChunk, a, c);
 
- 							addToSetMap(combinationsByChunk, b, c);
 
- 						}
 
- 						return combinations;
 
- 					});
 
- 					// list of modified chunks during this run
 
- 					// combinations affected by this change are skipped to allow
 
- 					// futher optimizations
 
- 					/** @type {Set<Chunk>} */
 
- 					const modifiedChunks = new Set();
 
- 					let changed = false;
 
- 					// eslint-disable-next-line no-constant-condition
 
- 					loop: while (true) {
 
- 						const combination = combinations.popFirst();
 
- 						if (combination === undefined) break;
 
- 						combination.deleted = true;
 
- 						const { a, b, integratedSize } = combination;
 
- 						// skip over pair when
 
- 						// one of the already merged chunks is a parent of one of the chunks
 
- 						if (modifiedChunks.size > 0) {
 
- 							const queue = new Set(a.groupsIterable);
 
- 							for (const group of b.groupsIterable) {
 
- 								queue.add(group);
 
- 							}
 
- 							for (const group of queue) {
 
- 								for (const mChunk of modifiedChunks) {
 
- 									if (mChunk !== a && mChunk !== b && mChunk.isInGroup(group)) {
 
- 										// This is a potential pair which needs recalculation
 
- 										// We can't do that now, but it merge before following pairs
 
- 										// so we leave space for it, and consider chunks as modified
 
- 										// just for the worse case
 
- 										remainingChunksToMerge--;
 
- 										if (remainingChunksToMerge <= 0) break loop;
 
- 										modifiedChunks.add(a);
 
- 										modifiedChunks.add(b);
 
- 										continue loop;
 
- 									}
 
- 								}
 
- 								for (const parent of group.parentsIterable) {
 
- 									queue.add(parent);
 
- 								}
 
- 							}
 
- 						}
 
- 						// merge the chunks
 
- 						if (a.integrate(b, "limit")) {
 
- 							chunks.splice(chunks.indexOf(b), 1);
 
- 							// flag chunk a as modified as further optimization are possible for all children here
 
- 							modifiedChunks.add(a);
 
- 							changed = true;
 
- 							remainingChunksToMerge--;
 
- 							if (remainingChunksToMerge <= 0) break;
 
- 							// Update all affected combinations
 
- 							// delete all combination with the removed chunk
 
- 							// we will use combinations with the kept chunk instead
 
- 							for (const combination of combinationsByChunk.get(b)) {
 
- 								if (combination.deleted) continue;
 
- 								combination.deleted = true;
 
- 								combinations.delete(combination);
 
- 							}
 
- 							// Update combinations with the kept chunk with new sizes
 
- 							for (const combination of combinationsByChunk.get(a)) {
 
- 								if (combination.deleted) continue;
 
- 								if (combination.a === a) {
 
- 									// Update size
 
- 									const newIntegratedSize = a.integratedSize(
 
- 										combination.b,
 
- 										options
 
- 									);
 
- 									if (newIntegratedSize === false) {
 
- 										combination.deleted = true;
 
- 										combinations.delete(combination);
 
- 										continue;
 
- 									}
 
- 									const finishUpdate = combinations.startUpdate(combination);
 
- 									combination.integratedSize = newIntegratedSize;
 
- 									combination.aSize = integratedSize;
 
- 									combination.sizeDiff =
 
- 										combination.bSize + integratedSize - newIntegratedSize;
 
- 									finishUpdate();
 
- 								} else if (combination.b === a) {
 
- 									// Update size
 
- 									const newIntegratedSize = combination.a.integratedSize(
 
- 										a,
 
- 										options
 
- 									);
 
- 									if (newIntegratedSize === false) {
 
- 										combination.deleted = true;
 
- 										combinations.delete(combination);
 
- 										continue;
 
- 									}
 
- 									const finishUpdate = combinations.startUpdate(combination);
 
- 									combination.integratedSize = newIntegratedSize;
 
- 									combination.bSize = integratedSize;
 
- 									combination.sizeDiff =
 
- 										integratedSize + combination.aSize - newIntegratedSize;
 
- 									finishUpdate();
 
- 								}
 
- 							}
 
- 						}
 
- 					}
 
- 					if (changed) return true;
 
- 				}
 
- 			);
 
- 		});
 
- 	}
 
- }
 
- module.exports = LimitChunkCountPlugin;
 
 
  |