| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 | 
							- /*
 
- 	MIT License http://www.opensource.org/licenses/mit-license.php
 
- 	Author Tobias Koppers @sokra
 
- */
 
- "use strict";
 
- const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency");
 
- const ModuleHotAcceptDependency = require("../dependencies/ModuleHotAcceptDependency");
 
- const ModuleHotDeclineDependency = require("../dependencies/ModuleHotDeclineDependency");
 
- const ConcatenatedModule = require("./ConcatenatedModule");
 
- const HarmonyExportImportedSpecifierDependency = require("../dependencies/HarmonyExportImportedSpecifierDependency");
 
- const HarmonyCompatibilityDependency = require("../dependencies/HarmonyCompatibilityDependency");
 
- function formatBailoutReason(msg) {
 
- 	return "ModuleConcatenation bailout: " + msg;
 
- }
 
- class ModuleConcatenationPlugin {
 
- 	constructor(options) {
 
- 		if(typeof options !== "object") options = {};
 
- 		this.options = options;
 
- 	}
 
- 	apply(compiler) {
 
- 		compiler.plugin("compilation", (compilation, params) => {
 
- 			params.normalModuleFactory.plugin("parser", (parser, parserOptions) => {
 
- 				parser.plugin("call eval", () => {
 
- 					parser.state.module.meta.hasEval = true;
 
- 				});
 
- 			});
 
- 			const bailoutReasonMap = new Map();
 
- 			function setBailoutReason(module, reason) {
 
- 				bailoutReasonMap.set(module, reason);
 
- 				module.optimizationBailout.push(typeof reason === "function" ? (rs) => formatBailoutReason(reason(rs)) : formatBailoutReason(reason));
 
- 			}
 
- 			function getBailoutReason(module, requestShortener) {
 
- 				const reason = bailoutReasonMap.get(module);
 
- 				if(typeof reason === "function") return reason(requestShortener);
 
- 				return reason;
 
- 			}
 
- 			compilation.plugin("optimize-chunk-modules", (chunks, modules) => {
 
- 				const relevantModules = [];
 
- 				const possibleInners = new Set();
 
- 				for(const module of modules) {
 
- 					// Only harmony modules are valid for optimization
 
- 					if(!module.meta || !module.meta.harmonyModule || !module.dependencies.some(d => d instanceof HarmonyCompatibilityDependency)) {
 
- 						setBailoutReason(module, "Module is not an ECMAScript module");
 
- 						continue;
 
- 					}
 
- 					// Because of variable renaming we can't use modules with eval
 
- 					if(module.meta && module.meta.hasEval) {
 
- 						setBailoutReason(module, "Module uses eval()");
 
- 						continue;
 
- 					}
 
- 					// Exports must be known (and not dynamic)
 
- 					if(!Array.isArray(module.providedExports)) {
 
- 						setBailoutReason(module, "Module exports are unknown");
 
- 						continue;
 
- 					}
 
- 					// Using dependency variables is not possible as this wraps the code in a function
 
- 					if(module.variables.length > 0) {
 
- 						setBailoutReason(module, `Module uses injected variables (${module.variables.map(v => v.name).join(", ")})`);
 
- 						continue;
 
- 					}
 
- 					// Hot Module Replacement need it's own module to work correctly
 
- 					if(module.dependencies.some(dep => dep instanceof ModuleHotAcceptDependency || dep instanceof ModuleHotDeclineDependency)) {
 
- 						setBailoutReason(module, "Module uses Hot Module Replacement");
 
- 						continue;
 
- 					}
 
- 					relevantModules.push(module);
 
- 					// Module must not be the entry points
 
- 					if(module.getChunks().some(chunk => chunk.entryModule === module)) {
 
- 						setBailoutReason(module, "Module is an entry point");
 
- 						continue;
 
- 					}
 
- 					// Module must only be used by Harmony Imports
 
- 					const nonHarmonyReasons = module.reasons.filter(reason => !(reason.dependency instanceof HarmonyImportDependency));
 
- 					if(nonHarmonyReasons.length > 0) {
 
- 						const importingModules = new Set(nonHarmonyReasons.map(r => r.module));
 
- 						const importingModuleTypes = new Map(Array.from(importingModules).map(m => [m, new Set(nonHarmonyReasons.filter(r => r.module === m).map(r => r.dependency.type).sort())]));
 
- 						setBailoutReason(module, (requestShortener) => {
 
- 							const names = Array.from(importingModules).map(m => `${m.readableIdentifier(requestShortener)} (referenced with ${Array.from(importingModuleTypes.get(m)).join(", ")})`).sort();
 
- 							return `Module is referenced from these modules with unsupported syntax: ${names.join(", ")}`;
 
- 						});
 
- 						continue;
 
- 					}
 
- 					possibleInners.add(module);
 
- 				}
 
- 				// sort by depth
 
- 				// modules with lower depth are more likely suited as roots
 
- 				// this improves performance, because modules already selected as inner are skipped
 
- 				relevantModules.sort((a, b) => {
 
- 					return a.depth - b.depth;
 
- 				});
 
- 				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
 
- 					if(usedAsInner.has(currentRoot))
 
- 						continue;
 
- 					// create a configuration with the root
 
- 					const currentConfiguration = new ConcatConfiguration(currentRoot);
 
- 					// cache failures to add modules
 
- 					const failureCache = new Map();
 
- 					// try to add all imports
 
- 					for(const imp of this.getImports(currentRoot)) {
 
- 						const problem = this.tryToAdd(currentConfiguration, imp, possibleInners, failureCache);
 
- 						if(problem) {
 
- 							failureCache.set(imp, problem);
 
- 							currentConfiguration.addWarning(imp, problem);
 
- 						}
 
- 					}
 
- 					if(!currentConfiguration.isEmpty()) {
 
- 						concatConfigurations.push(currentConfiguration);
 
- 						for(const module of currentConfiguration.modules) {
 
- 							if(module !== currentConfiguration.rootModule)
 
- 								usedAsInner.add(module);
 
- 						}
 
- 					}
 
- 				}
 
- 				// HACK: Sort configurations by length and start with the longest one
 
- 				// to get the biggers 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)
 
- 				concatConfigurations.sort((a, b) => {
 
- 					return b.modules.size - a.modules.size;
 
- 				});
 
- 				const usedModules = new Set();
 
- 				for(const concatConfiguration of concatConfigurations) {
 
- 					if(usedModules.has(concatConfiguration.rootModule))
 
- 						continue;
 
- 					const newModule = new ConcatenatedModule(concatConfiguration.rootModule, Array.from(concatConfiguration.modules));
 
- 					concatConfiguration.sortWarnings();
 
- 					for(const warning of concatConfiguration.warnings) {
 
- 						newModule.optimizationBailout.push((requestShortener) => {
 
- 							const reason = getBailoutReason(warning[0], requestShortener);
 
- 							const reasonWithPrefix = reason ? ` (<- ${reason})` : "";
 
- 							if(warning[0] === warning[1])
 
- 								return formatBailoutReason(`Cannot concat with ${warning[0].readableIdentifier(requestShortener)}${reasonWithPrefix}`);
 
- 							else
 
- 								return formatBailoutReason(`Cannot concat with ${warning[0].readableIdentifier(requestShortener)} because of ${warning[1].readableIdentifier(requestShortener)}${reasonWithPrefix}`);
 
- 						});
 
- 					}
 
- 					const chunks = concatConfiguration.rootModule.getChunks();
 
- 					for(const m of concatConfiguration.modules) {
 
- 						usedModules.add(m);
 
- 						chunks.forEach(chunk => chunk.removeModule(m));
 
- 					}
 
- 					chunks.forEach(chunk => {
 
- 						chunk.addModule(newModule);
 
- 						newModule.addChunk(chunk);
 
- 						if(chunk.entryModule === concatConfiguration.rootModule)
 
- 							chunk.entryModule = newModule;
 
- 					});
 
- 					compilation.modules.push(newModule);
 
- 					newModule.reasons.forEach(reason => reason.dependency.module = newModule);
 
- 					newModule.dependencies.forEach(dep => {
 
- 						if(dep.module) {
 
- 							dep.module.reasons.forEach(reason => {
 
- 								if(reason.dependency === dep)
 
- 									reason.module = newModule;
 
- 							});
 
- 						}
 
- 					});
 
- 				}
 
- 				compilation.modules = compilation.modules.filter(m => !usedModules.has(m));
 
- 			});
 
- 		});
 
- 	}
 
- 	getImports(module) {
 
- 		return Array.from(new Set(module.dependencies
 
- 			// Only harmony Dependencies
 
- 			.filter(dep => dep instanceof HarmonyImportDependency && dep.module)
 
- 			// Dependencies are simple enough to concat them
 
- 			.filter(dep => {
 
- 				return !module.dependencies.some(d =>
 
- 					d instanceof HarmonyExportImportedSpecifierDependency &&
 
- 					d.importDependency === dep &&
 
- 					!d.id &&
 
- 					!Array.isArray(dep.module.providedExports)
 
- 				);
 
- 			})
 
- 			// Take the imported module
 
- 			.map(dep => dep.module)
 
- 		));
 
- 	}
 
- 	tryToAdd(config, module, possibleModules, failureCache) {
 
- 		const cacheEntry = failureCache.get(module);
 
- 		if(cacheEntry) {
 
- 			return cacheEntry;
 
- 		}
 
- 		// Already added?
 
- 		if(config.has(module)) {
 
- 			return null;
 
- 		}
 
- 		// Not possible to add?
 
- 		if(!possibleModules.has(module)) {
 
- 			failureCache.set(module, module); // cache failures for performance
 
- 			return module;
 
- 		}
 
- 		// module must be in the same chunks
 
- 		if(!config.rootModule.hasEqualsChunks(module)) {
 
- 			failureCache.set(module, module); // cache failures for performance
 
- 			return module;
 
- 		}
 
- 		// Clone config to make experimental changes
 
- 		const testConfig = config.clone();
 
- 		// Add the module
 
- 		testConfig.add(module);
 
- 		// Every module which depends on the added module must be in the configuration too.
 
- 		for(const reason of module.reasons) {
 
- 			const problem = this.tryToAdd(testConfig, reason.module, possibleModules, failureCache);
 
- 			if(problem) {
 
- 				failureCache.set(module, problem); // cache failures for performance
 
- 				return problem;
 
- 			}
 
- 		}
 
- 		// Eagerly try to add imports too if possible
 
- 		for(const imp of this.getImports(module)) {
 
- 			const problem = this.tryToAdd(testConfig, imp, possibleModules, failureCache);
 
- 			if(problem) {
 
- 				config.addWarning(module, problem);
 
- 			}
 
- 		}
 
- 		// Commit experimental changes
 
- 		config.set(testConfig);
 
- 		return null;
 
- 	}
 
- }
 
- class ConcatConfiguration {
 
- 	constructor(rootModule) {
 
- 		this.rootModule = rootModule;
 
- 		this.modules = new Set([rootModule]);
 
- 		this.warnings = new Map();
 
- 	}
 
- 	add(module) {
 
- 		this.modules.add(module);
 
- 	}
 
- 	has(module) {
 
- 		return this.modules.has(module);
 
- 	}
 
- 	isEmpty() {
 
- 		return this.modules.size === 1;
 
- 	}
 
- 	addWarning(module, problem) {
 
- 		this.warnings.set(module, problem);
 
- 	}
 
- 	sortWarnings() {
 
- 		this.warnings = new Map(Array.from(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;
 
- 		}));
 
- 	}
 
- 	clone() {
 
- 		const clone = new ConcatConfiguration(this.rootModule);
 
- 		for(const module of this.modules)
 
- 			clone.add(module);
 
- 		for(const pair of this.warnings)
 
- 			clone.addWarning(pair[0], pair[1]);
 
- 		return clone;
 
- 	}
 
- 	set(config) {
 
- 		this.rootModule = config.rootModule;
 
- 		this.modules = new Set(config.modules);
 
- 		this.warnings = new Map(config.warnings);
 
- 	}
 
- }
 
- module.exports = ModuleConcatenationPlugin;
 
 
  |