SizeLimitsPlugin.js 5.2 KB

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