ModuleConcatenationPlugin.js 29 KB

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