SizeLimitsPlugin.js 4.7 KB

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