UnsplitVendorsPlugin.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Alexander Akait @alexander-akait
  4. */
  5. "use strict";
  6. const UnsplitVendorsWarning = require("../errors/UnsplitVendorsWarning");
  7. const { compareStrings } = require("../util/comparators");
  8. const { NODE_MODULES_REGEXP } = require("../util/identifier");
  9. const getModuleSize = require("./getModuleSize");
  10. const getSourceModules = require("./getSourceModules");
  11. /** @import { PerformanceOptions } from "../../declarations/WebpackOptions" */
  12. /** @import Compiler from "../Compiler" */
  13. /**
  14. * @import {
  15. * UnsplitVendorChunkDetails
  16. * } from "../errors/UnsplitVendorsWarning"
  17. */
  18. const PLUGIN_NAME = "UnsplitVendorsPlugin";
  19. // Enough to name the offenders without printing the chunk graph.
  20. const MAX_REPORTED_CHUNKS = 5;
  21. class UnsplitVendorsPlugin {
  22. /**
  23. * Creates an instance of UnsplitVendorsPlugin.
  24. * @param {PerformanceOptions} options the plugin options
  25. */
  26. constructor(options) {
  27. /** @type {PerformanceOptions["hints"]} */
  28. this.hints = options.hints;
  29. }
  30. /**
  31. * Applies the plugin by registering its hooks on the compiler.
  32. * @param {Compiler} compiler the compiler instance
  33. * @returns {void}
  34. */
  35. apply(compiler) {
  36. const hints = this.hints;
  37. if (!hints) return;
  38. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
  39. compilation.hooks.afterSeal.tap(PLUGIN_NAME, () => {
  40. const { chunkGraph } = compilation;
  41. /** @type {UnsplitVendorChunkDetails[]} */
  42. const mixed = [];
  43. for (const chunk of compilation.chunks) {
  44. // Only an initial chunk is downloaded on every visit, so only there
  45. // does mixing cost a returning visitor anything.
  46. if (!chunk.canBeInitial()) continue;
  47. let vendorModules = 0;
  48. let vendorSize = 0;
  49. let applicationModules = 0;
  50. for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
  51. for (const inner of getSourceModules(module)) {
  52. const resource = inner.nameForCondition();
  53. // A module with no resource is webpack's own (a runtime module),
  54. // which belongs to neither side and must not force a report.
  55. if (!resource) continue;
  56. if (NODE_MODULES_REGEXP.test(resource)) {
  57. vendorModules++;
  58. vendorSize += getModuleSize(inner);
  59. } else {
  60. applicationModules++;
  61. }
  62. }
  63. }
  64. if (vendorModules === 0 || applicationModules === 0) continue;
  65. mixed.push({
  66. name: chunk.name || `${chunk.id}`,
  67. vendorModules,
  68. vendorSize
  69. });
  70. }
  71. if (mixed.length === 0) return;
  72. // Ties break by name: chunk iteration order is not stable.
  73. mixed.sort(
  74. (a, b) =>
  75. b.vendorSize - a.vendorSize || compareStrings(a.name, b.name)
  76. );
  77. const warning = new UnsplitVendorsWarning(
  78. mixed.slice(0, MAX_REPORTED_CHUNKS)
  79. );
  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 = UnsplitVendorsPlugin;