summaryrefslogtreecommitdiffstats
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.js485
1 files changed, 485 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 0000000..0c5bfd3
--- /dev/null
+++ b/node_modules/webpack/lib/optimize/ModuleConcatenationPlugin.js
@@ -0,0 +1,485 @@
+/*
+ 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 HarmonyCompatibilityDependency = require("../dependencies/HarmonyCompatibilityDependency");
+const StackedSetMap = require("../util/StackedSetMap");
+
+const formatBailoutReason = msg => {
+ return "ModuleConcatenation bailout: " + msg;
+};
+
+class ModuleConcatenationPlugin {
+ constructor(options) {
+ if (typeof options !== "object") options = {};
+ this.options = options;
+ }
+
+ apply(compiler) {
+ compiler.hooks.compilation.tap(
+ "ModuleConcatenationPlugin",
+ (compilation, { normalModuleFactory }) => {
+ const handler = (parser, parserOptions) => {
+ parser.hooks.call.for("eval").tap("ModuleConcatenationPlugin", () => {
+ // Because of variable renaming we can't use modules with eval.
+ parser.state.module.buildMeta.moduleConcatenationBailout = "eval()";
+ });
+ };
+
+ normalModuleFactory.hooks.parser
+ .for("javascript/auto")
+ .tap("ModuleConcatenationPlugin", handler);
+ normalModuleFactory.hooks.parser
+ .for("javascript/dynamic")
+ .tap("ModuleConcatenationPlugin", handler);
+ normalModuleFactory.hooks.parser
+ .for("javascript/esm")
+ .tap("ModuleConcatenationPlugin", handler);
+
+ const bailoutReasonMap = new Map();
+
+ const setBailoutReason = (module, reason) => {
+ bailoutReasonMap.set(module, reason);
+ module.optimizationBailout.push(
+ typeof reason === "function"
+ ? rs => formatBailoutReason(reason(rs))
+ : formatBailoutReason(reason)
+ );
+ };
+
+ const getBailoutReason = (module, requestShortener) => {
+ const reason = bailoutReasonMap.get(module);
+ if (typeof reason === "function") return reason(requestShortener);
+ return reason;
+ };
+
+ compilation.hooks.optimizeChunkModules.tap(
+ "ModuleConcatenationPlugin",
+ (allChunks, modules) => {
+ const relevantModules = [];
+ const possibleInners = new Set();
+ for (const module of modules) {
+ // Only harmony modules are valid for optimization
+ if (
+ !module.buildMeta ||
+ module.buildMeta.exportsType !== "namespace" ||
+ !module.dependencies.some(
+ d => d instanceof HarmonyCompatibilityDependency
+ )
+ ) {
+ setBailoutReason(module, "Module is not an ECMAScript module");
+ continue;
+ }
+
+ // Some expressions are not compatible with module concatenation
+ // because they may produce unexpected results. The plugin bails out
+ // if some were detected upfront.
+ if (
+ module.buildMeta &&
+ module.buildMeta.moduleConcatenationBailout
+ ) {
+ setBailoutReason(
+ module,
+ `Module uses ${module.buildMeta.moduleConcatenationBailout}`
+ );
+ continue;
+ }
+
+ // Exports must be known (and not dynamic)
+ if (!Array.isArray(module.buildMeta.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.isEntryModule()) {
+ setBailoutReason(module, "Module is an entry point");
+ continue;
+ }
+
+ // Module must be in any chunk (we don't want to do useless work)
+ if (module.getNumberOfChunks() === 0) {
+ setBailoutReason(module, "Module is not in any chunk");
+ continue;
+ }
+
+ // Module must only be used by Harmony Imports
+ const nonHarmonyReasons = module.reasons.filter(
+ reason =>
+ !reason.dependency ||
+ !(reason.dependency instanceof HarmonyImportDependency)
+ );
+ if (nonHarmonyReasons.length > 0) {
+ const importingModules = new Set(
+ nonHarmonyReasons.map(r => r.module).filter(Boolean)
+ );
+ const importingExplanations = new Set(
+ nonHarmonyReasons.map(r => r.explanation).filter(Boolean)
+ );
+ const importingModuleTypes = new Map(
+ Array.from(importingModules).map(
+ m => /** @type {[string, Set]} */ ([
+ 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();
+ const explanations = Array.from(importingExplanations).sort();
+ if (names.length > 0 && explanations.length === 0) {
+ return `Module is referenced from these modules with unsupported syntax: ${names.join(
+ ", "
+ )}`;
+ } else if (names.length === 0 && explanations.length > 0) {
+ return `Module is referenced by: ${explanations.join(
+ ", "
+ )}`;
+ } else if (names.length > 0 && explanations.length > 0) {
+ return `Module is referenced from these modules with unsupported syntax: ${names.join(
+ ", "
+ )} and by: ${explanations.join(", ")}`;
+ } else {
+ return "Module is referenced in a unsupported way";
+ }
+ });
+ 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(compilation, currentRoot)) {
+ const problem = this._tryToAdd(
+ compilation,
+ 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.getModules()) {
+ 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 modules = concatConfiguration.getModules();
+ const rootModule = concatConfiguration.rootModule;
+ const newModule = new ConcatenatedModule(
+ rootModule,
+ Array.from(modules),
+ ConcatenatedModule.createConcatenationList(
+ rootModule,
+ modules,
+ compilation
+ )
+ );
+ for (const warning of concatConfiguration.getWarningsSorted()) {
+ 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 modules) {
+ usedModules.add(m);
+ for (const chunk of chunks) {
+ chunk.removeModule(m);
+ }
+ }
+ for (const chunk of chunks) {
+ chunk.addModule(newModule);
+ newModule.addChunk(chunk);
+ }
+ for (const chunk of allChunks) {
+ if (chunk.entryModule === concatConfiguration.rootModule) {
+ chunk.entryModule = newModule;
+ }
+ }
+ compilation.modules.push(newModule);
+ for (const reason of newModule.reasons) {
+ if (reason.dependency.module === concatConfiguration.rootModule)
+ reason.dependency.module = newModule;
+ if (
+ reason.dependency.redirectedModule ===
+ concatConfiguration.rootModule
+ )
+ reason.dependency.redirectedModule = newModule;
+ }
+ // TODO: remove when LTS node version contains fixed v8 version
+ // @see https://github.com/webpack/webpack/pull/6613
+ // Turbofan does not correctly inline for-of loops with polymorphic input arrays.
+ // Work around issue by using a standard for loop and assigning dep.module.reasons
+ for (let i = 0; i < newModule.dependencies.length; i++) {
+ let dep = newModule.dependencies[i];
+ if (dep.module) {
+ let reasons = dep.module.reasons;
+ for (let j = 0; j < reasons.length; j++) {
+ let reason = reasons[j];
+ if (reason.dependency === dep) {
+ reason.module = newModule;
+ }
+ }
+ }
+ }
+ }
+ compilation.modules = compilation.modules.filter(
+ m => !usedModules.has(m)
+ );
+ }
+ );
+ }
+ );
+ }
+
+ _getImports(compilation, module) {
+ return new Set(
+ module.dependencies
+
+ // Get reference info only for harmony Dependencies
+ .map(dep => {
+ if (!(dep instanceof HarmonyImportDependency)) return null;
+ if (!compilation) return dep.getReference();
+ return compilation.getDependencyReference(module, dep);
+ })
+
+ // Reference is valid and has a module
+ // Dependencies are simple enough to concat them
+ .filter(
+ ref =>
+ ref &&
+ ref.module &&
+ (Array.isArray(ref.importedNames) ||
+ Array.isArray(ref.module.buildMeta.providedExports))
+ )
+
+ // Take the imported module
+ .map(ref => ref.module)
+ );
+ }
+
+ _tryToAdd(compilation, 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) {
+ // Modules that are not used can be ignored
+ if (
+ reason.module.factoryMeta.sideEffectFree &&
+ reason.module.used === false
+ )
+ continue;
+
+ const problem = this._tryToAdd(
+ compilation,
+ testConfig,
+ reason.module,
+ possibleModules,
+ failureCache
+ );
+ if (problem) {
+ failureCache.set(module, problem); // cache failures for performance
+ return problem;
+ }
+ }
+
+ // Commit experimental changes
+ config.set(testConfig);
+
+ // Eagerly try to add imports too if possible
+ for (const imp of this._getImports(compilation, module)) {
+ const problem = this._tryToAdd(
+ compilation,
+ config,
+ imp,
+ possibleModules,
+ failureCache
+ );
+ if (problem) {
+ config.addWarning(imp, problem);
+ }
+ }
+ return null;
+ }
+}
+
+class ConcatConfiguration {
+ constructor(rootModule, cloneFrom) {
+ this.rootModule = rootModule;
+ if (cloneFrom) {
+ this.modules = cloneFrom.modules.createChild(5);
+ this.warnings = cloneFrom.warnings.createChild(5);
+ } else {
+ this.modules = new StackedSetMap();
+ this.modules.add(rootModule);
+ this.warnings = new StackedSetMap();
+ }
+ }
+
+ 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);
+ }
+
+ getWarningsSorted() {
+ return new Map(
+ this.warnings.asPairArray().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;
+ })
+ );
+ }
+
+ getModules() {
+ return this.modules.asSet();
+ }
+
+ clone() {
+ return new ConcatConfiguration(this.rootModule, this);
+ }
+
+ set(config) {
+ this.rootModule = config.rootModule;
+ this.modules = config.modules;
+ this.warnings = config.warnings;
+ }
+}
+
+module.exports = ModuleConcatenationPlugin;