WarnCaseSensitiveModulesPlugin.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. /** @import Compiler from "./Compiler" */
  7. /** @import Module from "./Module" */
  8. /** @import ModuleGraph from "./ModuleGraph" */
  9. /** @import NormalModule from "./NormalModule" */
  10. const WebpackError = require("./errors/WebpackError");
  11. /**
  12. * Sorts the conflicting modules by identifier to keep warning output stable.
  13. * @param {Module[]} modules the modules to be sorted
  14. * @returns {Module[]} sorted version of original modules
  15. */
  16. const sortModules = (modules) =>
  17. modules.sort((a, b) => {
  18. const aIdent = a.identifier();
  19. const bIdent = b.identifier();
  20. /* istanbul ignore next */
  21. if (aIdent < bIdent) return -1;
  22. /* istanbul ignore next */
  23. if (aIdent > bIdent) return 1;
  24. /* istanbul ignore next */
  25. return 0;
  26. });
  27. /**
  28. * Formats the conflicting modules and one representative incoming reason for
  29. * each module into the warning body.
  30. * @param {Module[]} modules each module from throw
  31. * @param {ModuleGraph} moduleGraph the module graph
  32. * @returns {string} each message from provided modules
  33. */
  34. const createModulesListMessage = (modules, moduleGraph) =>
  35. modules
  36. .map((m) => {
  37. let message = `* ${m.identifier()}`;
  38. const validReasons = [
  39. ...moduleGraph.getIncomingConnectionsByOriginModule(m).keys()
  40. ].filter(Boolean);
  41. if (validReasons.length > 0) {
  42. message += `\n Used by ${validReasons.length} module(s), i. e.`;
  43. message += `\n ${
  44. /** @type {Module[]} */ (validReasons)[0].identifier()
  45. }`;
  46. }
  47. return message;
  48. })
  49. .join("\n");
  50. /**
  51. * Warning emitted when webpack finds modules whose identifiers differ only by
  52. * letter casing, which can behave inconsistently across filesystems.
  53. */
  54. class CaseSensitiveModulesWarning extends WebpackError {
  55. /**
  56. * Builds a warning message that lists the case-conflicting modules and
  57. * representative importers that caused them to be included.
  58. * @param {Iterable<Module>} modules modules that were detected
  59. * @param {ModuleGraph} moduleGraph the module graph
  60. */
  61. constructor(modules, moduleGraph) {
  62. const sortedModules = sortModules([...modules]);
  63. const modulesList = createModulesListMessage(sortedModules, moduleGraph);
  64. super(`There are multiple modules with names that only differ in casing.
  65. This can lead to unexpected behavior when compiling on a filesystem with other case-semantic.
  66. Use equal casing. Compare these module identifiers:
  67. ${modulesList}`);
  68. /** @type {string} */
  69. this.name = "CaseSensitiveModulesWarning";
  70. /** @type {Module} */
  71. this.module = sortedModules[0];
  72. }
  73. }
  74. const PLUGIN_NAME = "WarnCaseSensitiveModulesPlugin";
  75. class WarnCaseSensitiveModulesPlugin {
  76. /**
  77. * Applies the plugin by registering its hooks on the compiler.
  78. * @param {Compiler} compiler the compiler instance
  79. * @returns {void}
  80. */
  81. apply(compiler) {
  82. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
  83. compilation.hooks.seal.tap(PLUGIN_NAME, () => {
  84. /** @type {Map<string, Map<string, Module>>} */
  85. const moduleWithoutCase = new Map();
  86. for (const module of compilation.modules) {
  87. const identifier = module.identifier();
  88. // Ignore `data:` URLs, because it's not a real path
  89. if (
  90. /** @type {NormalModule} */
  91. (module).resourceResolveData !== undefined &&
  92. /** @type {NormalModule} */
  93. (module).resourceResolveData.encodedContent !== undefined
  94. ) {
  95. continue;
  96. }
  97. const lowerIdentifier = identifier.toLowerCase();
  98. let map = moduleWithoutCase.get(lowerIdentifier);
  99. if (map === undefined) {
  100. map = new Map();
  101. moduleWithoutCase.set(lowerIdentifier, map);
  102. }
  103. map.set(identifier, module);
  104. }
  105. for (const pair of moduleWithoutCase) {
  106. const map = pair[1];
  107. if (map.size > 1) {
  108. compilation.warnings.push(
  109. new CaseSensitiveModulesWarning(
  110. map.values(),
  111. compilation.moduleGraph
  112. )
  113. );
  114. }
  115. }
  116. });
  117. });
  118. }
  119. }
  120. module.exports = WarnCaseSensitiveModulesPlugin;