From 363723fc84f7b8477592e0105aeb331ec9a017af Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 14 Aug 2017 05:01:11 +0200 Subject: node_modules --- .../lib/optimize/ModuleConcatenationPlugin.js | 307 +++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 node_modules/webpack/lib/optimize/ModuleConcatenationPlugin.js (limited to 'node_modules/webpack/lib/optimize/ModuleConcatenationPlugin.js') diff --git a/node_modules/webpack/lib/optimize/ModuleConcatenationPlugin.js b/node_modules/webpack/lib/optimize/ModuleConcatenationPlugin.js new file mode 100644 index 000000000..9e797c75e --- /dev/null +++ b/node_modules/webpack/lib/optimize/ModuleConcatenationPlugin.js @@ -0,0 +1,307 @@ +/* + 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); + 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; -- cgit v1.2.3