fileSizeUtils.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import fs from 'fs';
  2. import path from 'path';
  3. import chalk from 'chalk';
  4. import {filesize} from 'filesize';
  5. import recursive from 'recursive-readdir';
  6. import stripAnsi from 'strip-ansi';
  7. import type {Stats} from 'webpack';
  8. function getDifferenceLabel(currentSize: number, previousSize: number) {
  9. const FIFTY_KILOBYTES = 1024 * 50;
  10. const difference = currentSize - previousSize;
  11. const fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
  12. if (difference >= FIFTY_KILOBYTES) {
  13. return chalk.red('+' + fileSize);
  14. } else if (difference < FIFTY_KILOBYTES && difference > 0) {
  15. return chalk.yellow('+' + fileSize);
  16. } else if (difference < 0) {
  17. return chalk.green(fileSize);
  18. }
  19. return '';
  20. }
  21. function canReadAsset(asset: string) {
  22. return /\.(js|css)$/.test(asset);
  23. }
  24. function removeFileNameHash(buildFolder: string, fileName: string) {
  25. return fileName
  26. .replace(buildFolder, '')
  27. .replace(/\\/g, '/')
  28. .replace(
  29. /\/?(.*)(\.[0-9a-f]+)(\.chunk)?(\.js|\.css)/,
  30. // eslint-disable-next-line max-params
  31. (_, p1, __, ___, p4) => p1 + p4,
  32. );
  33. }
  34. function getFileSize(path: string) {
  35. const stats = fs.statSync(path);
  36. return stats.size;
  37. }
  38. export function printFileSizesAfterBuild(options: {
  39. webpackStats: Stats;
  40. previousSizeMap: Awaited<ReturnType<typeof measureFileSizesBeforeBuild>>;
  41. buildFolder: string;
  42. maxSize: number;
  43. }) {
  44. const {webpackStats, previousSizeMap, buildFolder, maxSize} = options;
  45. const {root} = previousSizeMap;
  46. const {sizes} = previousSizeMap;
  47. const assetsList
  48. = webpackStats.toJson({
  49. all: false,
  50. assets: true,
  51. assetsSort: 'size',
  52. })?.assets ?? [];
  53. const assets = assetsList
  54. .filter(asset => canReadAsset(asset.name))
  55. .map(function(asset) {
  56. const size = getFileSize(path.join(root, asset.name));
  57. const previousSize = sizes[removeFileNameHash(root, asset.name)];
  58. const difference = getDifferenceLabel(size, previousSize);
  59. const isJs = path.extname(asset.name) === '.js';
  60. const pathPen = isJs ? chalk.hex('#48c0a3') : chalk.hex('#b0a4e3');
  61. const sizePen
  62. = size > maxSize ? chalk.hex('#ff2121').bold : chalk.hex('#0aa344');
  63. return {
  64. folder: path.join(path.basename(buildFolder), path.dirname(asset.name)),
  65. name: path.basename(asset.name),
  66. size,
  67. sizeLabel: filesize(size) + (difference ? ' (' + difference + ')' : ''),
  68. pathPen,
  69. sizePen,
  70. };
  71. });
  72. const longestSizeLabelLength = Math.max.apply(
  73. null,
  74. assets.map(a => stripAnsi(a.sizeLabel).length),
  75. );
  76. console.log();
  77. console.log('Packaging resource list:');
  78. console.log();
  79. let overstep = false;
  80. assets.forEach(function(asset) {
  81. const {sizeLabel, name, size, pathPen, sizePen, folder} = asset;
  82. let label = sizeLabel;
  83. const sizeLength = stripAnsi(sizeLabel).length;
  84. if (sizeLength < longestSizeLabelLength) {
  85. const rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength);
  86. label += rightPadding;
  87. }
  88. if (!overstep && size > maxSize && path.extname(name) === '.js') {
  89. overstep = true;
  90. }
  91. const filePath = folder + path.sep + name;
  92. console.log(sizePen(`Size: ${label}`), pathPen(filePath));
  93. });
  94. if (overstep) {
  95. const varColor = '#177cb0';
  96. const pathColor = '#f00056';
  97. console.log();
  98. console.log();
  99. console.log(
  100. chalk.grey(
  101. `Some chunks are larger ${filesize(
  102. maxSize,
  103. )} after compilation. Consider:`,
  104. ),
  105. );
  106. console.log();
  107. console.log(
  108. chalk.hex(pathColor)(
  109. '1.Using dynamic import() to code-split the application',
  110. ),
  111. );
  112. console.log(
  113. chalk.hex(pathColor)(
  114. '2.Adjust the prompt size by adjusting'
  115. + ` ${chalk.hex(varColor)('SWT_MAX_CHUNK_SIZE')}`
  116. + ` in ${chalk.white('packages/app/.env')}`,
  117. ),
  118. );
  119. console.log(
  120. chalk.hex(pathColor)(
  121. `3.Modify ${chalk.hex(varColor)('splitChunks')} in `
  122. + `${chalk.white('packages/webpack/config/optimization.js')}`,
  123. ),
  124. );
  125. }
  126. }
  127. export function measureFileSizesBeforeBuild(buildFolder: string) {
  128. return new Promise<{root: string; sizes: Record<string, number>}>(function(
  129. resolve,
  130. ) {
  131. recursive(buildFolder, function(err, fileNames) {
  132. let sizes;
  133. if (!err && fileNames) {
  134. sizes = fileNames
  135. .filter(canReadAsset)
  136. .reduce(function(memo: Record<string, number>, fileName) {
  137. const key = removeFileNameHash(buildFolder, fileName);
  138. memo[key] = getFileSize(fileName);
  139. return memo;
  140. }, {});
  141. }
  142. resolve({
  143. root: buildFolder,
  144. sizes: sizes || {},
  145. });
  146. });
  147. });
  148. }