ModuleConcatenationPlugin.js 29 KB

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