UnusedExternalsPlugin.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Alexander Akait @alexander-akait
  4. */
  5. "use strict";
  6. const ExternalModule = require("../ExternalModule");
  7. const {
  8. getAlternateCoreModuleRequest
  9. } = require("../ExternalModuleFactoryPlugin");
  10. const UnusedExternalsWarning = require("../errors/UnusedExternalsWarning");
  11. /**
  12. * @import {
  13. * Externals,
  14. * PerformanceOptions
  15. * } from "../../declarations/WebpackOptions"
  16. */
  17. /** @import Compiler from "../Compiler" */
  18. const PLUGIN_NAME = "UnusedExternalsPlugin";
  19. /**
  20. * Collects the requests the configuration names outright. A RegExp or a function
  21. * decides per request, so there is no entry that could be called unused.
  22. * @param {Externals} externals the configured externals
  23. * @param {Set<string>} requests collected requests
  24. * @returns {void}
  25. */
  26. const collectRequests = (externals, requests) => {
  27. if (typeof externals === "string") {
  28. requests.add(externals);
  29. } else if (Array.isArray(externals)) {
  30. for (const entry of externals) collectRequests(entry, requests);
  31. } else if (externals instanceof RegExp || typeof externals === "function") {
  32. // Decides per request, so it names nothing that could be called unused.
  33. } else if (externals) {
  34. for (const request of Object.keys(externals)) requests.add(request);
  35. }
  36. };
  37. class UnusedExternalsPlugin {
  38. /**
  39. * Creates an instance of UnusedExternalsPlugin.
  40. * @param {PerformanceOptions} options the plugin options
  41. */
  42. constructor(options) {
  43. /** @type {PerformanceOptions["hints"]} */
  44. this.hints = options.hints;
  45. }
  46. /**
  47. * Applies the plugin by registering its hooks on the compiler.
  48. * @param {Compiler} compiler the compiler instance
  49. * @returns {void}
  50. */
  51. apply(compiler) {
  52. const hints = this.hints;
  53. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
  54. compilation.hooks.afterSeal.tap(PLUGIN_NAME, () => {
  55. const { externals } = compilation.options;
  56. if (externals === undefined) return;
  57. /** @type {Set<string>} */
  58. const declared = new Set();
  59. collectRequests(externals, declared);
  60. if (declared.size === 0) return;
  61. // Nothing was factorized, so nothing could be made external.
  62. if (compilation.modules.size === 0) return;
  63. for (const module of compilation.modules) {
  64. if (module instanceof ExternalModule) {
  65. // The request as written, not what it was rewritten to.
  66. declared.delete(module.userRequest);
  67. // A core module is external under either spelling, so the entry
  68. // that matched may be the one the request does not use.
  69. const alternateRequest = getAlternateCoreModuleRequest(
  70. module.userRequest
  71. );
  72. if (alternateRequest !== undefined) {
  73. declared.delete(alternateRequest);
  74. }
  75. }
  76. }
  77. if (declared.size === 0) return;
  78. const unused = [...declared].sort();
  79. const warning = new UnusedExternalsWarning(unused);
  80. if (hints === "error") {
  81. compilation.errors.push(warning);
  82. } else if (hints === "stats") {
  83. compilation.hints.push(warning);
  84. } else {
  85. compilation.warnings.push(warning);
  86. }
  87. });
  88. });
  89. }
  90. }
  91. module.exports = UnusedExternalsPlugin;