mirror of https://github.com/webpack/webpack.git
				
				
				
			Merge pull request #8468 from webpack/feature/chunk-root-modules
add algorithm to extract graph roots
This commit is contained in:
		
						commit
						a20f621263
					
				|  | @ -1267,6 +1267,10 @@ export interface StatsOptions { | |||
| 	 * add the origins of chunks and chunk merging info | ||||
| 	 */ | ||||
| 	chunkOrigins?: boolean; | ||||
| 	/** | ||||
| 	 * add root modules information to chunk information | ||||
| 	 */ | ||||
| 	chunkRootModules?: boolean; | ||||
| 	/** | ||||
| 	 * add chunk information | ||||
| 	 */ | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ const { | |||
| 	compareSelect, | ||||
| 	compareIds | ||||
| } = require("./util/comparators"); | ||||
| const findGraphRoots = require("./util/findGraphRoots"); | ||||
| 
 | ||||
| /** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */ | ||||
| /** @typedef {import("./Chunk")} Chunk */ | ||||
|  | @ -155,6 +156,8 @@ class ChunkGraph { | |||
| 		this._blockChunkGroups = new WeakMap(); | ||||
| 		/** @private @type {ModuleGraph} */ | ||||
| 		this.moduleGraph = moduleGraph; | ||||
| 
 | ||||
| 		this._getGraphRoots = this._getGraphRoots.bind(this); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
|  | @ -185,6 +188,25 @@ class ChunkGraph { | |||
| 		return c; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @param {SortableSet<Module>} set the sortable Set to get the roots of | ||||
| 	 * @returns {Module[]} the graph roots | ||||
| 	 */ | ||||
| 	_getGraphRoots(set) { | ||||
| 		const { moduleGraph } = this; | ||||
| 		return Array.from( | ||||
| 			findGraphRoots(set, module => { | ||||
| 				return moduleGraph | ||||
| 					.getOutgoingConnections(module) | ||||
| 					.reduce((arr, connection) => { | ||||
| 						const module = connection.module; | ||||
| 						if (module) arr.push(module); | ||||
| 						return arr; | ||||
| 					}, []); | ||||
| 			}) | ||||
| 		).sort(compareModulesByIdentifier); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @param {Chunk} chunk the new chunk | ||||
| 	 * @param {Module} module the module | ||||
|  | @ -534,6 +556,15 @@ class ChunkGraph { | |||
| 		return cgc.modules.getFromUnorderedCache(getModulesSizes); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @param {Chunk} chunk the chunk | ||||
| 	 * @returns {Module[]} root modules of the chunks (ordered by identifer) | ||||
| 	 */ | ||||
| 	getChunkRootModules(chunk) { | ||||
| 		const cgc = this._getChunkGraphChunk(chunk); | ||||
| 		return cgc.modules.getFromUnorderedCache(this._getGraphRoots); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @param {Chunk} chunk the chunk | ||||
| 	 * @param {ChunkSizeOptions} options options object | ||||
|  |  | |||
|  | @ -300,7 +300,7 @@ class ModuleGraph { | |||
| 	 * @param {Module} module the module | ||||
| 	 * @returns {ModuleGraphConnection[]} list of outgoing connections | ||||
| 	 */ | ||||
| 	getOutgoingConnection(module) { | ||||
| 	getOutgoingConnections(module) { | ||||
| 		const connections = this._getModuleGraphModule(module).outgoingConnections; | ||||
| 		return Array.from(connections); | ||||
| 	} | ||||
|  |  | |||
							
								
								
									
										93
									
								
								lib/Stats.js
								
								
								
								
							
							
						
						
									
										93
									
								
								lib/Stats.js
								
								
								
								
							|  | @ -170,7 +170,14 @@ class Stats { | |||
| 			!forToString | ||||
| 		); | ||||
| 		const showChunks = optionOrLocalFallback(options.chunks, !forToString); | ||||
| 		const showChunkModules = optionOrLocalFallback(options.chunkModules, true); | ||||
| 		const showChunkModules = optionOrLocalFallback( | ||||
| 			options.chunkModules, | ||||
| 			!forToString | ||||
| 		); | ||||
| 		const showChunkRootModules = optionOrLocalFallback( | ||||
| 			options.chunkRootModules, | ||||
| 			forToString ? !showChunkModules : true | ||||
| 		); | ||||
| 		const showChunkOrigins = optionOrLocalFallback( | ||||
| 			options.chunkOrigins, | ||||
| 			!forToString | ||||
|  | @ -178,7 +185,7 @@ class Stats { | |||
| 		const showModules = optionOrLocalFallback(options.modules, true); | ||||
| 		const showNestedModules = optionOrLocalFallback( | ||||
| 			options.nestedModules, | ||||
| 			true | ||||
| 			!forToString | ||||
| 		); | ||||
| 		const showOrphanModules = optionOrLocalFallback( | ||||
| 			options.orphanModules, | ||||
|  | @ -793,6 +800,25 @@ class Stats { | |||
| 						); | ||||
| 					} | ||||
| 				} | ||||
| 				if (showChunkRootModules) { | ||||
| 					const rootModules = chunkGraph.getChunkRootModules(chunk); | ||||
| 					obj.rootModules = rootModules | ||||
| 						.slice() | ||||
| 						.sort(sortRealModules) | ||||
| 						.filter(createModuleFilter("root-of-chunk")) | ||||
| 						.map(m => fnModule(m)); | ||||
| 					obj.filteredRootModules = rootModules.length - obj.rootModules.length; | ||||
| 					obj.nonRootModules = | ||||
| 						chunkGraph.getNumberOfChunkModules(chunk) - rootModules.length; | ||||
| 					if (sortModules) { | ||||
| 						obj.rootModules.sort( | ||||
| 							concatComparators( | ||||
| 								sortByField(sortModules), | ||||
| 								keepOriginalOrder(obj.rootModules) | ||||
| 							) | ||||
| 						); | ||||
| 					} | ||||
| 				} | ||||
| 				if (showChunkOrigins) { | ||||
| 					const originsKeySet = new Set(); | ||||
| 					obj.origins = Array.from(chunk.groupsIterable, g => g.origins) | ||||
|  | @ -1059,7 +1085,7 @@ class Stats { | |||
| 						color: getAssetColor(asset, colors.normal) | ||||
| 					}, | ||||
| 					{ | ||||
| 						value: asset.chunks.join(", "), | ||||
| 						value: asset.chunks.map(c => `{${c}}`).join(", "), | ||||
| 						color: colors.bold | ||||
| 					}, | ||||
| 					{ | ||||
|  | @ -1134,21 +1160,6 @@ class Stats { | |||
| 			processChunkGroups(outputChunkGroups, "Chunk Group"); | ||||
| 		} | ||||
| 
 | ||||
| 		const modulesByIdentifier = {}; | ||||
| 		if (obj.modules) { | ||||
| 			for (const module of obj.modules) { | ||||
| 				modulesByIdentifier[`$${module.identifier}`] = module; | ||||
| 			} | ||||
| 		} else if (obj.chunks) { | ||||
| 			for (const chunk of obj.chunks) { | ||||
| 				if (chunk.modules) { | ||||
| 					for (const module of chunk.modules) { | ||||
| 						modulesByIdentifier[`$${module.identifier}`] = module; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		const processSizes = sizes => { | ||||
| 			const keys = Object.keys(sizes); | ||||
| 			if (keys.length > 1) { | ||||
|  | @ -1341,11 +1352,16 @@ class Stats { | |||
| 				newline(); | ||||
| 			} | ||||
| 			if (module.modules) { | ||||
| 				processModulesList(module, prefix + "| "); | ||||
| 				processModulesList(module, prefix + "| ", "nested module"); | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| 		const processModulesList = (obj, prefix) => { | ||||
| 		const processModulesList = ( | ||||
| 			obj, | ||||
| 			prefix, | ||||
| 			itemType = "module", | ||||
| 			dependentItemType = "dependent module" | ||||
| 		) => { | ||||
| 			if (obj.modules) { | ||||
| 				let maxModuleId = 0; | ||||
| 				for (const module of obj.modules) { | ||||
|  | @ -1397,7 +1413,19 @@ class Stats { | |||
| 					if (obj.modules.length > 0) colors.normal(" + "); | ||||
| 					colors.normal(obj.filteredModules); | ||||
| 					if (obj.modules.length > 0) colors.normal(" hidden"); | ||||
| 					colors.normal(obj.filteredModules !== 1 ? " modules" : " module"); | ||||
| 					colors.normal(` ${itemType}${obj.filteredModules !== 1 ? "s" : ""}`); | ||||
| 					newline(); | ||||
| 				} | ||||
| 				if (obj.dependentModules > 0) { | ||||
| 					const additional = obj.modules.length > 0 || obj.filteredModules > 0; | ||||
| 					colors.normal(prefix); | ||||
| 					colors.normal("   "); | ||||
| 					if (additional) colors.normal(" + "); | ||||
| 					colors.normal(obj.dependentModules); | ||||
| 					if (additional) colors.normal(" hidden"); | ||||
| 					colors.normal( | ||||
| 						` ${dependentItemType}${obj.dependentModules !== 1 ? "s" : ""}` | ||||
| 					); | ||||
| 					newline(); | ||||
| 				} | ||||
| 			} | ||||
|  | @ -1473,9 +1501,8 @@ class Stats { | |||
| 							colors.normal("["); | ||||
| 							colors.normal(origin.moduleId); | ||||
| 							colors.normal("] "); | ||||
| 							const module = modulesByIdentifier[`$${origin.module}`]; | ||||
| 							if (module) { | ||||
| 								colors.bold(module.name); | ||||
| 							if (origin.moduleName) { | ||||
| 								colors.bold(origin.moduleName); | ||||
| 								colors.normal(" "); | ||||
| 							} | ||||
| 						} | ||||
|  | @ -1485,7 +1512,21 @@ class Stats { | |||
| 						newline(); | ||||
| 					} | ||||
| 				} | ||||
| 				processModulesList(chunk, " "); | ||||
| 				const hasRootModules = | ||||
| 					chunk.rootModules || | ||||
| 					chunk.filteredRootModules || | ||||
| 					chunk.nonRootModules; | ||||
| 				processModulesList( | ||||
| 					{ | ||||
| 						modules: chunk.rootModules, | ||||
| 						filteredModules: chunk.filteredRootModules, | ||||
| 						dependentModules: chunk.nonRootModules | ||||
| 					}, | ||||
| 					" ", | ||||
| 					"root module", | ||||
| 					"dependent module" | ||||
| 				); | ||||
| 				processModulesList(chunk, hasRootModules ? " | " : " ", "chunk module"); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|  | @ -1552,6 +1593,7 @@ class Stats { | |||
| 					modules: false, | ||||
| 					chunks: true, | ||||
| 					chunkModules: true, | ||||
| 					chunkRootModules: false, | ||||
| 					chunkOrigins: true, | ||||
| 					depth: true, | ||||
| 					env: true, | ||||
|  | @ -1572,6 +1614,7 @@ class Stats { | |||
| 					chunkGroups: true, | ||||
| 					chunks: true, | ||||
| 					chunkModules: false, | ||||
| 					chunkRootModules: false, | ||||
| 					chunkOrigins: true, | ||||
| 					depth: true, | ||||
| 					usedExports: true, | ||||
|  |  | |||
|  | @ -0,0 +1,214 @@ | |||
| /* | ||||
| 	MIT License http://www.opensource.org/licenses/mit-license.php
 | ||||
| 	Author Tobias Koppers @sokra | ||||
| */ | ||||
| 
 | ||||
| "use strict"; | ||||
| 
 | ||||
| const NO_MARKER = 0; | ||||
| const IN_PROGRESS_MARKER = 1; | ||||
| const DONE_MARKER = 2; | ||||
| const DONE_MAYBE_ROOT_CYCLE_MARKER = 3; | ||||
| const DONE_AND_ROOT_MARKER = 4; | ||||
| 
 | ||||
| class Node { | ||||
| 	constructor(item) { | ||||
| 		this.item = item; | ||||
| 		this.dependencies = new Set(); | ||||
| 		this.marker = NO_MARKER; | ||||
| 		this.cycle = undefined; | ||||
| 		this.incoming = 0; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| class Cycle { | ||||
| 	constructor() { | ||||
| 		this.nodes = new Set(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * @typedef {Object} StackEntry | ||||
|  * @property {Node} node | ||||
|  * @property {Node[]} openEdges | ||||
|  */ | ||||
| 
 | ||||
| /** | ||||
|  * @template T | ||||
|  * @param {Iterable<T>} items list of items | ||||
|  * @param {function(T): Iterable<T>} getDependencies function to get dependencies of an item (items that are not in list are ignored) | ||||
|  * @returns {Iterable<T>} graph roots of the items | ||||
|  */ | ||||
| module.exports = (items, getDependencies) => { | ||||
| 	const itemToNode = new Map(); | ||||
| 	for (const item of items) { | ||||
| 		const node = new Node(item); | ||||
| 		itemToNode.set(item, node); | ||||
| 	} | ||||
| 
 | ||||
| 	// early exit when there is only a single item
 | ||||
| 	if (itemToNode.size <= 1) return items; | ||||
| 
 | ||||
| 	// grab all the dependencies
 | ||||
| 	for (const node of itemToNode.values()) { | ||||
| 		for (const dep of getDependencies(node.item)) { | ||||
| 			const depNode = itemToNode.get(dep); | ||||
| 			if (depNode !== undefined) { | ||||
| 				node.dependencies.add(depNode); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Set of current root modules
 | ||||
| 	// items will be removed if a new reference to it has been found
 | ||||
| 	/** @type {Set<Node>} */ | ||||
| 	const roots = new Set(); | ||||
| 
 | ||||
| 	// Set of current cycles without references to it
 | ||||
| 	// cycles will be removed if a new reference to it has been found
 | ||||
| 	// that is not part of the cycle
 | ||||
| 	/** @type {Set<Cycle>} */ | ||||
| 	const rootCycles = new Set(); | ||||
| 
 | ||||
| 	// For all non-marked nodes
 | ||||
| 	for (const selectedNode of itemToNode.values()) { | ||||
| 		if (selectedNode.marker === NO_MARKER) { | ||||
| 			// deep-walk all referenced modules
 | ||||
| 			// in a non-recursive way
 | ||||
| 
 | ||||
| 			// start by entering the selected node
 | ||||
| 			selectedNode.marker = IN_PROGRESS_MARKER; | ||||
| 
 | ||||
| 			// keep a stack to avoid recursive walk
 | ||||
| 			/** @type {StackEntry[]} */ | ||||
| 			const stack = [ | ||||
| 				{ | ||||
| 					node: selectedNode, | ||||
| 					openEdges: Array.from(selectedNode.dependencies) | ||||
| 				} | ||||
| 			]; | ||||
| 
 | ||||
| 			// process the top item until stack is empty
 | ||||
| 			while (stack.length > 0) { | ||||
| 				const topOfStack = stack[stack.length - 1]; | ||||
| 
 | ||||
| 				// Are there still edges unprocessed in the current node?
 | ||||
| 				if (topOfStack.openEdges.length > 0) { | ||||
| 					// Process one dependency
 | ||||
| 					const dependency = topOfStack.openEdges.pop(); | ||||
| 					switch (dependency.marker) { | ||||
| 						case NO_MARKER: | ||||
| 							// dependency has not be visited yet
 | ||||
| 							// mark it as in-progress and recurse
 | ||||
| 							stack.push({ | ||||
| 								node: dependency, | ||||
| 								openEdges: Array.from(dependency.dependencies) | ||||
| 							}); | ||||
| 							dependency.marker = IN_PROGRESS_MARKER; | ||||
| 							break; | ||||
| 						case IN_PROGRESS_MARKER: { | ||||
| 							// It's a in-progress cycle
 | ||||
| 							let cycle = dependency.cycle; | ||||
| 							if (!cycle) { | ||||
| 								cycle = new Cycle(); | ||||
| 								cycle.nodes.add(dependency); | ||||
| 								dependency.cycle = cycle; | ||||
| 							} | ||||
| 							// set cycle property for each node in the cycle
 | ||||
| 							// if nodes are already part of a cycle
 | ||||
| 							// we merge the cycles to a shared cycle
 | ||||
| 							for ( | ||||
| 								let i = stack.length - 1; | ||||
| 								stack[i].node !== dependency; | ||||
| 								i-- | ||||
| 							) { | ||||
| 								const node = stack[i].node; | ||||
| 								if (node.cycle) { | ||||
| 									if (node.cycle !== cycle) { | ||||
| 										// merge cycles
 | ||||
| 										for (const cycleNode of node.cycle.nodes) { | ||||
| 											cycleNode.cycle = cycle; | ||||
| 											cycle.nodes.add(cycleNode); | ||||
| 										} | ||||
| 									} | ||||
| 								} else { | ||||
| 									node.cycle = cycle; | ||||
| 									cycle.nodes.add(node); | ||||
| 								} | ||||
| 							} | ||||
| 							// don't recurse into dependencies
 | ||||
| 							// these are already on the stack
 | ||||
| 							break; | ||||
| 						} | ||||
| 						case DONE_AND_ROOT_MARKER: | ||||
| 							// This node has be visited yet and is currently a root node
 | ||||
| 							// But as this is a new reference to the node
 | ||||
| 							// it's not really a root
 | ||||
| 							// so we have to convert it to a normal node
 | ||||
| 							dependency.marker = DONE_MARKER; | ||||
| 							roots.delete(dependency); | ||||
| 							break; | ||||
| 						case DONE_MAYBE_ROOT_CYCLE_MARKER: | ||||
| 							// This node has be visited yet and
 | ||||
| 							// is maybe currently part of a completed root cycle
 | ||||
| 							// we found a new reference to the cycle
 | ||||
| 							// so it's not really a root cycle
 | ||||
| 							// remove the cycle from the root cycles
 | ||||
| 							// and convert it to a normal node
 | ||||
| 							rootCycles.delete(dependency.cycle); | ||||
| 							dependency.marker = DONE_MARKER; | ||||
| 							break; | ||||
| 						// DONE_MARKER: nothing to do, don't recurse into dependencies
 | ||||
| 					} | ||||
| 				} else { | ||||
| 					// All dependencies of the current node has been visited
 | ||||
| 					// we leave the node
 | ||||
| 					stack.pop(); | ||||
| 					topOfStack.node.marker = DONE_MARKER; | ||||
| 				} | ||||
| 			} | ||||
| 			const cycle = selectedNode.cycle; | ||||
| 			if (cycle) { | ||||
| 				for (const node of cycle.nodes) { | ||||
| 					node.marker = DONE_MAYBE_ROOT_CYCLE_MARKER; | ||||
| 				} | ||||
| 				rootCycles.add(cycle); | ||||
| 			} else { | ||||
| 				selectedNode.marker = DONE_AND_ROOT_MARKER; | ||||
| 				roots.add(selectedNode); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Extract roots from root cycles
 | ||||
| 	// We take the nodes with most incoming edges
 | ||||
| 	// inside of the cycle
 | ||||
| 	for (const cycle of rootCycles) { | ||||
| 		let max = 0; | ||||
| 		const cycleRoots = new Set(); | ||||
| 		const nodes = cycle.nodes; | ||||
| 		for (const node of nodes) { | ||||
| 			for (const dep of node.dependencies) { | ||||
| 				if (nodes.has(dep)) { | ||||
| 					dep.incoming++; | ||||
| 					if (dep.incoming < max) continue; | ||||
| 					if (dep.incoming > max) { | ||||
| 						cycleRoots.clear(); | ||||
| 						max = dep.incoming; | ||||
| 					} | ||||
| 					cycleRoots.add(dep); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		for (const cycleRoot of cycleRoots) { | ||||
| 			roots.add(cycleRoot); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// When roots were found, return them
 | ||||
| 	if (roots.size > 0) { | ||||
| 		return Array.from(roots, r => r.item); | ||||
| 	} else { | ||||
| 		throw new Error("Implementation of findGraphRoots is broken"); | ||||
| 	} | ||||
| }; | ||||
|  | @ -1745,6 +1745,10 @@ | |||
|           "description": "add the origins of chunks and chunk merging info", | ||||
|           "type": "boolean" | ||||
|         }, | ||||
|         "chunkRootModules": { | ||||
|           "description": "add root modules information to chunk information", | ||||
|           "type": "boolean" | ||||
|         }, | ||||
|         "chunks": { | ||||
|           "description": "add chunk information", | ||||
|           "type": "boolean" | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1 @@ | |||
| import "./b"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./c"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./index"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./a"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./b"; | ||||
|  | @ -0,0 +1,2 @@ | |||
| import "./c"; | ||||
| import "./index"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./index"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./a"; | ||||
|  | @ -0,0 +1,2 @@ | |||
| import "./index"; | ||||
| import "./b"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./c"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./index"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./a"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./b"; | ||||
|  | @ -0,0 +1,2 @@ | |||
| import "./c"; | ||||
| import "./index"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./index"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./a"; | ||||
|  | @ -0,0 +1,12 @@ | |||
| import(/* webpackChunkName: "tree" */ "./tree"); | ||||
| 
 | ||||
| import(/* webpackChunkName: "trees" */ "./trees/1"); | ||||
| import(/* webpackChunkName: "trees" */ "./trees/2"); | ||||
| import(/* webpackChunkName: "trees" */ "./trees/3"); | ||||
| 
 | ||||
| import(/* webpackChunkName: "cycle" */ "./cycle"); | ||||
| 
 | ||||
| import(/* webpackChunkName: "cycle2" */ "./cycle2"); | ||||
| 
 | ||||
| import(/* webpackChunkName: "cycles" */ "./cycles/1"); | ||||
| import(/* webpackChunkName: "cycles" */ "./cycles/2"); | ||||
|  | @ -0,0 +1 @@ | |||
| import "./b"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./c"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./a"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./a"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./a"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./b"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./b"; | ||||
|  | @ -0,0 +1 @@ | |||
| import "./c"; | ||||
|  | @ -0,0 +1,14 @@ | |||
| module.exports = { | ||||
| 	mode: "development", | ||||
| 	entry: "./index.js", | ||||
| 	optimization: { | ||||
| 		moduleIds: "natural", | ||||
| 		chunkIds: "natural", | ||||
| 		splitChunks: false | ||||
| 	}, | ||||
| 	stats: { | ||||
| 		all: false, | ||||
| 		chunks: true, | ||||
| 		chunkRootModules: true | ||||
| 	} | ||||
| }; | ||||
		Loading…
	
		Reference in New Issue