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
 | 
						 * add the origins of chunks and chunk merging info
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	chunkOrigins?: boolean;
 | 
						chunkOrigins?: boolean;
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * add root modules information to chunk information
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						chunkRootModules?: boolean;
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * add chunk information
 | 
						 * add chunk information
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@ const {
 | 
				
			||||||
	compareSelect,
 | 
						compareSelect,
 | 
				
			||||||
	compareIds
 | 
						compareIds
 | 
				
			||||||
} = require("./util/comparators");
 | 
					} = require("./util/comparators");
 | 
				
			||||||
 | 
					const findGraphRoots = require("./util/findGraphRoots");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
 | 
					/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
 | 
				
			||||||
/** @typedef {import("./Chunk")} Chunk */
 | 
					/** @typedef {import("./Chunk")} Chunk */
 | 
				
			||||||
| 
						 | 
					@ -155,6 +156,8 @@ class ChunkGraph {
 | 
				
			||||||
		this._blockChunkGroups = new WeakMap();
 | 
							this._blockChunkGroups = new WeakMap();
 | 
				
			||||||
		/** @private @type {ModuleGraph} */
 | 
							/** @private @type {ModuleGraph} */
 | 
				
			||||||
		this.moduleGraph = moduleGraph;
 | 
							this.moduleGraph = moduleGraph;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this._getGraphRoots = this._getGraphRoots.bind(this);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
| 
						 | 
					@ -185,6 +188,25 @@ class ChunkGraph {
 | 
				
			||||||
		return c;
 | 
							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 {Chunk} chunk the new chunk
 | 
				
			||||||
	 * @param {Module} module the module
 | 
						 * @param {Module} module the module
 | 
				
			||||||
| 
						 | 
					@ -534,6 +556,15 @@ class ChunkGraph {
 | 
				
			||||||
		return cgc.modules.getFromUnorderedCache(getModulesSizes);
 | 
							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 {Chunk} chunk the chunk
 | 
				
			||||||
	 * @param {ChunkSizeOptions} options options object
 | 
						 * @param {ChunkSizeOptions} options options object
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -300,7 +300,7 @@ class ModuleGraph {
 | 
				
			||||||
	 * @param {Module} module the module
 | 
						 * @param {Module} module the module
 | 
				
			||||||
	 * @returns {ModuleGraphConnection[]} list of outgoing connections
 | 
						 * @returns {ModuleGraphConnection[]} list of outgoing connections
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	getOutgoingConnection(module) {
 | 
						getOutgoingConnections(module) {
 | 
				
			||||||
		const connections = this._getModuleGraphModule(module).outgoingConnections;
 | 
							const connections = this._getModuleGraphModule(module).outgoingConnections;
 | 
				
			||||||
		return Array.from(connections);
 | 
							return Array.from(connections);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										93
									
								
								lib/Stats.js
								
								
								
								
							
							
						
						
									
										93
									
								
								lib/Stats.js
								
								
								
								
							| 
						 | 
					@ -170,7 +170,14 @@ class Stats {
 | 
				
			||||||
			!forToString
 | 
								!forToString
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
		const showChunks = optionOrLocalFallback(options.chunks, !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(
 | 
							const showChunkOrigins = optionOrLocalFallback(
 | 
				
			||||||
			options.chunkOrigins,
 | 
								options.chunkOrigins,
 | 
				
			||||||
			!forToString
 | 
								!forToString
 | 
				
			||||||
| 
						 | 
					@ -178,7 +185,7 @@ class Stats {
 | 
				
			||||||
		const showModules = optionOrLocalFallback(options.modules, true);
 | 
							const showModules = optionOrLocalFallback(options.modules, true);
 | 
				
			||||||
		const showNestedModules = optionOrLocalFallback(
 | 
							const showNestedModules = optionOrLocalFallback(
 | 
				
			||||||
			options.nestedModules,
 | 
								options.nestedModules,
 | 
				
			||||||
			true
 | 
								!forToString
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
		const showOrphanModules = optionOrLocalFallback(
 | 
							const showOrphanModules = optionOrLocalFallback(
 | 
				
			||||||
			options.orphanModules,
 | 
								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) {
 | 
									if (showChunkOrigins) {
 | 
				
			||||||
					const originsKeySet = new Set();
 | 
										const originsKeySet = new Set();
 | 
				
			||||||
					obj.origins = Array.from(chunk.groupsIterable, g => g.origins)
 | 
										obj.origins = Array.from(chunk.groupsIterable, g => g.origins)
 | 
				
			||||||
| 
						 | 
					@ -1059,7 +1085,7 @@ class Stats {
 | 
				
			||||||
						color: getAssetColor(asset, colors.normal)
 | 
											color: getAssetColor(asset, colors.normal)
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
						value: asset.chunks.join(", "),
 | 
											value: asset.chunks.map(c => `{${c}}`).join(", "),
 | 
				
			||||||
						color: colors.bold
 | 
											color: colors.bold
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
| 
						 | 
					@ -1134,21 +1160,6 @@ class Stats {
 | 
				
			||||||
			processChunkGroups(outputChunkGroups, "Chunk Group");
 | 
								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 processSizes = sizes => {
 | 
				
			||||||
			const keys = Object.keys(sizes);
 | 
								const keys = Object.keys(sizes);
 | 
				
			||||||
			if (keys.length > 1) {
 | 
								if (keys.length > 1) {
 | 
				
			||||||
| 
						 | 
					@ -1341,11 +1352,16 @@ class Stats {
 | 
				
			||||||
				newline();
 | 
									newline();
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if (module.modules) {
 | 
								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) {
 | 
								if (obj.modules) {
 | 
				
			||||||
				let maxModuleId = 0;
 | 
									let maxModuleId = 0;
 | 
				
			||||||
				for (const module of obj.modules) {
 | 
									for (const module of obj.modules) {
 | 
				
			||||||
| 
						 | 
					@ -1397,7 +1413,19 @@ class Stats {
 | 
				
			||||||
					if (obj.modules.length > 0) colors.normal(" + ");
 | 
										if (obj.modules.length > 0) colors.normal(" + ");
 | 
				
			||||||
					colors.normal(obj.filteredModules);
 | 
										colors.normal(obj.filteredModules);
 | 
				
			||||||
					if (obj.modules.length > 0) colors.normal(" hidden");
 | 
										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();
 | 
										newline();
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					@ -1473,9 +1501,8 @@ class Stats {
 | 
				
			||||||
							colors.normal("[");
 | 
												colors.normal("[");
 | 
				
			||||||
							colors.normal(origin.moduleId);
 | 
												colors.normal(origin.moduleId);
 | 
				
			||||||
							colors.normal("] ");
 | 
												colors.normal("] ");
 | 
				
			||||||
							const module = modulesByIdentifier[`$${origin.module}`];
 | 
												if (origin.moduleName) {
 | 
				
			||||||
							if (module) {
 | 
													colors.bold(origin.moduleName);
 | 
				
			||||||
								colors.bold(module.name);
 | 
					 | 
				
			||||||
								colors.normal(" ");
 | 
													colors.normal(" ");
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
| 
						 | 
					@ -1485,7 +1512,21 @@ class Stats {
 | 
				
			||||||
						newline();
 | 
											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,
 | 
										modules: false,
 | 
				
			||||||
					chunks: true,
 | 
										chunks: true,
 | 
				
			||||||
					chunkModules: true,
 | 
										chunkModules: true,
 | 
				
			||||||
 | 
										chunkRootModules: false,
 | 
				
			||||||
					chunkOrigins: true,
 | 
										chunkOrigins: true,
 | 
				
			||||||
					depth: true,
 | 
										depth: true,
 | 
				
			||||||
					env: true,
 | 
										env: true,
 | 
				
			||||||
| 
						 | 
					@ -1572,6 +1614,7 @@ class Stats {
 | 
				
			||||||
					chunkGroups: true,
 | 
										chunkGroups: true,
 | 
				
			||||||
					chunks: true,
 | 
										chunks: true,
 | 
				
			||||||
					chunkModules: false,
 | 
										chunkModules: false,
 | 
				
			||||||
 | 
										chunkRootModules: false,
 | 
				
			||||||
					chunkOrigins: true,
 | 
										chunkOrigins: true,
 | 
				
			||||||
					depth: true,
 | 
										depth: true,
 | 
				
			||||||
					usedExports: 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",
 | 
					          "description": "add the origins of chunks and chunk merging info",
 | 
				
			||||||
          "type": "boolean"
 | 
					          "type": "boolean"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "chunkRootModules": {
 | 
				
			||||||
 | 
					          "description": "add root modules information to chunk information",
 | 
				
			||||||
 | 
					          "type": "boolean"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "chunks": {
 | 
					        "chunks": {
 | 
				
			||||||
          "description": "add chunk information",
 | 
					          "description": "add chunk information",
 | 
				
			||||||
          "type": "boolean"
 | 
					          "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