1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 | const asyncLib = require("neo-async");
|
9 | const ChunkGraph = require("../ChunkGraph");
|
10 | const ModuleGraph = require("../ModuleGraph");
|
11 | const { STAGE_DEFAULT } = require("../OptimizationStages");
|
12 | const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency");
|
13 | const { compareModulesByIdentifier } = require("../util/comparators");
|
14 | const {
|
15 | intersectRuntime,
|
16 | mergeRuntimeOwned,
|
17 | filterRuntime,
|
18 | runtimeToString,
|
19 | mergeRuntime
|
20 | } = require("../util/runtime");
|
21 | const ConcatenatedModule = require("./ConcatenatedModule");
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | const formatBailoutReason = msg => {
|
44 | return "ModuleConcatenation bailout: " + msg;
|
45 | };
|
46 |
|
47 | class ModuleConcatenationPlugin {
|
48 | constructor(options) {
|
49 | if (typeof options !== "object") options = {};
|
50 | this.options = options;
|
51 | }
|
52 |
|
53 | |
54 |
|
55 |
|
56 |
|
57 |
|
58 | apply(compiler) {
|
59 | compiler.hooks.compilation.tap("ModuleConcatenationPlugin", compilation => {
|
60 | const moduleGraph = compilation.moduleGraph;
|
61 | const bailoutReasonMap = new Map();
|
62 |
|
63 | const setBailoutReason = (module, reason) => {
|
64 | setInnerBailoutReason(module, reason);
|
65 | moduleGraph
|
66 | .getOptimizationBailout(module)
|
67 | .push(
|
68 | typeof reason === "function"
|
69 | ? rs => formatBailoutReason(reason(rs))
|
70 | : formatBailoutReason(reason)
|
71 | );
|
72 | };
|
73 |
|
74 | const setInnerBailoutReason = (module, reason) => {
|
75 | bailoutReasonMap.set(module, reason);
|
76 | };
|
77 |
|
78 | const getInnerBailoutReason = (module, requestShortener) => {
|
79 | const reason = bailoutReasonMap.get(module);
|
80 | if (typeof reason === "function") return reason(requestShortener);
|
81 | return reason;
|
82 | };
|
83 |
|
84 | const formatBailoutWarning = (module, problem) => requestShortener => {
|
85 | if (typeof problem === "function") {
|
86 | return formatBailoutReason(
|
87 | `Cannot concat with ${module.readableIdentifier(
|
88 | requestShortener
|
89 | )}: ${problem(requestShortener)}`
|
90 | );
|
91 | }
|
92 | const reason = getInnerBailoutReason(module, requestShortener);
|
93 | const reasonWithPrefix = reason ? `: ${reason}` : "";
|
94 | if (module === problem) {
|
95 | return formatBailoutReason(
|
96 | `Cannot concat with ${module.readableIdentifier(
|
97 | requestShortener
|
98 | )}${reasonWithPrefix}`
|
99 | );
|
100 | } else {
|
101 | return formatBailoutReason(
|
102 | `Cannot concat with ${module.readableIdentifier(
|
103 | requestShortener
|
104 | )} because of ${problem.readableIdentifier(
|
105 | requestShortener
|
106 | )}${reasonWithPrefix}`
|
107 | );
|
108 | }
|
109 | };
|
110 |
|
111 | compilation.hooks.optimizeChunkModules.tapAsync(
|
112 | {
|
113 | name: "ModuleConcatenationPlugin",
|
114 | stage: STAGE_DEFAULT
|
115 | },
|
116 | (allChunks, modules, callback) => {
|
117 | const logger = compilation.getLogger(
|
118 | "webpack.ModuleConcatenationPlugin"
|
119 | );
|
120 | const { chunkGraph, moduleGraph } = compilation;
|
121 | const relevantModules = [];
|
122 | const possibleInners = new Set();
|
123 | const context = {
|
124 | chunkGraph,
|
125 | moduleGraph
|
126 | };
|
127 | logger.time("select relevant modules");
|
128 | for (const module of modules) {
|
129 | let canBeRoot = true;
|
130 | let canBeInner = true;
|
131 |
|
132 | const bailoutReason = module.getConcatenationBailoutReason(context);
|
133 | if (bailoutReason) {
|
134 | setBailoutReason(module, bailoutReason);
|
135 | continue;
|
136 | }
|
137 |
|
138 |
|
139 | if (moduleGraph.isAsync(module)) {
|
140 | setBailoutReason(module, `Module is async`);
|
141 | continue;
|
142 | }
|
143 |
|
144 |
|
145 | if (!module.buildInfo.strict) {
|
146 | setBailoutReason(module, `Module is not in strict mode`);
|
147 | continue;
|
148 | }
|
149 |
|
150 |
|
151 | if (chunkGraph.getNumberOfModuleChunks(module) === 0) {
|
152 | setBailoutReason(module, "Module is not in any chunk");
|
153 | continue;
|
154 | }
|
155 |
|
156 |
|
157 | const exportsInfo = moduleGraph.getExportsInfo(module);
|
158 | const relevantExports = exportsInfo.getRelevantExports(undefined);
|
159 | const unknownReexports = relevantExports.filter(exportInfo => {
|
160 | return (
|
161 | exportInfo.isReexport() && !exportInfo.getTarget(moduleGraph)
|
162 | );
|
163 | });
|
164 | if (unknownReexports.length > 0) {
|
165 | setBailoutReason(
|
166 | module,
|
167 | `Reexports in this module do not have a static target (${Array.from(
|
168 | unknownReexports,
|
169 | exportInfo =>
|
170 | `${
|
171 | exportInfo.name || "other exports"
|
172 | }: ${exportInfo.getUsedInfo()}`
|
173 | ).join(", ")})`
|
174 | );
|
175 | continue;
|
176 | }
|
177 |
|
178 |
|
179 | const unknownProvidedExports = relevantExports.filter(
|
180 | exportInfo => {
|
181 | return exportInfo.provided !== true;
|
182 | }
|
183 | );
|
184 | if (unknownProvidedExports.length > 0) {
|
185 | setBailoutReason(
|
186 | module,
|
187 | `List of module exports is dynamic (${Array.from(
|
188 | unknownProvidedExports,
|
189 | exportInfo =>
|
190 | `${
|
191 | exportInfo.name || "other exports"
|
192 | }: ${exportInfo.getProvidedInfo()} and ${exportInfo.getUsedInfo()}`
|
193 | ).join(", ")})`
|
194 | );
|
195 | canBeRoot = false;
|
196 | }
|
197 |
|
198 |
|
199 | if (chunkGraph.isEntryModule(module)) {
|
200 | setInnerBailoutReason(module, "Module is an entry point");
|
201 | canBeInner = false;
|
202 | }
|
203 |
|
204 | if (canBeRoot) relevantModules.push(module);
|
205 | if (canBeInner) possibleInners.add(module);
|
206 | }
|
207 | logger.timeEnd("select relevant modules");
|
208 | logger.debug(
|
209 | `${relevantModules.length} potential root modules, ${possibleInners.size} potential inner modules`
|
210 | );
|
211 |
|
212 |
|
213 |
|
214 | logger.time("sort relevant modules");
|
215 | relevantModules.sort((a, b) => {
|
216 | return moduleGraph.getDepth(a) - moduleGraph.getDepth(b);
|
217 | });
|
218 | logger.timeEnd("sort relevant modules");
|
219 |
|
220 |
|
221 | const stats = {
|
222 | cached: 0,
|
223 | alreadyInConfig: 0,
|
224 | invalidModule: 0,
|
225 | incorrectChunks: 0,
|
226 | incorrectDependency: 0,
|
227 | incorrectModuleDependency: 0,
|
228 | incorrectChunksOfImporter: 0,
|
229 | incorrectRuntimeCondition: 0,
|
230 | importerFailed: 0,
|
231 | added: 0
|
232 | };
|
233 | let statsCandidates = 0;
|
234 | let statsSizeSum = 0;
|
235 | let statsEmptyConfigurations = 0;
|
236 |
|
237 | logger.time("find modules to concatenate");
|
238 | const concatConfigurations = [];
|
239 | const usedAsInner = new Set();
|
240 | for (const currentRoot of relevantModules) {
|
241 |
|
242 |
|
243 |
|
244 | if (usedAsInner.has(currentRoot)) continue;
|
245 |
|
246 | let chunkRuntime = undefined;
|
247 | for (const r of chunkGraph.getModuleRuntimes(currentRoot)) {
|
248 | chunkRuntime = mergeRuntimeOwned(chunkRuntime, r);
|
249 | }
|
250 | const exportsInfo = moduleGraph.getExportsInfo(currentRoot);
|
251 | const filteredRuntime = filterRuntime(chunkRuntime, r =>
|
252 | exportsInfo.isModuleUsed(r)
|
253 | );
|
254 | const activeRuntime =
|
255 | filteredRuntime === true
|
256 | ? chunkRuntime
|
257 | : filteredRuntime === false
|
258 | ? undefined
|
259 | : filteredRuntime;
|
260 |
|
261 |
|
262 | const currentConfiguration = new ConcatConfiguration(
|
263 | currentRoot,
|
264 | activeRuntime
|
265 | );
|
266 |
|
267 |
|
268 | const failureCache = new Map();
|
269 |
|
270 |
|
271 |
|
272 | const candidates = new Set();
|
273 |
|
274 |
|
275 | for (const imp of this._getImports(
|
276 | compilation,
|
277 | currentRoot,
|
278 | activeRuntime
|
279 | )) {
|
280 | candidates.add(imp);
|
281 | }
|
282 |
|
283 | for (const imp of candidates) {
|
284 | const impCandidates = new Set();
|
285 | const problem = this._tryToAdd(
|
286 | compilation,
|
287 | currentConfiguration,
|
288 | imp,
|
289 | chunkRuntime,
|
290 | activeRuntime,
|
291 | possibleInners,
|
292 | impCandidates,
|
293 | failureCache,
|
294 | chunkGraph,
|
295 | true,
|
296 | stats
|
297 | );
|
298 | if (problem) {
|
299 | failureCache.set(imp, problem);
|
300 | currentConfiguration.addWarning(imp, problem);
|
301 | } else {
|
302 | for (const c of impCandidates) {
|
303 | candidates.add(c);
|
304 | }
|
305 | }
|
306 | }
|
307 | statsCandidates += candidates.size;
|
308 | if (!currentConfiguration.isEmpty()) {
|
309 | const modules = currentConfiguration.getModules();
|
310 | statsSizeSum += modules.size;
|
311 | concatConfigurations.push(currentConfiguration);
|
312 | for (const module of modules) {
|
313 | if (module !== currentConfiguration.rootModule) {
|
314 | usedAsInner.add(module);
|
315 | }
|
316 | }
|
317 | } else {
|
318 | statsEmptyConfigurations++;
|
319 | const optimizationBailouts =
|
320 | moduleGraph.getOptimizationBailout(currentRoot);
|
321 | for (const warning of currentConfiguration.getWarningsSorted()) {
|
322 | optimizationBailouts.push(
|
323 | formatBailoutWarning(warning[0], warning[1])
|
324 | );
|
325 | }
|
326 | }
|
327 | }
|
328 | logger.timeEnd("find modules to concatenate");
|
329 | logger.debug(
|
330 | `${
|
331 | concatConfigurations.length
|
332 | } successful concat configurations (avg size: ${
|
333 | statsSizeSum / concatConfigurations.length
|
334 | }), ${statsEmptyConfigurations} bailed out completely`
|
335 | );
|
336 | logger.debug(
|
337 | `${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)`
|
338 | );
|
339 | // HACK: Sort configurations by length and start with the longest one
|
340 | // to get the biggest groups possible. Used modules are marked with usedModules
|
341 | // TODO: Allow to reuse existing configuration while trying to add dependencies.
|
342 | // This would improve performance. O(n^2) -> O(n)
|
343 | logger.time(`sort concat configurations`);
|
344 | concatConfigurations.sort((a, b) => {
|
345 | return b.modules.size - a.modules.size;
|
346 | });
|
347 | logger.timeEnd(`sort concat configurations`);
|
348 | const usedModules = new Set();
|
349 |
|
350 | logger.time("create concatenated modules");
|
351 | asyncLib.each(
|
352 | concatConfigurations,
|
353 | (concatConfiguration, callback) => {
|
354 | const rootModule = concatConfiguration.rootModule;
|
355 |
|
356 | // Avoid overlapping configurations
|
357 | // TODO: remove this when todo above is fixed
|
358 | if (usedModules.has(rootModule)) return callback();
|
359 | const modules = concatConfiguration.getModules();
|
360 | for (const m of modules) {
|
361 | usedModules.add(m);
|
362 | }
|
363 |
|
364 | // Create a new ConcatenatedModule
|
365 | let newModule = ConcatenatedModule.create(
|
366 | rootModule,
|
367 | modules,
|
368 | concatConfiguration.runtime,
|
369 | compiler.root,
|
370 | compilation.outputOptions.hashFunction
|
371 | );
|
372 |
|
373 | const build = () => {
|
374 | newModule.build(
|
375 | compiler.options,
|
376 | compilation,
|
377 | null,
|
378 | null,
|
379 | err => {
|
380 | if (err) {
|
381 | if (!err.module) {
|
382 | err.module = newModule;
|
383 | }
|
384 | return callback(err);
|
385 | }
|
386 | integrate();
|
387 | }
|
388 | );
|
389 | };
|
390 |
|
391 | const integrate = () => {
|
392 | ChunkGraph.setChunkGraphForModule(newModule, chunkGraph);
|
393 | ModuleGraph.setModuleGraphForModule(newModule, moduleGraph);
|
394 |
|
395 | for (const warning of concatConfiguration.getWarningsSorted()) {
|
396 | moduleGraph
|
397 | .getOptimizationBailout(newModule)
|
398 | .push(formatBailoutWarning(warning[0], warning[1]));
|
399 | }
|
400 | moduleGraph.cloneModuleAttributes(rootModule, newModule);
|
401 | for (const m of modules) {
|
402 | // add to builtModules when one of the included modules was built
|
403 | if (compilation.builtModules.has(m)) {
|
404 | compilation.builtModules.add(newModule);
|
405 | }
|
406 | if (m !== rootModule) {
|
407 | // attach external references to the concatenated module too
|
408 | moduleGraph.copyOutgoingModuleConnections(
|
409 | m,
|
410 | newModule,
|
411 | c => {
|
412 | return (
|
413 | c.originModule === m &&
|
414 | !(
|
415 | c.dependency instanceof HarmonyImportDependency &&
|
416 | modules.has(c.module)
|
417 | )
|
418 | );
|
419 | }
|
420 | );
|
421 | // remove module from chunk
|
422 | for (const chunk of chunkGraph.getModuleChunksIterable(
|
423 | rootModule
|
424 | )) {
|
425 | chunkGraph.disconnectChunkAndModule(chunk, m);
|
426 | }
|
427 | }
|
428 | }
|
429 | compilation.modules.delete(rootModule);
|
430 | ChunkGraph.clearChunkGraphForModule(rootModule);
|
431 | ModuleGraph.clearModuleGraphForModule(rootModule);
|
432 |
|
433 | // remove module from chunk
|
434 | chunkGraph.replaceModule(rootModule, newModule);
|
435 | // replace module references with the concatenated module
|
436 | moduleGraph.moveModuleConnections(rootModule, newModule, c => {
|
437 | const otherModule =
|
438 | c.module === rootModule ? c.originModule : c.module;
|
439 | const innerConnection =
|
440 | c.dependency instanceof HarmonyImportDependency &&
|
441 | modules.has(otherModule);
|
442 | return !innerConnection;
|
443 | });
|
444 | // add concatenated module to the compilation
|
445 | compilation.modules.add(newModule);
|
446 |
|
447 | callback();
|
448 | };
|
449 |
|
450 | build();
|
451 | },
|
452 | err => {
|
453 | logger.timeEnd("create concatenated modules");
|
454 | process.nextTick(callback.bind(null, err));
|
455 | }
|
456 | );
|
457 | }
|
458 | );
|
459 | });
|
460 | }
|
461 |
|
462 | /**
|
463 | * @param {Compilation} compilation the compilation
|
464 | * @param {Module} module the module to be added
|
465 | * @param {RuntimeSpec} runtime the runtime scope
|
466 | * @returns {Set<Module>} the imported modules
|
467 | */
|
468 | _getImports(compilation, module, runtime) {
|
469 | const moduleGraph = compilation.moduleGraph;
|
470 | const set = new Set();
|
471 | for (const dep of module.dependencies) {
|
472 | // Get reference info only for harmony Dependencies
|
473 | if (!(dep instanceof HarmonyImportDependency)) continue;
|
474 |
|
475 | const connection = moduleGraph.getConnection(dep);
|
476 | // Reference is valid and has a module
|
477 | if (
|
478 | !connection ||
|
479 | !connection.module ||
|
480 | !connection.isTargetActive(runtime)
|
481 | ) {
|
482 | continue;
|
483 | }
|
484 |
|
485 | const importedNames = compilation.getDependencyReferencedExports(
|
486 | dep,
|
487 | undefined
|
488 | );
|
489 |
|
490 | if (
|
491 | importedNames.every(i =>
|
492 | Array.isArray(i) ? i.length > 0 : i.name.length > 0
|
493 | ) ||
|
494 | Array.isArray(moduleGraph.getProvidedExports(module))
|
495 | ) {
|
496 | set.add(connection.module);
|
497 | }
|
498 | }
|
499 | return set;
|
500 | }
|
501 |
|
502 | /**
|
503 | * @param {Compilation} compilation webpack compilation
|
504 | * @param {ConcatConfiguration} config concat configuration (will be modified when added)
|
505 | * @param {Module} module the module to be added
|
506 | * @param {RuntimeSpec} runtime the runtime scope of the generated code
|
507 | * @param {RuntimeSpec} activeRuntime the runtime scope of the root module
|
508 | * @param {Set<Module>} possibleModules modules that are candidates
|
509 | * @param {Set<Module>} candidates list of potential candidates (will be added to)
|
510 | * @param {Map<Module, Module | function(RequestShortener): string>} failureCache cache for problematic modules to be more performant
|
511 | * @param {ChunkGraph} chunkGraph the chunk graph
|
512 | * @param {boolean} avoidMutateOnFailure avoid mutating the config when adding fails
|
513 | * @param {Statistics} statistics gathering metrics
|
514 | * @returns {Module | function(RequestShortener): string} the problematic module
|
515 | */
|
516 | _tryToAdd(
|
517 | compilation,
|
518 | config,
|
519 | module,
|
520 | runtime,
|
521 | activeRuntime,
|
522 | possibleModules,
|
523 | candidates,
|
524 | failureCache,
|
525 | chunkGraph,
|
526 | avoidMutateOnFailure,
|
527 | statistics
|
528 | ) {
|
529 | const cacheEntry = failureCache.get(module);
|
530 | if (cacheEntry) {
|
531 | statistics.cached++;
|
532 | return cacheEntry;
|
533 | }
|
534 |
|
535 | // Already added?
|
536 | if (config.has(module)) {
|
537 | statistics.alreadyInConfig++;
|
538 | return null;
|
539 | }
|
540 |
|
541 | // Not possible to add?
|
542 | if (!possibleModules.has(module)) {
|
543 | statistics.invalidModule++;
|
544 | failureCache.set(module, module); // cache failures for performance
|
545 | return module;
|
546 | }
|
547 |
|
548 | // Module must be in the correct chunks
|
549 | const missingChunks = Array.from(
|
550 | chunkGraph.getModuleChunksIterable(config.rootModule)
|
551 | ).filter(chunk => !chunkGraph.isModuleInChunk(module, chunk));
|
552 | if (missingChunks.length > 0) {
|
553 | const problem = requestShortener => {
|
554 | const missingChunksList = Array.from(
|
555 | new Set(missingChunks.map(chunk => chunk.name || "unnamed chunk(s)"))
|
556 | ).sort();
|
557 | const chunks = Array.from(
|
558 | new Set(
|
559 | Array.from(chunkGraph.getModuleChunksIterable(module)).map(
|
560 | chunk => chunk.name || "unnamed chunk(s)"
|
561 | )
|
562 | )
|
563 | ).sort();
|
564 | return `Module ${module.readableIdentifier(
|
565 | requestShortener
|
566 | )} is not in the same chunk(s) (expected in chunk(s) ${missingChunksList.join(
|
567 | ", "
|
568 | )}, module is in chunk(s) ${chunks.join(", ")})`;
|
569 | };
|
570 | statistics.incorrectChunks++;
|
571 | failureCache.set(module, problem); // cache failures for performance
|
572 | return problem;
|
573 | }
|
574 |
|
575 | const moduleGraph = compilation.moduleGraph;
|
576 |
|
577 | const incomingConnections =
|
578 | moduleGraph.getIncomingConnectionsByOriginModule(module);
|
579 |
|
580 | const incomingConnectionsFromNonModules =
|
581 | incomingConnections.get(null) || incomingConnections.get(undefined);
|
582 | if (incomingConnectionsFromNonModules) {
|
583 | const activeNonModulesConnections =
|
584 | incomingConnectionsFromNonModules.filter(connection => {
|
585 | // We are not interested in inactive connections
|
586 | // or connections without dependency
|
587 | return connection.isActive(runtime) || connection.dependency;
|
588 | });
|
589 | if (activeNonModulesConnections.length > 0) {
|
590 | const problem = requestShortener => {
|
591 | const importingExplanations = new Set(
|
592 | activeNonModulesConnections.map(c => c.explanation).filter(Boolean)
|
593 | );
|
594 | const explanations = Array.from(importingExplanations).sort();
|
595 | return `Module ${module.readableIdentifier(
|
596 | requestShortener
|
597 | )} is referenced ${
|
598 | explanations.length > 0
|
599 | ? `by: ${explanations.join(", ")}`
|
600 | : "in an unsupported way"
|
601 | }`;
|
602 | };
|
603 | statistics.incorrectDependency++;
|
604 | failureCache.set(module, problem); // cache failures for performance
|
605 | return problem;
|
606 | }
|
607 | }
|
608 |
|
609 | /** @type {Map<Module, readonly ModuleGraph.ModuleGraphConnection[]>} */
|
610 | const incomingConnectionsFromModules = new Map();
|
611 | for (const [originModule, connections] of incomingConnections) {
|
612 | if (originModule) {
|
613 | // Ignore connection from orphan modules
|
614 | if (chunkGraph.getNumberOfModuleChunks(originModule) === 0) continue;
|
615 |
|
616 | // We don't care for connections from other runtimes
|
617 | let originRuntime = undefined;
|
618 | for (const r of chunkGraph.getModuleRuntimes(originModule)) {
|
619 | originRuntime = mergeRuntimeOwned(originRuntime, r);
|
620 | }
|
621 |
|
622 | if (!intersectRuntime(runtime, originRuntime)) continue;
|
623 |
|
624 | // We are not interested in inactive connections
|
625 | const activeConnections = connections.filter(connection =>
|
626 | connection.isActive(runtime)
|
627 | );
|
628 | if (activeConnections.length > 0)
|
629 | incomingConnectionsFromModules.set(originModule, activeConnections);
|
630 | }
|
631 | }
|
632 |
|
633 | const incomingModules = Array.from(incomingConnectionsFromModules.keys());
|
634 |
|
635 | // Module must be in the same chunks like the referencing module
|
636 | const otherChunkModules = incomingModules.filter(originModule => {
|
637 | for (const chunk of chunkGraph.getModuleChunksIterable(
|
638 | config.rootModule
|
639 | )) {
|
640 | if (!chunkGraph.isModuleInChunk(originModule, chunk)) {
|
641 | return true;
|
642 | }
|
643 | }
|
644 | return false;
|
645 | });
|
646 | if (otherChunkModules.length > 0) {
|
647 | const problem = requestShortener => {
|
648 | const names = otherChunkModules
|
649 | .map(m => m.readableIdentifier(requestShortener))
|
650 | .sort();
|
651 | return `Module ${module.readableIdentifier(
|
652 | requestShortener
|
653 | )} is referenced from different chunks by these modules: ${names.join(
|
654 | ", "
|
655 | )}`;
|
656 | };
|
657 | statistics.incorrectChunksOfImporter++;
|
658 | failureCache.set(module, problem); // cache failures for performance
|
659 | return problem;
|
660 | }
|
661 |
|
662 | /** @type {Map<Module, readonly ModuleGraph.ModuleGraphConnection[]>} */
|
663 | const nonHarmonyConnections = new Map();
|
664 | for (const [originModule, connections] of incomingConnectionsFromModules) {
|
665 | const selected = connections.filter(
|
666 | connection =>
|
667 | !connection.dependency ||
|
668 | !(connection.dependency instanceof HarmonyImportDependency)
|
669 | );
|
670 | if (selected.length > 0)
|
671 | nonHarmonyConnections.set(originModule, connections);
|
672 | }
|
673 | if (nonHarmonyConnections.size > 0) {
|
674 | const problem = requestShortener => {
|
675 | const names = Array.from(nonHarmonyConnections)
|
676 | .map(([originModule, connections]) => {
|
677 | return `${originModule.readableIdentifier(
|
678 | requestShortener
|
679 | )} (referenced with ${Array.from(
|
680 | new Set(
|
681 | connections
|
682 | .map(c => c.dependency && c.dependency.type)
|
683 | .filter(Boolean)
|
684 | )
|
685 | )
|
686 | .sort()
|
687 | .join(", ")})`;
|
688 | })
|
689 | .sort();
|
690 | return `Module ${module.readableIdentifier(
|
691 | requestShortener
|
692 | )} is referenced from these modules with unsupported syntax: ${names.join(
|
693 | ", "
|
694 | )}`;
|
695 | };
|
696 | statistics.incorrectModuleDependency++;
|
697 | failureCache.set(module, problem); // cache failures for performance
|
698 | return problem;
|
699 | }
|
700 |
|
701 | if (runtime !== undefined && typeof runtime !== "string") {
|
702 | // Module must be consistently referenced in the same runtimes
|
703 | /** @type {{ originModule: Module, runtimeCondition: RuntimeSpec }[]} */
|
704 | const otherRuntimeConnections = [];
|
705 | outer: for (const [
|
706 | originModule,
|
707 | connections
|
708 | ] of incomingConnectionsFromModules) {
|
709 | /** @type {false | RuntimeSpec} */
|
710 | let currentRuntimeCondition = false;
|
711 | for (const connection of connections) {
|
712 | const runtimeCondition = filterRuntime(runtime, runtime => {
|
713 | return connection.isTargetActive(runtime);
|
714 | });
|
715 | if (runtimeCondition === false) continue;
|
716 | if (runtimeCondition === true) continue outer;
|
717 | if (currentRuntimeCondition !== false) {
|
718 | currentRuntimeCondition = mergeRuntime(
|
719 | currentRuntimeCondition,
|
720 | runtimeCondition
|
721 | );
|
722 | } else {
|
723 | currentRuntimeCondition = runtimeCondition;
|
724 | }
|
725 | }
|
726 | if (currentRuntimeCondition !== false) {
|
727 | otherRuntimeConnections.push({
|
728 | originModule,
|
729 | runtimeCondition: currentRuntimeCondition
|
730 | });
|
731 | }
|
732 | }
|
733 | if (otherRuntimeConnections.length > 0) {
|
734 | const problem = requestShortener => {
|
735 | return `Module ${module.readableIdentifier(
|
736 | requestShortener
|
737 | )} is runtime-dependent referenced by these modules: ${Array.from(
|
738 | otherRuntimeConnections,
|
739 | ({ originModule, runtimeCondition }) =>
|
740 | `${originModule.readableIdentifier(
|
741 | requestShortener
|
742 | )} (expected runtime ${runtimeToString(
|
743 | runtime
|
744 | )}, module is only referenced in ${runtimeToString(
|
745 | /** @type {RuntimeSpec} */ (runtimeCondition)
|
746 | )})`
|
747 | ).join(", ")}`;
|
748 | };
|
749 | statistics.incorrectRuntimeCondition++;
|
750 | failureCache.set(module, problem); // cache failures for performance
|
751 | return problem;
|
752 | }
|
753 | }
|
754 |
|
755 | let backup;
|
756 | if (avoidMutateOnFailure) {
|
757 | backup = config.snapshot();
|
758 | }
|
759 |
|
760 | // Add the module
|
761 | config.add(module);
|
762 |
|
763 | incomingModules.sort(compareModulesByIdentifier);
|
764 |
|
765 | // Every module which depends on the added module must be in the configuration too.
|
766 | for (const originModule of incomingModules) {
|
767 | const problem = this._tryToAdd(
|
768 | compilation,
|
769 | config,
|
770 | originModule,
|
771 | runtime,
|
772 | activeRuntime,
|
773 | possibleModules,
|
774 | candidates,
|
775 | failureCache,
|
776 | chunkGraph,
|
777 | false,
|
778 | statistics
|
779 | );
|
780 | if (problem) {
|
781 | if (backup !== undefined) config.rollback(backup);
|
782 | statistics.importerFailed++;
|
783 | failureCache.set(module, problem); // cache failures for performance
|
784 | return problem;
|
785 | }
|
786 | }
|
787 |
|
788 | // Add imports to possible candidates list
|
789 | for (const imp of this._getImports(compilation, module, runtime)) {
|
790 | candidates.add(imp);
|
791 | }
|
792 | statistics.added++;
|
793 | return null;
|
794 | }
|
795 | }
|
796 |
|
797 | class ConcatConfiguration {
|
798 | /**
|
799 | * @param {Module} rootModule the root module
|
800 | * @param {RuntimeSpec} runtime the runtime
|
801 | */
|
802 | constructor(rootModule, runtime) {
|
803 | this.rootModule = rootModule;
|
804 | this.runtime = runtime;
|
805 | /** @type {Set<Module>} */
|
806 | this.modules = new Set();
|
807 | this.modules.add(rootModule);
|
808 | /** @type {Map<Module, Module | function(RequestShortener): string>} */
|
809 | this.warnings = new Map();
|
810 | }
|
811 |
|
812 | add(module) {
|
813 | this.modules.add(module);
|
814 | }
|
815 |
|
816 | has(module) {
|
817 | return this.modules.has(module);
|
818 | }
|
819 |
|
820 | isEmpty() {
|
821 | return this.modules.size === 1;
|
822 | }
|
823 |
|
824 | addWarning(module, problem) {
|
825 | this.warnings.set(module, problem);
|
826 | }
|
827 |
|
828 | getWarningsSorted() {
|
829 | return new Map(
|
830 | Array.from(this.warnings).sort((a, b) => {
|
831 | const ai = a[0].identifier();
|
832 | const bi = b[0].identifier();
|
833 | if (ai < bi) return -1;
|
834 | if (ai > bi) return 1;
|
835 | return 0;
|
836 | })
|
837 | );
|
838 | }
|
839 |
|
840 | /**
|
841 | * @returns {Set<Module>} modules as set
|
842 | */
|
843 | getModules() {
|
844 | return this.modules;
|
845 | }
|
846 |
|
847 | snapshot() {
|
848 | return this.modules.size;
|
849 | }
|
850 |
|
851 | rollback(snapshot) {
|
852 | const modules = this.modules;
|
853 | for (const m of modules) {
|
854 | if (snapshot === 0) {
|
855 | modules.delete(m);
|
856 | } else {
|
857 | snapshot--;
|
858 | }
|
859 | }
|
860 | }
|
861 | }
|
862 |
|
863 | module.exports = ModuleConcatenationPlugin;
|
864 |
|
\ | No newline at end of file |