| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | /* | 
					
						
							|  |  |  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
					
						
							|  |  |  | 	Author Tobias Koppers @sokra | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-01 02:52:50 +08:00
										 |  |  | const crypto = require("crypto"); | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | const SortableSet = require("../util/SortableSet"); | 
					
						
							|  |  |  | const GraphHelpers = require("../GraphHelpers"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-01 02:52:50 +08:00
										 |  |  | const hashFilename = (name) => { | 
					
						
							|  |  |  | 	return crypto.createHash("md5").update(name).digest("hex").slice(0, 8); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | const sortByIdentifier = (a, b) => { | 
					
						
							|  |  |  | 	if(a.identifier() > b.identifier()) return 1; | 
					
						
							|  |  |  | 	if(a.identifier() < b.identifier()) return -1; | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const getRequests = chunk => { | 
					
						
							|  |  |  | 	let requests = 0; | 
					
						
							|  |  |  | 	for(const chunkGroup of chunk.groupsIterable) { | 
					
						
							|  |  |  | 		requests = Math.max(requests, chunkGroup.chunks.length); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return requests; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const getModulesSize = modules => { | 
					
						
							|  |  |  | 	let sum = 0; | 
					
						
							|  |  |  | 	for(const m of modules) | 
					
						
							|  |  |  | 		sum += m.size(); | 
					
						
							|  |  |  | 	return sum; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const isOverlap = (a, b) => { | 
					
						
							| 
									
										
										
										
											2018-02-02 01:21:52 +08:00
										 |  |  | 	for(const item of a.keys()) { | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 		if(b.has(item)) return true; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-16 16:12:01 +08:00
										 |  |  | const getCombinations = (array, maxDepth) => { | 
					
						
							|  |  |  | 	const results = []; | 
					
						
							|  |  |  | 	getCombinationsInteral(array, maxDepth, results); | 
					
						
							|  |  |  | 	return results; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const getCombinationsInteral = (array, maxDepth, results) => { | 
					
						
							|  |  |  | 	results.push(array); | 
					
						
							|  |  |  | 	if(maxDepth === 0 || array.length === 1) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for(let i = 0; i < array.length; i++) { | 
					
						
							|  |  |  | 		const newArray = array.slice(0, i).concat(array.slice(i + 1, array.length)); | 
					
						
							|  |  |  | 		getCombinationsInteral(newArray, maxDepth - 1, results); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | const compareEntries = (a, b) => { | 
					
						
							| 
									
										
										
										
											2018-01-20 20:11:02 +08:00
										 |  |  | 	// 1. by priority
 | 
					
						
							|  |  |  | 	const diffPriority = a.cacheGroup.priority - b.cacheGroup.priority; | 
					
						
							|  |  |  | 	if(diffPriority) return diffPriority; | 
					
						
							| 
									
										
										
										
											2018-02-16 16:12:01 +08:00
										 |  |  | 	// 2. by number of chunks
 | 
					
						
							|  |  |  | 	const diffCount = a.chunks.size - b.chunks.size; | 
					
						
							|  |  |  | 	if(diffCount) return diffCount; | 
					
						
							|  |  |  | 	// 3. by size reduction
 | 
					
						
							|  |  |  | 	const aSizeReduce = a.size * (a.chunks.size - 1); | 
					
						
							|  |  |  | 	const bSizeReduce = b.size * (b.chunks.size - 1); | 
					
						
							|  |  |  | 	const diffSizeReduce = aSizeReduce - bSizeReduce; | 
					
						
							|  |  |  | 	if(diffSizeReduce) return diffSizeReduce; | 
					
						
							|  |  |  | 	// 4. by number of modules (to be able to compare by identifier)
 | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 	const modulesA = a.modules; | 
					
						
							|  |  |  | 	const modulesB = b.modules; | 
					
						
							|  |  |  | 	const diff = modulesA.size - modulesB.size; | 
					
						
							|  |  |  | 	if(diff) return diff; | 
					
						
							| 
									
										
										
										
											2018-02-16 16:12:01 +08:00
										 |  |  | 	// 5. by module identifiers
 | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 	modulesA.sort(); | 
					
						
							|  |  |  | 	modulesB.sort(); | 
					
						
							|  |  |  | 	const aI = modulesA[Symbol.iterator](); | 
					
						
							|  |  |  | 	const bI = modulesB[Symbol.iterator](); | 
					
						
							|  |  |  | 	while(true) { // eslint-disable-line
 | 
					
						
							|  |  |  | 		const aItem = aI.next(); | 
					
						
							|  |  |  | 		const bItem = bI.next(); | 
					
						
							|  |  |  | 		if(aItem.done) return 0; | 
					
						
							|  |  |  | 		const aModuleIdentifier = aItem.value.identifier(); | 
					
						
							|  |  |  | 		const bModuleIdentifier = bItem.value.identifier(); | 
					
						
							|  |  |  | 		if(aModuleIdentifier > bModuleIdentifier) return -1; | 
					
						
							|  |  |  | 		if(aModuleIdentifier < bModuleIdentifier) return 1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = class SplitChunksPlugin { | 
					
						
							|  |  |  | 	constructor(options) { | 
					
						
							|  |  |  | 		this.options = SplitChunksPlugin.normalizeOptions(options); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	static normalizeOptions(options) { | 
					
						
							|  |  |  | 		return { | 
					
						
							| 
									
										
										
										
											2018-02-16 16:12:01 +08:00
										 |  |  | 			maxComplexity: options.maxComplexity || 0, | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 			chunks: options.chunks || "all", | 
					
						
							|  |  |  | 			minSize: options.minSize || 0, | 
					
						
							|  |  |  | 			minChunks: options.minChunks || 1, | 
					
						
							|  |  |  | 			maxAsyncRequests: options.maxAsyncRequests || 1, | 
					
						
							|  |  |  | 			maxInitialRequests: options.maxInitialRequests || 1, | 
					
						
							|  |  |  | 			getName: SplitChunksPlugin.normalizeName(options.name) || (() => {}), | 
					
						
							|  |  |  | 			getCacheGroups: SplitChunksPlugin.normalizeCacheGroups(options.cacheGroups), | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	static normalizeName(option) { | 
					
						
							|  |  |  | 		if(option === true) { | 
					
						
							|  |  |  | 			const fn = (module, chunks, cacheGroup) => { | 
					
						
							|  |  |  | 				const names = chunks.map(c => c.name); | 
					
						
							|  |  |  | 				if(!names.every(Boolean)) return; | 
					
						
							|  |  |  | 				names.sort(); | 
					
						
							| 
									
										
										
										
											2018-02-01 02:52:50 +08:00
										 |  |  | 				let name = (cacheGroup && cacheGroup !== "default" ? cacheGroup + "~" : "") + names.join("~"); | 
					
						
							|  |  |  | 				// Filenames and paths can't be too long otherwise an
 | 
					
						
							|  |  |  | 				// ENAMETOOLONG error is raised. If the generated name if too
 | 
					
						
							|  |  |  | 				// long, it is truncated and a hash is appended. The limit has
 | 
					
						
							|  |  |  | 				// been set to 100 to prevent `[name].[chunkhash].[ext]` from
 | 
					
						
							|  |  |  | 				// generating a 256+ character string.
 | 
					
						
							|  |  |  | 				if(name.length > 100) { | 
					
						
							|  |  |  | 					name = name.slice(0, 100) + "~" + hashFilename(name); | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 				return name; | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 			return fn; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if(typeof option === "string") { | 
					
						
							|  |  |  | 			const fn = () => { | 
					
						
							|  |  |  | 				return option; | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 			return fn; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if(typeof option === "function") | 
					
						
							|  |  |  | 			return option; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	static normalizeCacheGroups(cacheGroups) { | 
					
						
							|  |  |  | 		if(typeof cacheGroups === "function") { | 
					
						
							|  |  |  | 			return cacheGroups; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if(cacheGroups && typeof cacheGroups === "object") { | 
					
						
							|  |  |  | 			const fn = (module, chunks) => { | 
					
						
							|  |  |  | 				let results; | 
					
						
							|  |  |  | 				for(const key of Object.keys(cacheGroups)) { | 
					
						
							|  |  |  | 					let option = cacheGroups[key]; | 
					
						
							| 
									
										
										
										
											2018-01-20 20:11:02 +08:00
										 |  |  | 					if(option === false) | 
					
						
							|  |  |  | 						continue; | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 					if(option instanceof RegExp || typeof option === "string") { | 
					
						
							|  |  |  | 						option = { | 
					
						
							|  |  |  | 							test: option | 
					
						
							|  |  |  | 						}; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					if(typeof option === "function") { | 
					
						
							|  |  |  | 						let result = option(module); | 
					
						
							|  |  |  | 						if(result) { | 
					
						
							|  |  |  | 							if(results === undefined) results = []; | 
					
						
							|  |  |  | 							for(const r of (Array.isArray(result) ? result : [result])) { | 
					
						
							|  |  |  | 								const result = Object.assign({ | 
					
						
							|  |  |  | 									key, | 
					
						
							|  |  |  | 								}, r); | 
					
						
							|  |  |  | 								if(result.name) result.getName = () => result.name; | 
					
						
							|  |  |  | 								results.push(result); | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} else if(SplitChunksPlugin.checkTest(option.test, module, chunks)) { | 
					
						
							|  |  |  | 						if(results === undefined) results = []; | 
					
						
							|  |  |  | 						results.push({ | 
					
						
							|  |  |  | 							key: key, | 
					
						
							| 
									
										
										
										
											2018-01-20 20:11:02 +08:00
										 |  |  | 							priority: option.priority, | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 							getName: SplitChunksPlugin.normalizeName(option.name), | 
					
						
							|  |  |  | 							chunks: option.chunks, | 
					
						
							|  |  |  | 							enforce: option.enforce, | 
					
						
							|  |  |  | 							minSize: option.minSize, | 
					
						
							|  |  |  | 							minChunks: option.minChunks, | 
					
						
							|  |  |  | 							maxAsyncRequests: option.maxAsyncRequests, | 
					
						
							|  |  |  | 							maxInitialRequests: option.maxInitialRequests, | 
					
						
							|  |  |  | 							reuseExistingChunk: option.reuseExistingChunk | 
					
						
							|  |  |  | 						}); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return results; | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 			return fn; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		const fn = () => {}; | 
					
						
							|  |  |  | 		return fn; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	static checkTest(test, module, chunks) { | 
					
						
							|  |  |  | 		if(test === undefined) | 
					
						
							|  |  |  | 			return true; | 
					
						
							|  |  |  | 		if(typeof test === "function") | 
					
						
							|  |  |  | 			return test(module, chunks); | 
					
						
							|  |  |  | 		if(typeof test === "boolean") | 
					
						
							|  |  |  | 			return test; | 
					
						
							|  |  |  | 		const names = chunks.map(c => c.name).concat(module.nameForCondition ? [module.nameForCondition()] : []).filter(Boolean); | 
					
						
							|  |  |  | 		if(typeof test === "string") { | 
					
						
							|  |  |  | 			for(const name of names) | 
					
						
							|  |  |  | 				if(name.startsWith(test)) | 
					
						
							|  |  |  | 					return true; | 
					
						
							|  |  |  | 			return false; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if(test instanceof RegExp) { | 
					
						
							|  |  |  | 			for(const name of names) | 
					
						
							|  |  |  | 				if(test.test(name)) | 
					
						
							|  |  |  | 					return true; | 
					
						
							|  |  |  | 			return false; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	apply(compiler) { | 
					
						
							| 
									
										
										
										
											2018-01-24 19:00:50 +08:00
										 |  |  | 		compiler.hooks.thisCompilation.tap("SplitChunksPlugin", compilation => { | 
					
						
							| 
									
										
										
										
											2018-01-20 20:11:02 +08:00
										 |  |  | 			let alreadyOptimized = false; | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 			compilation.hooks.unseal.tap("SplitChunksPlugin", () => { | 
					
						
							| 
									
										
										
										
											2018-01-20 20:11:02 +08:00
										 |  |  | 				alreadyOptimized = false; | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 			}); | 
					
						
							|  |  |  | 			compilation.hooks.optimizeChunksAdvanced.tap("SplitChunksPlugin", chunks => { | 
					
						
							| 
									
										
										
										
											2018-01-20 20:11:02 +08:00
										 |  |  | 				if(alreadyOptimized) return; | 
					
						
							|  |  |  | 				alreadyOptimized = true; | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 				// Give each selected chunk an index (to create strings from chunks)
 | 
					
						
							|  |  |  | 				const indexMap = new Map(); | 
					
						
							|  |  |  | 				let index = 1; | 
					
						
							|  |  |  | 				for(const chunk of chunks) { | 
					
						
							|  |  |  | 					indexMap.set(chunk, index++); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				// Map a list of chunks to a list of modules
 | 
					
						
							|  |  |  | 				// For the key the chunk "index" is used, the value is a SortableSet of modules
 | 
					
						
							|  |  |  | 				const chunksInfoMap = new Map(); | 
					
						
							|  |  |  | 				// Walk through all modules
 | 
					
						
							|  |  |  | 				for(const module of compilation.modules) { | 
					
						
							|  |  |  | 					// Get array of chunks
 | 
					
						
							|  |  |  | 					const chunks = module.getChunks(); | 
					
						
							|  |  |  | 					// Get cache group
 | 
					
						
							|  |  |  | 					let cacheGroups = this.options.getCacheGroups(module, chunks); | 
					
						
							| 
									
										
										
										
											2018-01-20 20:11:02 +08:00
										 |  |  | 					if(!Array.isArray(cacheGroups)) continue; | 
					
						
							|  |  |  | 					for(const cacheGroupSource of cacheGroups) { | 
					
						
							|  |  |  | 						const cacheGroup = { | 
					
						
							|  |  |  | 							key: cacheGroupSource.key, | 
					
						
							|  |  |  | 							priority: cacheGroupSource.priority || 0, | 
					
						
							|  |  |  | 							chunks: cacheGroupSource.chunks || this.options.chunks, | 
					
						
							|  |  |  | 							minSize: cacheGroupSource.minSize !== undefined ? cacheGroupSource.minSize : cacheGroupSource.enforce ? 0 : this.options.minSize, | 
					
						
							|  |  |  | 							minChunks: cacheGroupSource.minChunks !== undefined ? cacheGroupSource.minChunks : cacheGroupSource.enforce ? 1 : this.options.minChunks, | 
					
						
							|  |  |  | 							maxAsyncRequests: cacheGroupSource.maxAsyncRequests !== undefined ? cacheGroupSource.maxAsyncRequests : cacheGroupSource.enforce ? Infinity : this.options.maxAsyncRequests, | 
					
						
							|  |  |  | 							maxInitialRequests: cacheGroupSource.maxInitialRequests !== undefined ? cacheGroupSource.maxInitialRequests : cacheGroupSource.enforce ? Infinity : this.options.maxInitialRequests, | 
					
						
							|  |  |  | 							getName: cacheGroupSource.getName !== undefined ? cacheGroupSource.getName : this.options.getName, | 
					
						
							|  |  |  | 							reuseExistingChunk: cacheGroupSource.reuseExistingChunk | 
					
						
							|  |  |  | 						}; | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 						// Select chunks by configuration
 | 
					
						
							| 
									
										
										
										
											2018-02-16 16:12:01 +08:00
										 |  |  | 						const maxSelectedChunks = cacheGroup.chunks === "initial" ? chunks.filter(chunk => chunk.canBeInitial()) : | 
					
						
							| 
									
										
										
										
											2018-01-22 19:15:58 +08:00
										 |  |  | 							cacheGroup.chunks === "async" ? chunks.filter(chunk => !chunk.canBeInitial()) : | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 							chunks; | 
					
						
							| 
									
										
										
										
											2018-02-16 16:12:01 +08:00
										 |  |  | 						// For all (nearly all) combination of chunk selection
 | 
					
						
							|  |  |  | 						for(const selectedChunks of getCombinations(maxSelectedChunks, this.options.maxComplexity)) { | 
					
						
							|  |  |  | 							// Get indices of chunks in which this module occurs
 | 
					
						
							|  |  |  | 							const chunkIndices = selectedChunks.map(chunk => indexMap.get(chunk)); | 
					
						
							|  |  |  | 							// Break if minimum number of chunks is not reached
 | 
					
						
							|  |  |  | 							if(chunkIndices.length < cacheGroup.minChunks) | 
					
						
							|  |  |  | 								continue; | 
					
						
							|  |  |  | 							// Determine name for split chunk
 | 
					
						
							|  |  |  | 							const name = cacheGroup.getName(module, selectedChunks, cacheGroup.key); | 
					
						
							|  |  |  | 							// Create key for maps
 | 
					
						
							|  |  |  | 							// When it has a name we use the name as key
 | 
					
						
							|  |  |  | 							// Elsewise we create the key from chunks and cache group key
 | 
					
						
							|  |  |  | 							// This automatically merges equal names
 | 
					
						
							|  |  |  | 							const chunksKey = chunkIndices.sort().join(); | 
					
						
							|  |  |  | 							const key = name && `name:${name}` || | 
					
						
							|  |  |  | 								`chunks:${chunksKey} key:${cacheGroup.key}`; | 
					
						
							|  |  |  | 							// Add module to maps
 | 
					
						
							|  |  |  | 							let info = chunksInfoMap.get(key); | 
					
						
							|  |  |  | 							if(info === undefined) { | 
					
						
							|  |  |  | 								chunksInfoMap.set(key, info = { | 
					
						
							|  |  |  | 									modules: new SortableSet(undefined, sortByIdentifier), | 
					
						
							|  |  |  | 									cacheGroup, | 
					
						
							|  |  |  | 									name, | 
					
						
							|  |  |  | 									chunks: new Map(), | 
					
						
							|  |  |  | 									reusedableChunks: new Set(), | 
					
						
							|  |  |  | 									chunksKeys: new Set() | 
					
						
							|  |  |  | 								}); | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 							info.modules.add(module); | 
					
						
							|  |  |  | 							if(!info.chunksKeys.has(chunksKey)) { | 
					
						
							|  |  |  | 								info.chunksKeys.add(chunksKey); | 
					
						
							|  |  |  | 								for(const chunk of selectedChunks) { | 
					
						
							|  |  |  | 									info.chunks.set(chunk, chunk.getNumberOfModules()); | 
					
						
							|  |  |  | 								} | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 							} | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-02-16 16:12:01 +08:00
										 |  |  | 				for(const [key, info] of chunksInfoMap) { | 
					
						
							| 
									
										
										
										
											2018-01-20 20:11:02 +08:00
										 |  |  | 					// Get size of module lists
 | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 					info.size = getModulesSize(info.modules); | 
					
						
							| 
									
										
										
										
											2018-02-16 16:12:01 +08:00
										 |  |  | 					if(info.size < info.cacheGroup.minSize) { | 
					
						
							|  |  |  | 						chunksInfoMap.delete(key); | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				let changed = false; | 
					
						
							|  |  |  | 				while(chunksInfoMap.size > 0) { | 
					
						
							|  |  |  | 					// Find best matching entry
 | 
					
						
							|  |  |  | 					let bestEntryKey; | 
					
						
							|  |  |  | 					let bestEntry; | 
					
						
							|  |  |  | 					for(const pair of chunksInfoMap) { | 
					
						
							|  |  |  | 						const key = pair[0]; | 
					
						
							|  |  |  | 						const info = pair[1]; | 
					
						
							|  |  |  | 						if(bestEntry === undefined) { | 
					
						
							|  |  |  | 							bestEntry = info; | 
					
						
							|  |  |  | 							bestEntryKey = key; | 
					
						
							|  |  |  | 						} else if(compareEntries(bestEntry, info) < 0) { | 
					
						
							|  |  |  | 							bestEntry = info; | 
					
						
							|  |  |  | 							bestEntryKey = key; | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					const item = bestEntry; | 
					
						
							| 
									
										
										
										
											2018-02-16 16:12:01 +08:00
										 |  |  | 					chunksInfoMap.delete(bestEntryKey); | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					let chunkName = item.name; | 
					
						
							|  |  |  | 					// Variable for the new chunk (lazy created)
 | 
					
						
							|  |  |  | 					let newChunk; | 
					
						
							|  |  |  | 					// When no chunk name, check if we can reuse a chunk instead of creating a new one
 | 
					
						
							|  |  |  | 					let isReused = false; | 
					
						
							|  |  |  | 					if(item.cacheGroup.reuseExistingChunk) { | 
					
						
							|  |  |  | 						for(const pair of item.chunks) { | 
					
						
							|  |  |  | 							if(pair[1] === item.modules.size) { | 
					
						
							|  |  |  | 								const chunk = pair[0]; | 
					
						
							|  |  |  | 								if(chunk.hasEntryModule()) continue; | 
					
						
							|  |  |  | 								if(!newChunk || !newChunk.name) | 
					
						
							|  |  |  | 									newChunk = chunk; | 
					
						
							|  |  |  | 								else if(chunk.name && chunk.name.length < newChunk.name.length) | 
					
						
							|  |  |  | 									newChunk = chunk; | 
					
						
							|  |  |  | 								else if(chunk.name && chunk.name.length === newChunk.name.length && chunk.name < newChunk.name) | 
					
						
							|  |  |  | 									newChunk = chunk; | 
					
						
							|  |  |  | 								chunkName = undefined; | 
					
						
							|  |  |  | 								isReused = true; | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					// Walk through all chunks
 | 
					
						
							|  |  |  | 					for(const chunk of item.chunks.keys()) { | 
					
						
							|  |  |  | 						// skip if we address ourself
 | 
					
						
							|  |  |  | 						if(chunk.name === chunkName || chunk === newChunk) continue; | 
					
						
							|  |  |  | 						// respect max requests when not enforced
 | 
					
						
							| 
									
										
										
										
											2018-01-22 19:15:58 +08:00
										 |  |  | 						const maxRequests = chunk.isOnlyInitial() ? item.cacheGroup.maxInitialRequests : | 
					
						
							|  |  |  | 							chunk.canBeInitial() ? Math.min(item.cacheGroup.maxInitialRequests, item.cacheGroup.maxAsyncRequests) : | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 							item.cacheGroup.maxAsyncRequests; | 
					
						
							|  |  |  | 						if(isFinite(maxRequests) && getRequests(chunk) >= maxRequests) continue; | 
					
						
							|  |  |  | 						if(newChunk === undefined) { | 
					
						
							|  |  |  | 							// Create the new chunk
 | 
					
						
							|  |  |  | 							newChunk = compilation.addChunk(chunkName); | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						// Add graph connections for splitted chunk
 | 
					
						
							|  |  |  | 						chunk.split(newChunk); | 
					
						
							|  |  |  | 						// Remove all selected modules from the chunk
 | 
					
						
							|  |  |  | 						for(const module of item.modules) { | 
					
						
							|  |  |  | 							chunk.removeModule(module); | 
					
						
							|  |  |  | 							module.rewriteChunkInReasons(chunk, [newChunk]); | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					// If we successfully created a new chunk or reused one
 | 
					
						
							|  |  |  | 					if(newChunk) { | 
					
						
							|  |  |  | 						// Add a note to the chunk
 | 
					
						
							|  |  |  | 						newChunk.chunkReason = isReused ? "reused as split chunk" : "split chunk"; | 
					
						
							|  |  |  | 						if(item.cacheGroup.key) { | 
					
						
							|  |  |  | 							newChunk.chunkReason += ` (cache group: ${item.cacheGroup.key})`; | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						if(chunkName) { | 
					
						
							|  |  |  | 							newChunk.chunkReason += ` (name: ${chunkName})`; | 
					
						
							|  |  |  | 							// If the choosen name is already an entry point we remove the entry point
 | 
					
						
							|  |  |  | 							const entrypoint = compilation.entrypoints.get(chunkName); | 
					
						
							|  |  |  | 							if(entrypoint) { | 
					
						
							|  |  |  | 								compilation.entrypoints.delete(chunkName); | 
					
						
							|  |  |  | 								entrypoint.remove(); | 
					
						
							|  |  |  | 								newChunk.entryModule = undefined; | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						if(!isReused) { | 
					
						
							|  |  |  | 							// Add all modules to the new chunk
 | 
					
						
							|  |  |  | 							for(const module of item.modules) { | 
					
						
							|  |  |  | 								GraphHelpers.connectChunkAndModule(newChunk, module); | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						// remove all modules from other entries and update size
 | 
					
						
							| 
									
										
										
										
											2018-02-16 16:12:01 +08:00
										 |  |  | 						for(const [key, info] of chunksInfoMap) { | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 							if(isOverlap(info.chunks, item.chunks)) { | 
					
						
							|  |  |  | 								const oldSize = info.modules.size; | 
					
						
							|  |  |  | 								for(const module of item.modules) { | 
					
						
							|  |  |  | 									info.modules.delete(module); | 
					
						
							|  |  |  | 								} | 
					
						
							| 
									
										
										
										
											2018-02-16 16:12:01 +08:00
										 |  |  | 								if(info.modules.size === 0) { | 
					
						
							|  |  |  | 									chunksInfoMap.delete(key); | 
					
						
							|  |  |  | 									continue; | 
					
						
							|  |  |  | 								} | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 								if(info.modules.size !== oldSize) { | 
					
						
							|  |  |  | 									info.size = getModulesSize(info.modules); | 
					
						
							| 
									
										
										
										
											2018-02-16 16:12:01 +08:00
										 |  |  | 									if(info.size < info.cacheGroup.minSize) | 
					
						
							|  |  |  | 										chunksInfoMap.delete(key); | 
					
						
							| 
									
										
										
										
											2018-01-20 00:06:59 +08:00
										 |  |  | 								} | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						changed = true; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if(changed) return true; | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | }; |