| 
									
										
										
										
											2013-01-31 01:49:25 +08:00
										 |  |  | /* | 
					
						
							|  |  |  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
					
						
							|  |  |  | 	Author Tobias Koppers @sokra | 
					
						
							|  |  |  | */ | 
					
						
							| 
									
										
										
										
											2018-07-30 23:08:51 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-05 12:17:12 +08:00
										 |  |  | "use strict"; | 
					
						
							| 
									
										
										
										
											2013-01-31 01:49:25 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-29 17:19:28 +08:00
										 |  |  | const validateOptions = require("schema-utils"); | 
					
						
							| 
									
										
										
										
											2017-11-11 20:05:55 +08:00
										 |  |  | const schema = require("../../schemas/plugins/optimize/LimitChunkCountPlugin.json"); | 
					
						
							| 
									
										
										
										
											2018-07-31 04:30:27 +08:00
										 |  |  | const { STAGE_ADVANCED } = require("../OptimizationStages"); | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | const LazyBucketSortedSet = require("../util/LazyBucketSortedSet"); | 
					
						
							| 
									
										
										
										
											2018-10-17 23:14:50 +08:00
										 |  |  | const { compareChunks } = require("../util/comparators"); | 
					
						
							| 
									
										
										
										
											2017-10-28 05:23:38 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-14 17:18:22 +08:00
										 |  |  | /** @typedef {import("../Chunk")} Chunk */ | 
					
						
							|  |  |  | /** @typedef {import("../Compiler")} Compiler */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-20 16:13:55 +08:00
										 |  |  | /** @typedef {import("../../declarations/plugins/optimize/LimitChunkCountPlugin").LimitChunkCountPluginOptions} LimitChunkCountPluginOptions */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @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); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2018-09-20 16:13:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-05 12:17:12 +08:00
										 |  |  | class LimitChunkCountPlugin { | 
					
						
							| 
									
										
										
										
											2018-09-20 16:13:55 +08:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {LimitChunkCountPluginOptions=} options options object | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2019-09-25 22:02:57 +08:00
										 |  |  | 	constructor(options) { | 
					
						
							| 
									
										
										
										
											2019-08-07 21:55:03 +08:00
										 |  |  | 		validateOptions(schema, options, { | 
					
						
							|  |  |  | 			name: "Limit Chunk Count Plugin", | 
					
						
							|  |  |  | 			baseDataPath: "options" | 
					
						
							|  |  |  | 		}); | 
					
						
							| 
									
										
										
										
											2018-09-20 16:13:55 +08:00
										 |  |  | 		this.options = options; | 
					
						
							| 
									
										
										
										
											2017-01-05 12:17:12 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-08-14 17:18:22 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 	 * @param {Compiler} compiler the webpack compiler | 
					
						
							| 
									
										
										
										
											2018-08-14 17:18:22 +08:00
										 |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2017-01-05 12:17:12 +08:00
										 |  |  | 	apply(compiler) { | 
					
						
							|  |  |  | 		const options = this.options; | 
					
						
							| 
									
										
										
										
											2018-02-25 09:00:20 +08:00
										 |  |  | 		compiler.hooks.compilation.tap("LimitChunkCountPlugin", compilation => { | 
					
						
							| 
									
										
										
										
											2018-07-31 04:30:27 +08:00
										 |  |  | 			compilation.hooks.optimizeChunks.tap( | 
					
						
							| 
									
										
										
										
											2018-12-09 19:54:17 +08:00
										 |  |  | 				{ | 
					
						
							| 
									
										
										
										
											2018-07-31 04:30:27 +08:00
										 |  |  | 					name: "LimitChunkCountPlugin", | 
					
						
							|  |  |  | 					stage: STAGE_ADVANCED | 
					
						
							| 
									
										
										
										
											2018-12-09 19:54:17 +08:00
										 |  |  | 				}, | 
					
						
							| 
									
										
										
										
											2018-02-25 09:00:20 +08:00
										 |  |  | 				chunks => { | 
					
						
							| 
									
										
										
										
											2018-08-14 17:18:22 +08:00
										 |  |  | 					const chunkGraph = compilation.chunkGraph; | 
					
						
							| 
									
										
										
										
											2018-02-25 09:00:20 +08:00
										 |  |  | 					const maxChunks = options.maxChunks; | 
					
						
							|  |  |  | 					if (!maxChunks) return; | 
					
						
							|  |  |  | 					if (maxChunks < 1) return; | 
					
						
							| 
									
										
										
										
											2018-09-06 22:59:11 +08:00
										 |  |  | 					if (compilation.chunks.size <= maxChunks) return; | 
					
						
							| 
									
										
										
										
											2013-01-31 01:49:25 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-26 06:11:26 +08:00
										 |  |  | 					let remainingChunksToMerge = compilation.chunks.size - maxChunks; | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					// order chunks in a deterministic way
 | 
					
						
							| 
									
										
										
										
											2018-10-17 23:14:50 +08:00
										 |  |  | 					const compareChunksWithGraph = compareChunks(chunkGraph); | 
					
						
							|  |  |  | 					const orderedChunks = Array.from(chunks).sort(compareChunksWithGraph); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 					// 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]; | 
					
						
							| 
									
										
										
										
											2019-09-26 06:11:26 +08:00
										 |  |  | 							// filter pairs that can not be integrated!
 | 
					
						
							|  |  |  | 							if (!chunkGraph.canChunksBeIntegrated(a, b)) continue; | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-26 06:11:26 +08:00
										 |  |  | 							const integratedSize = chunkGraph.getIntegratedChunksSize( | 
					
						
							|  |  |  | 								a, | 
					
						
							|  |  |  | 								b, | 
					
						
							| 
									
										
										
										
											2018-08-14 17:18:22 +08:00
										 |  |  | 								options | 
					
						
							|  |  |  | 							); | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-26 06:11:26 +08:00
										 |  |  | 							const aSize = chunkGraph.getChunkSize(a, options); | 
					
						
							|  |  |  | 							const bSize = chunkGraph.getChunkSize(b, options); | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 							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")) { | 
					
						
							| 
									
										
										
										
											2019-09-26 06:11:26 +08:00
										 |  |  | 							compilation.chunks.delete(b); | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 							// 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
 | 
					
						
							| 
									
										
										
										
											2019-09-26 06:11:26 +08:00
										 |  |  | 							for (const combination of combinationsByChunk.get(a)) { | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 								if (combination.deleted) continue; | 
					
						
							|  |  |  | 								combination.deleted = true; | 
					
						
							|  |  |  | 								combinations.delete(combination); | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 							// Update combinations with the kept chunk with new sizes
 | 
					
						
							| 
									
										
										
										
											2019-09-26 06:11:26 +08:00
										 |  |  | 							for (const combination of combinationsByChunk.get(b)) { | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 								if (combination.deleted) continue; | 
					
						
							| 
									
										
										
										
											2019-09-26 06:11:26 +08:00
										 |  |  | 								if (combination.a === b) { | 
					
						
							|  |  |  | 									if (!chunkGraph.canChunksBeIntegrated(a, combination.b)) { | 
					
						
							|  |  |  | 										combination.deleted = true; | 
					
						
							|  |  |  | 										combinations.delete(combination); | 
					
						
							|  |  |  | 										continue; | 
					
						
							|  |  |  | 									} | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 									// Update size
 | 
					
						
							|  |  |  | 									const newIntegratedSize = a.integratedSize( | 
					
						
							|  |  |  | 										combination.b, | 
					
						
							|  |  |  | 										options | 
					
						
							|  |  |  | 									); | 
					
						
							|  |  |  | 									const finishUpdate = combinations.startUpdate(combination); | 
					
						
							| 
									
										
										
										
											2019-09-26 06:11:26 +08:00
										 |  |  | 									combination.a = a; | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 									combination.integratedSize = newIntegratedSize; | 
					
						
							|  |  |  | 									combination.aSize = integratedSize; | 
					
						
							|  |  |  | 									combination.sizeDiff = | 
					
						
							|  |  |  | 										combination.bSize + integratedSize - newIntegratedSize; | 
					
						
							|  |  |  | 									finishUpdate(); | 
					
						
							| 
									
										
										
										
											2019-09-26 06:11:26 +08:00
										 |  |  | 								} else if (combination.b === b) { | 
					
						
							|  |  |  | 									if (!chunkGraph.canChunksBeIntegrated(combination.a, a)) { | 
					
						
							|  |  |  | 										combination.deleted = true; | 
					
						
							|  |  |  | 										combinations.delete(combination); | 
					
						
							|  |  |  | 										continue; | 
					
						
							|  |  |  | 									} | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 									// Update size
 | 
					
						
							|  |  |  | 									const newIntegratedSize = combination.a.integratedSize( | 
					
						
							|  |  |  | 										a, | 
					
						
							|  |  |  | 										options | 
					
						
							|  |  |  | 									); | 
					
						
							|  |  |  | 									const finishUpdate = combinations.startUpdate(combination); | 
					
						
							| 
									
										
										
										
											2019-09-26 06:11:26 +08:00
										 |  |  | 									combination.b = a; | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 									combination.integratedSize = newIntegratedSize; | 
					
						
							|  |  |  | 									combination.bSize = integratedSize; | 
					
						
							|  |  |  | 									combination.sizeDiff = | 
					
						
							|  |  |  | 										integratedSize + combination.aSize - newIntegratedSize; | 
					
						
							|  |  |  | 									finishUpdate(); | 
					
						
							|  |  |  | 								} | 
					
						
							|  |  |  | 							} | 
					
						
							| 
									
										
										
										
											2019-09-26 06:11:26 +08:00
										 |  |  | 							combinationsByChunk.set(a, combinationsByChunk.get(b)); | 
					
						
							|  |  |  | 							combinationsByChunk.delete(b); | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 						} | 
					
						
							| 
									
										
										
										
											2018-02-25 09:00:20 +08:00
										 |  |  | 					} | 
					
						
							| 
									
										
										
										
											2019-09-24 22:16:36 +08:00
										 |  |  | 					if (changed) return true; | 
					
						
							| 
									
										
										
										
											2014-02-04 01:12:19 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-02-25 09:00:20 +08:00
										 |  |  | 			); | 
					
						
							| 
									
										
										
										
											2013-01-31 01:49:25 +08:00
										 |  |  | 		}); | 
					
						
							| 
									
										
										
										
											2017-01-05 12:17:12 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | module.exports = LimitChunkCountPlugin; |