Add semantic token highlighting for 'that' variable and comment file references in VS Code extension Add Phone_Text_Input and Currency_Input components with formatting utilities Implement client widgets, form standardization, and soft delete functionality Add modal scroll lock and update documentation Implement comprehensive modal system with form integration and validation Fix modal component instantiation using jQuery plugin API Implement modal system with responsive sizing, queuing, and validation support Implement form submission with validation, error handling, and loading states Implement country/state selectors with dynamic data loading and Bootstrap styling Revert Rsx::Route() highlighting in Blade/PHP files Target specific PHP scopes for Rsx::Route() highlighting in Blade Expand injection selector for Rsx::Route() highlighting Add custom syntax highlighting for Rsx::Route() and Rsx.Route() calls Update jqhtml packages to v2.2.165 Add bundle path validation for common mistakes (development mode only) Create Ajax_Select_Input widget and Rsx_Reference_Data controller Create Country_Select_Input widget with default country support Initialize Tom Select on Select_Input widgets Add Tom Select bundle for enhanced select dropdowns Implement ISO 3166 geographic data system for country/region selection Implement widget-based form system with disabled state support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
953 lines
29 KiB
JavaScript
Executable File
953 lines
29 KiB
JavaScript
Executable File
/*
|
|
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;
|