ModuleConcatenationPlugin.js 29 KB

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