ModuleConcatenationPlugin.js 30 KB

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