ModuleConcatenationPlugin.js 29 KB

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