aboutsummaryrefslogtreecommitdiff
path: root/node_modules/webpack/lib/optimize/ModuleConcatenationPlugin.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/webpack/lib/optimize/ModuleConcatenationPlugin.js')
-rw-r--r--node_modules/webpack/lib/optimize/ModuleConcatenationPlugin.js307
1 files changed, 307 insertions, 0 deletions
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;