DuplicatePackagesPlugin.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Alexander Akait @alexander-akait
  4. */
  5. "use strict";
  6. const DuplicatePackagesWarning = require("./DuplicatePackagesWarning");
  7. /** @import { PerformanceOptions } from "../../declarations/WebpackOptions" */
  8. /** @import Compiler from "../Compiler" */
  9. /** @import Module from "../Module" */
  10. /** @import NormalModule from "../NormalModule" */
  11. /**
  12. * One copy of a package, identified by the directory its description file
  13. * (usually `package.json`) lives in.
  14. * @typedef {object} PackageCopy
  15. * @property {string} version version of this copy
  16. * @property {string} path directory of this copy
  17. * @property {Module[]} modules modules included from this copy
  18. */
  19. const PLUGIN_NAME = "DuplicatePackagesPlugin";
  20. class DuplicatePackagesPlugin {
  21. /**
  22. * Creates an instance of DuplicatePackagesPlugin.
  23. * @param {PerformanceOptions} options the plugin options
  24. */
  25. constructor(options) {
  26. /** @type {PerformanceOptions["hints"]} */
  27. this.hints = options.hints;
  28. }
  29. /**
  30. * Applies the plugin by registering its hooks on the compiler.
  31. * @param {Compiler} compiler the compiler instance
  32. * @returns {void}
  33. */
  34. apply(compiler) {
  35. const hints = this.hints;
  36. if (!hints) return;
  37. // `thisCompilation` keeps the hint to the root build, as child
  38. // compilations report the copies of their parent a second time
  39. compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
  40. /** @type {DuplicatePackagesWarning[]} */
  41. const warnings = [];
  42. compilation.hooks.seal.tap(PLUGIN_NAME, () => {
  43. /** @type {Map<string, Map<string, PackageCopy>>} */
  44. const packages = new Map();
  45. for (const module of compilation.modules) {
  46. const resolveData =
  47. /** @type {NormalModule} */
  48. (module).resourceResolveData;
  49. if (resolveData === undefined) continue;
  50. const { descriptionFileData, descriptionFileRoot } = resolveData;
  51. if (descriptionFileData === undefined) continue;
  52. const { name, version } = descriptionFileData;
  53. // Only a named and versioned package can be compared with another copy
  54. if (
  55. typeof name !== "string" ||
  56. typeof version !== "string" ||
  57. descriptionFileRoot === undefined
  58. ) {
  59. continue;
  60. }
  61. let copies = packages.get(name);
  62. if (copies === undefined) {
  63. copies = new Map();
  64. packages.set(name, copies);
  65. }
  66. const copy = copies.get(descriptionFileRoot);
  67. if (copy === undefined) {
  68. copies.set(descriptionFileRoot, {
  69. version,
  70. path: descriptionFileRoot,
  71. modules: [module]
  72. });
  73. } else {
  74. copy.modules.push(module);
  75. }
  76. }
  77. for (const name of [...packages.keys()].sort()) {
  78. const copies = /** @type {Map<string, PackageCopy>} */ (
  79. packages.get(name)
  80. );
  81. if (copies.size < 2) continue;
  82. const warning = new DuplicatePackagesWarning(
  83. name,
  84. [...copies.values()].sort((a, b) => (a.path < b.path ? -1 : 1)),
  85. compilation.moduleGraph,
  86. compilation.requestShortener
  87. );
  88. warnings.push(warning);
  89. }
  90. });
  91. // Reported past the hash: `createHash` folds every message into it, so
  92. // a hint pushed earlier would change the build's identity.
  93. compilation.hooks.afterSeal.tap(PLUGIN_NAME, () => {
  94. for (const warning of warnings) {
  95. if (hints === "error") {
  96. compilation.errors.push(warning);
  97. } else if (hints === "stats") {
  98. compilation.hints.push(warning);
  99. } else {
  100. compilation.warnings.push(warning);
  101. }
  102. }
  103. });
  104. });
  105. }
  106. }
  107. module.exports = DuplicatePackagesPlugin;