SizeLimitsPlugin.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Sean Larkin @thelarkinn
  4. */
  5. "use strict";
  6. const { find } = require("../util/SetHelpers");
  7. const AssetsOverSizeLimitWarning = require("./AssetsOverSizeLimitWarning");
  8. const EntrypointsOverSizeLimitWarning = require("./EntrypointsOverSizeLimitWarning");
  9. const NoAsyncChunksWarning = require("./NoAsyncChunksWarning");
  10. /** @typedef {import("webpack-sources").Source} Source */
  11. /** @typedef {import("../../declarations/WebpackOptions").PerformanceOptions} PerformanceOptions */
  12. /** @typedef {import("../ChunkGroup")} ChunkGroup */
  13. /** @typedef {import("../Compilation").Asset} Asset */
  14. /** @typedef {import("../Compiler")} Compiler */
  15. /** @typedef {import("../Entrypoint")} Entrypoint */
  16. /** @typedef {import("../WebpackError")} WebpackError */
  17. /**
  18. * @typedef {object} AssetDetails
  19. * @property {string} name
  20. * @property {number} size
  21. */
  22. /**
  23. * @typedef {object} EntrypointDetails
  24. * @property {string} name
  25. * @property {number} size
  26. * @property {string[]} files
  27. */
  28. /** @type {WeakSet<Entrypoint | ChunkGroup | Source>} */
  29. const isOverSizeLimitSet = new WeakSet();
  30. /**
  31. * @param {Asset["name"]} name the name
  32. * @param {Asset["source"]} source the source
  33. * @param {Asset["info"]} info the info
  34. * @returns {boolean} result
  35. */
  36. const excludeSourceMap = (name, source, info) => !info.development;
  37. const PLUGIN_NAME = "SizeLimitsPlugin";
  38. module.exports = class SizeLimitsPlugin {
  39. /**
  40. * @param {PerformanceOptions} options the plugin options
  41. */
  42. constructor(options) {
  43. this.hints = options.hints;
  44. this.maxAssetSize = options.maxAssetSize;
  45. this.maxEntrypointSize = options.maxEntrypointSize;
  46. this.assetFilter = options.assetFilter;
  47. }
  48. /**
  49. * @param {Entrypoint | ChunkGroup | Source} thing the resource to test
  50. * @returns {boolean} true if over the limit
  51. */
  52. static isOverSizeLimit(thing) {
  53. return isOverSizeLimitSet.has(thing);
  54. }
  55. /**
  56. * Apply the plugin
  57. * @param {Compiler} compiler the compiler instance
  58. * @returns {void}
  59. */
  60. apply(compiler) {
  61. const entrypointSizeLimit = this.maxEntrypointSize;
  62. const assetSizeLimit = this.maxAssetSize;
  63. const hints = this.hints;
  64. const assetFilter = this.assetFilter || excludeSourceMap;
  65. compiler.hooks.afterEmit.tap(PLUGIN_NAME, (compilation) => {
  66. /** @type {WebpackError[]} */
  67. const warnings = [];
  68. /**
  69. * @param {Entrypoint} entrypoint an entrypoint
  70. * @returns {number} the size of the entrypoint
  71. */
  72. const getEntrypointSize = (entrypoint) => {
  73. let size = 0;
  74. for (const file of entrypoint.getFiles()) {
  75. const asset = compilation.getAsset(file);
  76. if (
  77. asset &&
  78. assetFilter(asset.name, asset.source, asset.info) &&
  79. asset.source
  80. ) {
  81. size += asset.info.size || asset.source.size();
  82. }
  83. }
  84. return size;
  85. };
  86. /** @type {AssetDetails[]} */
  87. const assetsOverSizeLimit = [];
  88. for (const { name, source, info } of compilation.getAssets()) {
  89. if (!assetFilter(name, source, info) || !source) {
  90. continue;
  91. }
  92. const size = info.size || source.size();
  93. if (size > /** @type {number} */ (assetSizeLimit)) {
  94. assetsOverSizeLimit.push({
  95. name,
  96. size
  97. });
  98. isOverSizeLimitSet.add(source);
  99. }
  100. }
  101. /**
  102. * @param {Asset["name"]} name the name
  103. * @returns {boolean | undefined} result
  104. */
  105. const fileFilter = (name) => {
  106. const asset = compilation.getAsset(name);
  107. return asset && assetFilter(asset.name, asset.source, asset.info);
  108. };
  109. /** @type {EntrypointDetails[]} */
  110. const entrypointsOverLimit = [];
  111. for (const [name, entry] of compilation.entrypoints) {
  112. const size = getEntrypointSize(entry);
  113. if (size > /** @type {number} */ (entrypointSizeLimit)) {
  114. entrypointsOverLimit.push({
  115. name,
  116. size,
  117. files: entry.getFiles().filter(fileFilter)
  118. });
  119. isOverSizeLimitSet.add(entry);
  120. }
  121. }
  122. if (hints) {
  123. // 1. Individual Chunk: Size < 250kb
  124. // 2. Collective Initial Chunks [entrypoint] (Each Set?): Size < 250kb
  125. // 3. No Async Chunks
  126. // if !1, then 2, if !2 return
  127. if (assetsOverSizeLimit.length > 0) {
  128. warnings.push(
  129. new AssetsOverSizeLimitWarning(
  130. assetsOverSizeLimit,
  131. /** @type {number} */ (assetSizeLimit)
  132. )
  133. );
  134. }
  135. if (entrypointsOverLimit.length > 0) {
  136. warnings.push(
  137. new EntrypointsOverSizeLimitWarning(
  138. entrypointsOverLimit,
  139. /** @type {number} */ (entrypointSizeLimit)
  140. )
  141. );
  142. }
  143. if (warnings.length > 0) {
  144. const someAsyncChunk = find(
  145. compilation.chunks,
  146. (chunk) => !chunk.canBeInitial()
  147. );
  148. if (!someAsyncChunk) {
  149. warnings.push(new NoAsyncChunksWarning());
  150. }
  151. if (hints === "error") {
  152. compilation.errors.push(...warnings);
  153. } else {
  154. compilation.warnings.push(...warnings);
  155. }
  156. }
  157. }
  158. });
  159. }
  160. };