mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			953 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			953 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
/*
 | 
						|
	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
						|
	Author Tobias Koppers @sokra
 | 
						|
*/
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
const asyncLib = require("neo-async");
 | 
						|
const ChunkGraph = require("../ChunkGraph");
 | 
						|
const ModuleGraph = require("../ModuleGraph");
 | 
						|
const { JS_TYPE } = require("../ModuleSourceTypesConstants");
 | 
						|
const { STAGE_DEFAULT } = require("../OptimizationStages");
 | 
						|
const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency");
 | 
						|
const { compareModulesByIdentifier } = require("../util/comparators");
 | 
						|
const {
 | 
						|
	filterRuntime,
 | 
						|
	intersectRuntime,
 | 
						|
	mergeRuntime,
 | 
						|
	mergeRuntimeOwned,
 | 
						|
	runtimeToString
 | 
						|
} = require("../util/runtime");
 | 
						|
const ConcatenatedModule = require("./ConcatenatedModule");
 | 
						|
 | 
						|
/** @typedef {import("../Compilation")} Compilation */
 | 
						|
/** @typedef {import("../Compiler")} Compiler */
 | 
						|
/** @typedef {import("../Module")} Module */
 | 
						|
/** @typedef {import("../Module").BuildInfo} BuildInfo */
 | 
						|
/** @typedef {import("../RequestShortener")} RequestShortener */
 | 
						|
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
 | 
						|
 | 
						|
/**
 | 
						|
 * @typedef {object} Statistics
 | 
						|
 * @property {number} cached
 | 
						|
 * @property {number} alreadyInConfig
 | 
						|
 * @property {number} invalidModule
 | 
						|
 * @property {number} incorrectChunks
 | 
						|
 * @property {number} incorrectDependency
 | 
						|
 * @property {number} incorrectModuleDependency
 | 
						|
 * @property {number} incorrectChunksOfImporter
 | 
						|
 * @property {number} incorrectRuntimeCondition
 | 
						|
 * @property {number} importerFailed
 | 
						|
 * @property {number} added
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {string} msg message
 | 
						|
 * @returns {string} formatted message
 | 
						|
 */
 | 
						|
const formatBailoutReason = (msg) => `ModuleConcatenation bailout: ${msg}`;
 | 
						|
 | 
						|
const PLUGIN_NAME = "ModuleConcatenationPlugin";
 | 
						|
 | 
						|
class ModuleConcatenationPlugin {
 | 
						|
	/**
 | 
						|
	 * Apply the plugin
 | 
						|
	 * @param {Compiler} compiler the compiler instance
 | 
						|
	 * @returns {void}
 | 
						|
	 */
 | 
						|
	apply(compiler) {
 | 
						|
		const { _backCompat: backCompat } = compiler;
 | 
						|
		compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
 | 
						|
			if (compilation.moduleMemCaches) {
 | 
						|
				throw new Error(
 | 
						|
					"optimization.concatenateModules can't be used with cacheUnaffected as module concatenation is a global effect"
 | 
						|
				);
 | 
						|
			}
 | 
						|
			const moduleGraph = compilation.moduleGraph;
 | 
						|
			/** @type {Map<Module, string | ((requestShortener: RequestShortener) => string)>} */
 | 
						|
			const bailoutReasonMap = new Map();
 | 
						|
 | 
						|
			/**
 | 
						|
			 * @param {Module} module the module
 | 
						|
			 * @param {string | ((requestShortener: RequestShortener) => string)} reason the reason
 | 
						|
			 */
 | 
						|
			const setBailoutReason = (module, reason) => {
 | 
						|
				setInnerBailoutReason(module, reason);
 | 
						|
				moduleGraph
 | 
						|
					.getOptimizationBailout(module)
 | 
						|
					.push(
 | 
						|
						typeof reason === "function"
 | 
						|
							? (rs) => formatBailoutReason(reason(rs))
 | 
						|
							: formatBailoutReason(reason)
 | 
						|
					);
 | 
						|
			};
 | 
						|
 | 
						|
			/**
 | 
						|
			 * @param {Module} module the module
 | 
						|
			 * @param {string | ((requestShortener: RequestShortener) => string)} reason the reason
 | 
						|
			 */
 | 
						|
			const setInnerBailoutReason = (module, reason) => {
 | 
						|
				bailoutReasonMap.set(module, reason);
 | 
						|
			};
 | 
						|
 | 
						|
			/**
 | 
						|
			 * @param {Module} module the module
 | 
						|
			 * @param {RequestShortener} requestShortener the request shortener
 | 
						|
			 * @returns {string | ((requestShortener: RequestShortener) => string) | undefined} the reason
 | 
						|
			 */
 | 
						|
			const getInnerBailoutReason = (module, requestShortener) => {
 | 
						|
				const reason = bailoutReasonMap.get(module);
 | 
						|
				if (typeof reason === "function") return reason(requestShortener);
 | 
						|
				return reason;
 | 
						|
			};
 | 
						|
 | 
						|
			/**
 | 
						|
			 * @param {Module} module the module
 | 
						|
			 * @param {Module | ((requestShortener: RequestShortener) => string)} problem the problem
 | 
						|
			 * @returns {(requestShortener: RequestShortener) => string} the reason
 | 
						|
			 */
 | 
						|
			const formatBailoutWarning = (module, problem) => (requestShortener) => {
 | 
						|
				if (typeof problem === "function") {
 | 
						|
					return formatBailoutReason(
 | 
						|
						`Cannot concat with ${module.readableIdentifier(
 | 
						|
							requestShortener
 | 
						|
						)}: ${problem(requestShortener)}`
 | 
						|
					);
 | 
						|
				}
 | 
						|
				const reason = getInnerBailoutReason(module, requestShortener);
 | 
						|
				const reasonWithPrefix = reason ? `: ${reason}` : "";
 | 
						|
				if (module === problem) {
 | 
						|
					return formatBailoutReason(
 | 
						|
						`Cannot concat with ${module.readableIdentifier(
 | 
						|
							requestShortener
 | 
						|
						)}${reasonWithPrefix}`
 | 
						|
					);
 | 
						|
				}
 | 
						|
				return formatBailoutReason(
 | 
						|
					`Cannot concat with ${module.readableIdentifier(
 | 
						|
						requestShortener
 | 
						|
					)} because of ${problem.readableIdentifier(
 | 
						|
						requestShortener
 | 
						|
					)}${reasonWithPrefix}`
 | 
						|
				);
 | 
						|
			};
 | 
						|
 | 
						|
			compilation.hooks.optimizeChunkModules.tapAsync(
 | 
						|
				{
 | 
						|
					name: PLUGIN_NAME,
 | 
						|
					stage: STAGE_DEFAULT
 | 
						|
				},
 | 
						|
				(allChunks, modules, callback) => {
 | 
						|
					const logger = compilation.getLogger(
 | 
						|
						"webpack.ModuleConcatenationPlugin"
 | 
						|
					);
 | 
						|
					const { chunkGraph, moduleGraph } = compilation;
 | 
						|
					const relevantModules = [];
 | 
						|
					const possibleInners = new Set();
 | 
						|
					const context = {
 | 
						|
						chunkGraph,
 | 
						|
						moduleGraph
 | 
						|
					};
 | 
						|
					const deferEnabled = compilation.options.experiments.deferImport;
 | 
						|
					logger.time("select relevant modules");
 | 
						|
					for (const module of modules) {
 | 
						|
						let canBeRoot = true;
 | 
						|
						let canBeInner = true;
 | 
						|
 | 
						|
						const bailoutReason = module.getConcatenationBailoutReason(context);
 | 
						|
						if (bailoutReason) {
 | 
						|
							setBailoutReason(module, bailoutReason);
 | 
						|
							continue;
 | 
						|
						}
 | 
						|
 | 
						|
						// Must not be an async module
 | 
						|
						if (moduleGraph.isAsync(module)) {
 | 
						|
							setBailoutReason(module, "Module is async");
 | 
						|
							continue;
 | 
						|
						}
 | 
						|
 | 
						|
						// Must be in strict mode
 | 
						|
						if (!(/** @type {BuildInfo} */ (module.buildInfo).strict)) {
 | 
						|
							setBailoutReason(module, "Module is not in strict mode");
 | 
						|
							continue;
 | 
						|
						}
 | 
						|
 | 
						|
						// Module must be in any chunk (we don't want to do useless work)
 | 
						|
						if (chunkGraph.getNumberOfModuleChunks(module) === 0) {
 | 
						|
							setBailoutReason(module, "Module is not in any chunk");
 | 
						|
							continue;
 | 
						|
						}
 | 
						|
 | 
						|
						// Exports must be known (and not dynamic)
 | 
						|
						const exportsInfo = moduleGraph.getExportsInfo(module);
 | 
						|
						const relevantExports = exportsInfo.getRelevantExports(undefined);
 | 
						|
						const unknownReexports = relevantExports.filter(
 | 
						|
							(exportInfo) =>
 | 
						|
								exportInfo.isReexport() && !exportInfo.getTarget(moduleGraph)
 | 
						|
						);
 | 
						|
						if (unknownReexports.length > 0) {
 | 
						|
							setBailoutReason(
 | 
						|
								module,
 | 
						|
								`Reexports in this module do not have a static target (${Array.from(
 | 
						|
									unknownReexports,
 | 
						|
									(exportInfo) =>
 | 
						|
										`${
 | 
						|
											exportInfo.name || "other exports"
 | 
						|
										}: ${exportInfo.getUsedInfo()}`
 | 
						|
								).join(", ")})`
 | 
						|
							);
 | 
						|
							continue;
 | 
						|
						}
 | 
						|
 | 
						|
						// Root modules must have a static list of exports
 | 
						|
						const unknownProvidedExports = relevantExports.filter(
 | 
						|
							(exportInfo) => exportInfo.provided !== true
 | 
						|
						);
 | 
						|
						if (unknownProvidedExports.length > 0) {
 | 
						|
							setBailoutReason(
 | 
						|
								module,
 | 
						|
								`List of module exports is dynamic (${Array.from(
 | 
						|
									unknownProvidedExports,
 | 
						|
									(exportInfo) =>
 | 
						|
										`${
 | 
						|
											exportInfo.name || "other exports"
 | 
						|
										}: ${exportInfo.getProvidedInfo()} and ${exportInfo.getUsedInfo()}`
 | 
						|
								).join(", ")})`
 | 
						|
							);
 | 
						|
							canBeRoot = false;
 | 
						|
						}
 | 
						|
 | 
						|
						// Module must not be an entry point
 | 
						|
						if (chunkGraph.isEntryModule(module)) {
 | 
						|
							setInnerBailoutReason(module, "Module is an entry point");
 | 
						|
							canBeInner = false;
 | 
						|
						}
 | 
						|
 | 
						|
						if (deferEnabled && moduleGraph.isDeferred(module)) {
 | 
						|
							setInnerBailoutReason(module, "Module is deferred");
 | 
						|
							canBeInner = false;
 | 
						|
						}
 | 
						|
 | 
						|
						if (canBeRoot) relevantModules.push(module);
 | 
						|
						if (canBeInner) possibleInners.add(module);
 | 
						|
					}
 | 
						|
					logger.timeEnd("select relevant modules");
 | 
						|
					logger.debug(
 | 
						|
						`${relevantModules.length} potential root modules, ${possibleInners.size} potential inner modules`
 | 
						|
					);
 | 
						|
					// sort by depth
 | 
						|
					// modules with lower depth are more likely suited as roots
 | 
						|
					// this improves performance, because modules already selected as inner are skipped
 | 
						|
					logger.time("sort relevant modules");
 | 
						|
					relevantModules.sort(
 | 
						|
						(a, b) =>
 | 
						|
							/** @type {number} */ (moduleGraph.getDepth(a)) -
 | 
						|
							/** @type {number} */ (moduleGraph.getDepth(b))
 | 
						|
					);
 | 
						|
					logger.timeEnd("sort relevant modules");
 | 
						|
 | 
						|
					/** @type {Statistics} */
 | 
						|
					const stats = {
 | 
						|
						cached: 0,
 | 
						|
						alreadyInConfig: 0,
 | 
						|
						invalidModule: 0,
 | 
						|
						incorrectChunks: 0,
 | 
						|
						incorrectDependency: 0,
 | 
						|
						incorrectModuleDependency: 0,
 | 
						|
						incorrectChunksOfImporter: 0,
 | 
						|
						incorrectRuntimeCondition: 0,
 | 
						|
						importerFailed: 0,
 | 
						|
						added: 0
 | 
						|
					};
 | 
						|
					let statsCandidates = 0;
 | 
						|
					let statsSizeSum = 0;
 | 
						|
					let statsEmptyConfigurations = 0;
 | 
						|
 | 
						|
					logger.time("find modules to concatenate");
 | 
						|
					const concatConfigurations = [];
 | 
						|
					const usedAsInner = new Set();
 | 
						|
					for (const currentRoot of relevantModules) {
 | 
						|
						// when used by another configuration as inner:
 | 
						|
						// the other configuration is better and we can skip this one
 | 
						|
						// TODO reconsider that when it's only used in a different runtime
 | 
						|
						if (usedAsInner.has(currentRoot)) continue;
 | 
						|
 | 
						|
						let chunkRuntime;
 | 
						|
						for (const r of chunkGraph.getModuleRuntimes(currentRoot)) {
 | 
						|
							chunkRuntime = mergeRuntimeOwned(chunkRuntime, r);
 | 
						|
						}
 | 
						|
						const exportsInfo = moduleGraph.getExportsInfo(currentRoot);
 | 
						|
						const filteredRuntime = filterRuntime(chunkRuntime, (r) =>
 | 
						|
							exportsInfo.isModuleUsed(r)
 | 
						|
						);
 | 
						|
						const activeRuntime =
 | 
						|
							filteredRuntime === true
 | 
						|
								? chunkRuntime
 | 
						|
								: filteredRuntime === false
 | 
						|
									? undefined
 | 
						|
									: filteredRuntime;
 | 
						|
 | 
						|
						// create a configuration with the root
 | 
						|
						const currentConfiguration = new ConcatConfiguration(
 | 
						|
							currentRoot,
 | 
						|
							activeRuntime
 | 
						|
						);
 | 
						|
 | 
						|
						// cache failures to add modules
 | 
						|
						const failureCache = new Map();
 | 
						|
 | 
						|
						// potential optional import candidates
 | 
						|
						/** @type {Set<Module>} */
 | 
						|
						const candidates = new Set();
 | 
						|
 | 
						|
						// try to add all imports
 | 
						|
						for (const imp of this._getImports(
 | 
						|
							compilation,
 | 
						|
							currentRoot,
 | 
						|
							activeRuntime
 | 
						|
						)) {
 | 
						|
							candidates.add(imp);
 | 
						|
						}
 | 
						|
 | 
						|
						for (const imp of candidates) {
 | 
						|
							const impCandidates = new Set();
 | 
						|
							const problem = this._tryToAdd(
 | 
						|
								compilation,
 | 
						|
								currentConfiguration,
 | 
						|
								imp,
 | 
						|
								chunkRuntime,
 | 
						|
								activeRuntime,
 | 
						|
								possibleInners,
 | 
						|
								impCandidates,
 | 
						|
								failureCache,
 | 
						|
								chunkGraph,
 | 
						|
								true,
 | 
						|
								stats
 | 
						|
							);
 | 
						|
							if (problem) {
 | 
						|
								failureCache.set(imp, problem);
 | 
						|
								currentConfiguration.addWarning(imp, problem);
 | 
						|
							} else {
 | 
						|
								for (const c of impCandidates) {
 | 
						|
									candidates.add(c);
 | 
						|
								}
 | 
						|
							}
 | 
						|
						}
 | 
						|
						statsCandidates += candidates.size;
 | 
						|
						if (!currentConfiguration.isEmpty()) {
 | 
						|
							const modules = currentConfiguration.getModules();
 | 
						|
							statsSizeSum += modules.size;
 | 
						|
							concatConfigurations.push(currentConfiguration);
 | 
						|
							for (const module of modules) {
 | 
						|
								if (module !== currentConfiguration.rootModule) {
 | 
						|
									usedAsInner.add(module);
 | 
						|
								}
 | 
						|
							}
 | 
						|
						} else {
 | 
						|
							statsEmptyConfigurations++;
 | 
						|
							const optimizationBailouts =
 | 
						|
								moduleGraph.getOptimizationBailout(currentRoot);
 | 
						|
							for (const warning of currentConfiguration.getWarningsSorted()) {
 | 
						|
								optimizationBailouts.push(
 | 
						|
									formatBailoutWarning(warning[0], warning[1])
 | 
						|
								);
 | 
						|
							}
 | 
						|
						}
 | 
						|
					}
 | 
						|
					logger.timeEnd("find modules to concatenate");
 | 
						|
					logger.debug(
 | 
						|
						`${
 | 
						|
							concatConfigurations.length
 | 
						|
						} successful concat configurations (avg size: ${
 | 
						|
							statsSizeSum / concatConfigurations.length
 | 
						|
						}), ${statsEmptyConfigurations} bailed out completely`
 | 
						|
					);
 | 
						|
					logger.debug(
 | 
						|
						`${statsCandidates} candidates were considered for adding (${stats.cached} cached failure, ${stats.alreadyInConfig} already in config, ${stats.invalidModule} invalid module, ${stats.incorrectChunks} incorrect chunks, ${stats.incorrectDependency} incorrect dependency, ${stats.incorrectChunksOfImporter} incorrect chunks of importer, ${stats.incorrectModuleDependency} incorrect module dependency, ${stats.incorrectRuntimeCondition} incorrect runtime condition, ${stats.importerFailed} importer failed, ${stats.added} added)`
 | 
						|
					);
 | 
						|
					// HACK: Sort configurations by length and start with the longest one
 | 
						|
					// to get the biggest groups possible. Used modules are marked with usedModules
 | 
						|
					// TODO: Allow to reuse existing configuration while trying to add dependencies.
 | 
						|
					// This would improve performance. O(n^2) -> O(n)
 | 
						|
					logger.time("sort concat configurations");
 | 
						|
					concatConfigurations.sort((a, b) => b.modules.size - a.modules.size);
 | 
						|
					logger.timeEnd("sort concat configurations");
 | 
						|
					const usedModules = new Set();
 | 
						|
 | 
						|
					logger.time("create concatenated modules");
 | 
						|
					asyncLib.each(
 | 
						|
						concatConfigurations,
 | 
						|
						(concatConfiguration, callback) => {
 | 
						|
							const rootModule = concatConfiguration.rootModule;
 | 
						|
 | 
						|
							// Avoid overlapping configurations
 | 
						|
							// TODO: remove this when todo above is fixed
 | 
						|
							if (usedModules.has(rootModule)) return callback();
 | 
						|
							const modules = concatConfiguration.getModules();
 | 
						|
							for (const m of modules) {
 | 
						|
								usedModules.add(m);
 | 
						|
							}
 | 
						|
 | 
						|
							// Create a new ConcatenatedModule
 | 
						|
							const newModule = ConcatenatedModule.create(
 | 
						|
								rootModule,
 | 
						|
								modules,
 | 
						|
								concatConfiguration.runtime,
 | 
						|
								compilation,
 | 
						|
								compiler.root,
 | 
						|
								compilation.outputOptions.hashFunction
 | 
						|
							);
 | 
						|
 | 
						|
							const build = () => {
 | 
						|
								newModule.build(
 | 
						|
									compilation.options,
 | 
						|
									compilation,
 | 
						|
									/** @type {EXPECTED_ANY} */
 | 
						|
									(null),
 | 
						|
									/** @type {EXPECTED_ANY} */
 | 
						|
									(null),
 | 
						|
									(err) => {
 | 
						|
										if (err) {
 | 
						|
											if (!err.module) {
 | 
						|
												err.module = newModule;
 | 
						|
											}
 | 
						|
											return callback(err);
 | 
						|
										}
 | 
						|
										integrate();
 | 
						|
									}
 | 
						|
								);
 | 
						|
							};
 | 
						|
 | 
						|
							const integrate = () => {
 | 
						|
								if (backCompat) {
 | 
						|
									ChunkGraph.setChunkGraphForModule(newModule, chunkGraph);
 | 
						|
									ModuleGraph.setModuleGraphForModule(newModule, moduleGraph);
 | 
						|
								}
 | 
						|
 | 
						|
								for (const warning of concatConfiguration.getWarningsSorted()) {
 | 
						|
									moduleGraph
 | 
						|
										.getOptimizationBailout(newModule)
 | 
						|
										.push(formatBailoutWarning(warning[0], warning[1]));
 | 
						|
								}
 | 
						|
								moduleGraph.cloneModuleAttributes(rootModule, newModule);
 | 
						|
								for (const m of modules) {
 | 
						|
									// add to builtModules when one of the included modules was built
 | 
						|
									if (compilation.builtModules.has(m)) {
 | 
						|
										compilation.builtModules.add(newModule);
 | 
						|
									}
 | 
						|
									if (m !== rootModule) {
 | 
						|
										// attach external references to the concatenated module too
 | 
						|
										moduleGraph.copyOutgoingModuleConnections(
 | 
						|
											m,
 | 
						|
											newModule,
 | 
						|
											(c) =>
 | 
						|
												c.originModule === m &&
 | 
						|
												!(
 | 
						|
													c.dependency instanceof HarmonyImportDependency &&
 | 
						|
													modules.has(c.module)
 | 
						|
												)
 | 
						|
										);
 | 
						|
										// remove module from chunk
 | 
						|
										for (const chunk of chunkGraph.getModuleChunksIterable(
 | 
						|
											rootModule
 | 
						|
										)) {
 | 
						|
											const sourceTypes = chunkGraph.getChunkModuleSourceTypes(
 | 
						|
												chunk,
 | 
						|
												m
 | 
						|
											);
 | 
						|
											if (sourceTypes.size === 1) {
 | 
						|
												chunkGraph.disconnectChunkAndModule(chunk, m);
 | 
						|
											} else {
 | 
						|
												const newSourceTypes = new Set(sourceTypes);
 | 
						|
												newSourceTypes.delete(JS_TYPE);
 | 
						|
												chunkGraph.setChunkModuleSourceTypes(
 | 
						|
													chunk,
 | 
						|
													m,
 | 
						|
													newSourceTypes
 | 
						|
												);
 | 
						|
											}
 | 
						|
										}
 | 
						|
									}
 | 
						|
								}
 | 
						|
								compilation.modules.delete(rootModule);
 | 
						|
								ChunkGraph.clearChunkGraphForModule(rootModule);
 | 
						|
								ModuleGraph.clearModuleGraphForModule(rootModule);
 | 
						|
 | 
						|
								// remove module from chunk
 | 
						|
								chunkGraph.replaceModule(rootModule, newModule);
 | 
						|
								// replace module references with the concatenated module
 | 
						|
								moduleGraph.moveModuleConnections(
 | 
						|
									rootModule,
 | 
						|
									newModule,
 | 
						|
									(c) => {
 | 
						|
										const otherModule =
 | 
						|
											c.module === rootModule ? c.originModule : c.module;
 | 
						|
										const innerConnection =
 | 
						|
											c.dependency instanceof HarmonyImportDependency &&
 | 
						|
											modules.has(/** @type {Module} */ (otherModule));
 | 
						|
										return !innerConnection;
 | 
						|
									}
 | 
						|
								);
 | 
						|
								// add concatenated module to the compilation
 | 
						|
								compilation.modules.add(newModule);
 | 
						|
 | 
						|
								callback();
 | 
						|
							};
 | 
						|
 | 
						|
							build();
 | 
						|
						},
 | 
						|
						(err) => {
 | 
						|
							logger.timeEnd("create concatenated modules");
 | 
						|
							process.nextTick(callback.bind(null, err));
 | 
						|
						}
 | 
						|
					);
 | 
						|
				}
 | 
						|
			);
 | 
						|
		});
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {Compilation} compilation the compilation
 | 
						|
	 * @param {Module} module the module to be added
 | 
						|
	 * @param {RuntimeSpec} runtime the runtime scope
 | 
						|
	 * @returns {Set<Module>} the imported modules
 | 
						|
	 */
 | 
						|
	_getImports(compilation, module, runtime) {
 | 
						|
		const moduleGraph = compilation.moduleGraph;
 | 
						|
		const set = new Set();
 | 
						|
		for (const dep of module.dependencies) {
 | 
						|
			// Get reference info only for harmony Dependencies
 | 
						|
			if (!(dep instanceof HarmonyImportDependency)) continue;
 | 
						|
 | 
						|
			const connection = moduleGraph.getConnection(dep);
 | 
						|
			// Reference is valid and has a module
 | 
						|
			if (
 | 
						|
				!connection ||
 | 
						|
				!connection.module ||
 | 
						|
				!connection.isTargetActive(runtime)
 | 
						|
			) {
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			const importedNames = compilation.getDependencyReferencedExports(
 | 
						|
				dep,
 | 
						|
				undefined
 | 
						|
			);
 | 
						|
 | 
						|
			if (
 | 
						|
				importedNames.every((i) =>
 | 
						|
					Array.isArray(i) ? i.length > 0 : i.name.length > 0
 | 
						|
				) ||
 | 
						|
				Array.isArray(moduleGraph.getProvidedExports(module))
 | 
						|
			) {
 | 
						|
				set.add(connection.module);
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return set;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {Compilation} compilation webpack compilation
 | 
						|
	 * @param {ConcatConfiguration} config concat configuration (will be modified when added)
 | 
						|
	 * @param {Module} module the module to be added
 | 
						|
	 * @param {RuntimeSpec} runtime the runtime scope of the generated code
 | 
						|
	 * @param {RuntimeSpec} activeRuntime the runtime scope of the root module
 | 
						|
	 * @param {Set<Module>} possibleModules modules that are candidates
 | 
						|
	 * @param {Set<Module>} candidates list of potential candidates (will be added to)
 | 
						|
	 * @param {Map<Module, Module | ((requestShortener: RequestShortener) => string)>} failureCache cache for problematic modules to be more performant
 | 
						|
	 * @param {ChunkGraph} chunkGraph the chunk graph
 | 
						|
	 * @param {boolean} avoidMutateOnFailure avoid mutating the config when adding fails
 | 
						|
	 * @param {Statistics} statistics gathering metrics
 | 
						|
	 * @returns {null | Module | ((requestShortener: RequestShortener) => string)} the problematic module
 | 
						|
	 */
 | 
						|
	_tryToAdd(
 | 
						|
		compilation,
 | 
						|
		config,
 | 
						|
		module,
 | 
						|
		runtime,
 | 
						|
		activeRuntime,
 | 
						|
		possibleModules,
 | 
						|
		candidates,
 | 
						|
		failureCache,
 | 
						|
		chunkGraph,
 | 
						|
		avoidMutateOnFailure,
 | 
						|
		statistics
 | 
						|
	) {
 | 
						|
		const cacheEntry = failureCache.get(module);
 | 
						|
		if (cacheEntry) {
 | 
						|
			statistics.cached++;
 | 
						|
			return cacheEntry;
 | 
						|
		}
 | 
						|
 | 
						|
		// Already added?
 | 
						|
		if (config.has(module)) {
 | 
						|
			statistics.alreadyInConfig++;
 | 
						|
			return null;
 | 
						|
		}
 | 
						|
 | 
						|
		// Not possible to add?
 | 
						|
		if (!possibleModules.has(module)) {
 | 
						|
			statistics.invalidModule++;
 | 
						|
			failureCache.set(module, module); // cache failures for performance
 | 
						|
			return module;
 | 
						|
		}
 | 
						|
 | 
						|
		// Module must be in the correct chunks
 | 
						|
		const missingChunks = [
 | 
						|
			...chunkGraph.getModuleChunksIterable(config.rootModule)
 | 
						|
		].filter((chunk) => !chunkGraph.isModuleInChunk(module, chunk));
 | 
						|
		if (missingChunks.length > 0) {
 | 
						|
			/**
 | 
						|
			 * @param {RequestShortener} requestShortener request shortener
 | 
						|
			 * @returns {string} problem description
 | 
						|
			 */
 | 
						|
			const problem = (requestShortener) => {
 | 
						|
				const missingChunksList = [
 | 
						|
					...new Set(
 | 
						|
						missingChunks.map((chunk) => chunk.name || "unnamed chunk(s)")
 | 
						|
					)
 | 
						|
				].sort();
 | 
						|
				const chunks = [
 | 
						|
					...new Set(
 | 
						|
						[...chunkGraph.getModuleChunksIterable(module)].map(
 | 
						|
							(chunk) => chunk.name || "unnamed chunk(s)"
 | 
						|
						)
 | 
						|
					)
 | 
						|
				].sort();
 | 
						|
				return `Module ${module.readableIdentifier(
 | 
						|
					requestShortener
 | 
						|
				)} is not in the same chunk(s) (expected in chunk(s) ${missingChunksList.join(
 | 
						|
					", "
 | 
						|
				)}, module is in chunk(s) ${chunks.join(", ")})`;
 | 
						|
			};
 | 
						|
			statistics.incorrectChunks++;
 | 
						|
			failureCache.set(module, problem); // cache failures for performance
 | 
						|
			return problem;
 | 
						|
		}
 | 
						|
 | 
						|
		const moduleGraph = compilation.moduleGraph;
 | 
						|
 | 
						|
		const incomingConnections =
 | 
						|
			moduleGraph.getIncomingConnectionsByOriginModule(module);
 | 
						|
 | 
						|
		const incomingConnectionsFromNonModules =
 | 
						|
			incomingConnections.get(null) || incomingConnections.get(undefined);
 | 
						|
		if (incomingConnectionsFromNonModules) {
 | 
						|
			const activeNonModulesConnections =
 | 
						|
				incomingConnectionsFromNonModules.filter((connection) =>
 | 
						|
					// We are not interested in inactive connections
 | 
						|
					// or connections without dependency
 | 
						|
					connection.isActive(runtime)
 | 
						|
				);
 | 
						|
			if (activeNonModulesConnections.length > 0) {
 | 
						|
				/**
 | 
						|
				 * @param {RequestShortener} requestShortener request shortener
 | 
						|
				 * @returns {string} problem description
 | 
						|
				 */
 | 
						|
				const problem = (requestShortener) => {
 | 
						|
					const importingExplanations = new Set(
 | 
						|
						activeNonModulesConnections
 | 
						|
							.map((c) => c.explanation)
 | 
						|
							.filter(Boolean)
 | 
						|
					);
 | 
						|
					const explanations = [...importingExplanations].sort();
 | 
						|
					return `Module ${module.readableIdentifier(
 | 
						|
						requestShortener
 | 
						|
					)} is referenced ${
 | 
						|
						explanations.length > 0
 | 
						|
							? `by: ${explanations.join(", ")}`
 | 
						|
							: "in an unsupported way"
 | 
						|
					}`;
 | 
						|
				};
 | 
						|
				statistics.incorrectDependency++;
 | 
						|
				failureCache.set(module, problem); // cache failures for performance
 | 
						|
				return problem;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		/** @type {Map<Module, ReadonlyArray<ModuleGraph.ModuleGraphConnection>>} */
 | 
						|
		const incomingConnectionsFromModules = new Map();
 | 
						|
		for (const [originModule, connections] of incomingConnections) {
 | 
						|
			if (originModule) {
 | 
						|
				// Ignore connection from orphan modules
 | 
						|
				if (chunkGraph.getNumberOfModuleChunks(originModule) === 0) continue;
 | 
						|
 | 
						|
				// We don't care for connections from other runtimes
 | 
						|
				let originRuntime;
 | 
						|
				for (const r of chunkGraph.getModuleRuntimes(originModule)) {
 | 
						|
					originRuntime = mergeRuntimeOwned(originRuntime, r);
 | 
						|
				}
 | 
						|
 | 
						|
				if (!intersectRuntime(runtime, originRuntime)) continue;
 | 
						|
 | 
						|
				// We are not interested in inactive connections
 | 
						|
				const activeConnections = connections.filter((connection) =>
 | 
						|
					connection.isActive(runtime)
 | 
						|
				);
 | 
						|
				if (activeConnections.length > 0) {
 | 
						|
					incomingConnectionsFromModules.set(originModule, activeConnections);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		const incomingModules = [...incomingConnectionsFromModules.keys()];
 | 
						|
 | 
						|
		// Module must be in the same chunks like the referencing module
 | 
						|
		const otherChunkModules = incomingModules.filter((originModule) => {
 | 
						|
			for (const chunk of chunkGraph.getModuleChunksIterable(
 | 
						|
				config.rootModule
 | 
						|
			)) {
 | 
						|
				if (!chunkGraph.isModuleInChunk(originModule, chunk)) {
 | 
						|
					return true;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return false;
 | 
						|
		});
 | 
						|
		if (otherChunkModules.length > 0) {
 | 
						|
			/**
 | 
						|
			 * @param {RequestShortener} requestShortener request shortener
 | 
						|
			 * @returns {string} problem description
 | 
						|
			 */
 | 
						|
			const problem = (requestShortener) => {
 | 
						|
				const names = otherChunkModules
 | 
						|
					.map((m) => m.readableIdentifier(requestShortener))
 | 
						|
					.sort();
 | 
						|
				return `Module ${module.readableIdentifier(
 | 
						|
					requestShortener
 | 
						|
				)} is referenced from different chunks by these modules: ${names.join(
 | 
						|
					", "
 | 
						|
				)}`;
 | 
						|
			};
 | 
						|
			statistics.incorrectChunksOfImporter++;
 | 
						|
			failureCache.set(module, problem); // cache failures for performance
 | 
						|
			return problem;
 | 
						|
		}
 | 
						|
 | 
						|
		/** @type {Map<Module, ReadonlyArray<ModuleGraph.ModuleGraphConnection>>} */
 | 
						|
		const nonHarmonyConnections = new Map();
 | 
						|
		for (const [originModule, connections] of incomingConnectionsFromModules) {
 | 
						|
			const selected = connections.filter(
 | 
						|
				(connection) =>
 | 
						|
					!connection.dependency ||
 | 
						|
					!(connection.dependency instanceof HarmonyImportDependency)
 | 
						|
			);
 | 
						|
			if (selected.length > 0) {
 | 
						|
				nonHarmonyConnections.set(originModule, connections);
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (nonHarmonyConnections.size > 0) {
 | 
						|
			/**
 | 
						|
			 * @param {RequestShortener} requestShortener request shortener
 | 
						|
			 * @returns {string} problem description
 | 
						|
			 */
 | 
						|
			const problem = (requestShortener) => {
 | 
						|
				const names = [...nonHarmonyConnections]
 | 
						|
					.map(
 | 
						|
						([originModule, connections]) =>
 | 
						|
							`${originModule.readableIdentifier(
 | 
						|
								requestShortener
 | 
						|
							)} (referenced with ${[
 | 
						|
								...new Set(
 | 
						|
									connections
 | 
						|
										.map((c) => c.dependency && c.dependency.type)
 | 
						|
										.filter(Boolean)
 | 
						|
								)
 | 
						|
							]
 | 
						|
								.sort()
 | 
						|
								.join(", ")})`
 | 
						|
					)
 | 
						|
					.sort();
 | 
						|
				return `Module ${module.readableIdentifier(
 | 
						|
					requestShortener
 | 
						|
				)} is referenced from these modules with unsupported syntax: ${names.join(
 | 
						|
					", "
 | 
						|
				)}`;
 | 
						|
			};
 | 
						|
			statistics.incorrectModuleDependency++;
 | 
						|
			failureCache.set(module, problem); // cache failures for performance
 | 
						|
			return problem;
 | 
						|
		}
 | 
						|
 | 
						|
		if (runtime !== undefined && typeof runtime !== "string") {
 | 
						|
			// Module must be consistently referenced in the same runtimes
 | 
						|
			/** @type {{ originModule: Module, runtimeCondition: RuntimeSpec }[]} */
 | 
						|
			const otherRuntimeConnections = [];
 | 
						|
			outer: for (const [
 | 
						|
				originModule,
 | 
						|
				connections
 | 
						|
			] of incomingConnectionsFromModules) {
 | 
						|
				/** @type {false | RuntimeSpec} */
 | 
						|
				let currentRuntimeCondition = false;
 | 
						|
				for (const connection of connections) {
 | 
						|
					const runtimeCondition = filterRuntime(runtime, (runtime) =>
 | 
						|
						connection.isTargetActive(runtime)
 | 
						|
					);
 | 
						|
					if (runtimeCondition === false) continue;
 | 
						|
					if (runtimeCondition === true) continue outer;
 | 
						|
					currentRuntimeCondition =
 | 
						|
						currentRuntimeCondition !== false
 | 
						|
							? mergeRuntime(currentRuntimeCondition, runtimeCondition)
 | 
						|
							: runtimeCondition;
 | 
						|
				}
 | 
						|
				if (currentRuntimeCondition !== false) {
 | 
						|
					otherRuntimeConnections.push({
 | 
						|
						originModule,
 | 
						|
						runtimeCondition: currentRuntimeCondition
 | 
						|
					});
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (otherRuntimeConnections.length > 0) {
 | 
						|
				/**
 | 
						|
				 * @param {RequestShortener} requestShortener request shortener
 | 
						|
				 * @returns {string} problem description
 | 
						|
				 */
 | 
						|
				const problem = (requestShortener) =>
 | 
						|
					`Module ${module.readableIdentifier(
 | 
						|
						requestShortener
 | 
						|
					)} is runtime-dependent referenced by these modules: ${Array.from(
 | 
						|
						otherRuntimeConnections,
 | 
						|
						({ originModule, runtimeCondition }) =>
 | 
						|
							`${originModule.readableIdentifier(
 | 
						|
								requestShortener
 | 
						|
							)} (expected runtime ${runtimeToString(
 | 
						|
								runtime
 | 
						|
							)}, module is only referenced in ${runtimeToString(
 | 
						|
								/** @type {RuntimeSpec} */ (runtimeCondition)
 | 
						|
							)})`
 | 
						|
					).join(", ")}`;
 | 
						|
				statistics.incorrectRuntimeCondition++;
 | 
						|
				failureCache.set(module, problem); // cache failures for performance
 | 
						|
				return problem;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		let backup;
 | 
						|
		if (avoidMutateOnFailure) {
 | 
						|
			backup = config.snapshot();
 | 
						|
		}
 | 
						|
 | 
						|
		// Add the module
 | 
						|
		config.add(module);
 | 
						|
 | 
						|
		incomingModules.sort(compareModulesByIdentifier);
 | 
						|
 | 
						|
		// Every module which depends on the added module must be in the configuration too.
 | 
						|
		for (const originModule of incomingModules) {
 | 
						|
			const problem = this._tryToAdd(
 | 
						|
				compilation,
 | 
						|
				config,
 | 
						|
				originModule,
 | 
						|
				runtime,
 | 
						|
				activeRuntime,
 | 
						|
				possibleModules,
 | 
						|
				candidates,
 | 
						|
				failureCache,
 | 
						|
				chunkGraph,
 | 
						|
				false,
 | 
						|
				statistics
 | 
						|
			);
 | 
						|
			if (problem) {
 | 
						|
				if (backup !== undefined) config.rollback(backup);
 | 
						|
				statistics.importerFailed++;
 | 
						|
				failureCache.set(module, problem); // cache failures for performance
 | 
						|
				return problem;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Add imports to possible candidates list
 | 
						|
		for (const imp of this._getImports(compilation, module, runtime)) {
 | 
						|
			candidates.add(imp);
 | 
						|
		}
 | 
						|
		statistics.added++;
 | 
						|
		return null;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/** @typedef {Module | ((requestShortener: RequestShortener) => string)} Problem */
 | 
						|
 | 
						|
class ConcatConfiguration {
 | 
						|
	/**
 | 
						|
	 * @param {Module} rootModule the root module
 | 
						|
	 * @param {RuntimeSpec} runtime the runtime
 | 
						|
	 */
 | 
						|
	constructor(rootModule, runtime) {
 | 
						|
		this.rootModule = rootModule;
 | 
						|
		this.runtime = runtime;
 | 
						|
		/** @type {Set<Module>} */
 | 
						|
		this.modules = new Set();
 | 
						|
		this.modules.add(rootModule);
 | 
						|
		/** @type {Map<Module, Problem>} */
 | 
						|
		this.warnings = new Map();
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {Module} module the module
 | 
						|
	 */
 | 
						|
	add(module) {
 | 
						|
		this.modules.add(module);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {Module} module the module
 | 
						|
	 * @returns {boolean} true, when the module is in the module set
 | 
						|
	 */
 | 
						|
	has(module) {
 | 
						|
		return this.modules.has(module);
 | 
						|
	}
 | 
						|
 | 
						|
	isEmpty() {
 | 
						|
		return this.modules.size === 1;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {Module} module the module
 | 
						|
	 * @param {Problem} problem the problem
 | 
						|
	 */
 | 
						|
	addWarning(module, problem) {
 | 
						|
		this.warnings.set(module, problem);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @returns {Map<Module, Problem>} warnings
 | 
						|
	 */
 | 
						|
	getWarningsSorted() {
 | 
						|
		return new Map(
 | 
						|
			[...this.warnings].sort((a, b) => {
 | 
						|
				const ai = a[0].identifier();
 | 
						|
				const bi = b[0].identifier();
 | 
						|
				if (ai < bi) return -1;
 | 
						|
				if (ai > bi) return 1;
 | 
						|
				return 0;
 | 
						|
			})
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @returns {Set<Module>} modules as set
 | 
						|
	 */
 | 
						|
	getModules() {
 | 
						|
		return this.modules;
 | 
						|
	}
 | 
						|
 | 
						|
	snapshot() {
 | 
						|
		return this.modules.size;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param {number} snapshot snapshot
 | 
						|
	 */
 | 
						|
	rollback(snapshot) {
 | 
						|
		const modules = this.modules;
 | 
						|
		for (const m of modules) {
 | 
						|
			if (snapshot === 0) {
 | 
						|
				modules.delete(m);
 | 
						|
			} else {
 | 
						|
				snapshot--;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
module.exports = ModuleConcatenationPlugin;
 |