SizeLimitsPlugin.js 4.7 KB

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