FileSizeReporter.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. /**
  2. * Copyright (c) 2015-present, Facebook, Inc.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. 'use strict';
  8. var fs = require('fs');
  9. var path = require('path');
  10. var chalk = require('chalk');
  11. var filesize = require('filesize');
  12. var recursive = require('recursive-readdir');
  13. var stripAnsi = require('strip-ansi');
  14. var gzipSize = require('gzip-size').sync;
  15. function canReadAsset(asset) {
  16. return (
  17. /\.(js|css)$/.test(asset) &&
  18. !/service-worker\.js/.test(asset) &&
  19. !/precache-manifest\.[0-9a-f]+\.js/.test(asset)
  20. );
  21. }
  22. // Prints a detailed summary of build files.
  23. function printFileSizesAfterBuild(
  24. webpackStats,
  25. previousSizeMap,
  26. buildFolder,
  27. maxBundleGzipSize,
  28. maxChunkGzipSize
  29. ) {
  30. var root = previousSizeMap.root;
  31. var sizes = previousSizeMap.sizes;
  32. var assets = (webpackStats.stats || [webpackStats])
  33. .map(stats =>
  34. stats
  35. .toJson({ all: false, assets: true })
  36. .assets.filter(asset => canReadAsset(asset.name))
  37. .map(asset => {
  38. var fileContents = fs.readFileSync(path.join(root, asset.name));
  39. var size = gzipSize(fileContents);
  40. var previousSize = sizes[removeFileNameHash(root, asset.name)];
  41. var difference = getDifferenceLabel(size, previousSize);
  42. return {
  43. folder: path.join(
  44. path.basename(buildFolder),
  45. path.dirname(asset.name)
  46. ),
  47. name: path.basename(asset.name),
  48. size: size,
  49. sizeLabel:
  50. filesize(size) + (difference ? ' (' + difference + ')' : ''),
  51. };
  52. })
  53. )
  54. .reduce((single, all) => all.concat(single), []);
  55. assets.sort((a, b) => b.size - a.size);
  56. var longestSizeLabelLength = Math.max.apply(
  57. null,
  58. assets.map(a => stripAnsi(a.sizeLabel).length)
  59. );
  60. var suggestBundleSplitting = false;
  61. assets.forEach(asset => {
  62. var sizeLabel = asset.sizeLabel;
  63. var sizeLength = stripAnsi(sizeLabel).length;
  64. if (sizeLength < longestSizeLabelLength) {
  65. var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength);
  66. sizeLabel += rightPadding;
  67. }
  68. var isMainBundle = asset.name.indexOf('main.') === 0;
  69. var maxRecommendedSize = isMainBundle
  70. ? maxBundleGzipSize
  71. : maxChunkGzipSize;
  72. var isLarge = maxRecommendedSize && asset.size > maxRecommendedSize;
  73. if (isLarge && path.extname(asset.name) === '.js') {
  74. suggestBundleSplitting = true;
  75. }
  76. console.log(
  77. ' ' +
  78. (isLarge ? chalk.yellow(sizeLabel) : sizeLabel) +
  79. ' ' +
  80. chalk.dim(asset.folder + path.sep) +
  81. chalk.cyan(asset.name)
  82. );
  83. });
  84. if (suggestBundleSplitting) {
  85. console.log();
  86. console.log(
  87. chalk.yellow('The bundle size is significantly larger than recommended.')
  88. );
  89. console.log(
  90. chalk.yellow(
  91. 'Consider reducing it with code splitting: https://goo.gl/9VhYWB'
  92. )
  93. );
  94. console.log(
  95. chalk.yellow(
  96. 'You can also analyze the project dependencies: https://goo.gl/LeUzfb'
  97. )
  98. );
  99. }
  100. }
  101. function removeFileNameHash(buildFolder, fileName) {
  102. return fileName
  103. .replace(buildFolder, '')
  104. .replace(/\\/g, '/')
  105. .replace(
  106. /\/?(.*)(\.[0-9a-f]+)(\.chunk)?(\.js|\.css)/,
  107. (match, p1, p2, p3, p4) => p1 + p4
  108. );
  109. }
  110. // Input: 1024, 2048
  111. // Output: "(+1 KB)"
  112. function getDifferenceLabel(currentSize, previousSize) {
  113. var FIFTY_KILOBYTES = 1024 * 50;
  114. var difference = currentSize - previousSize;
  115. var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
  116. if (difference >= FIFTY_KILOBYTES) {
  117. return chalk.red('+' + fileSize);
  118. } else if (difference < FIFTY_KILOBYTES && difference > 0) {
  119. return chalk.yellow('+' + fileSize);
  120. } else if (difference < 0) {
  121. return chalk.green(fileSize);
  122. } else {
  123. return '';
  124. }
  125. }
  126. function measureFileSizesBeforeBuild(buildFolder) {
  127. return new Promise(resolve => {
  128. recursive(buildFolder, (err, fileNames) => {
  129. var sizes;
  130. if (!err && fileNames) {
  131. sizes = fileNames.filter(canReadAsset).reduce((memo, fileName) => {
  132. var contents = fs.readFileSync(fileName);
  133. var key = removeFileNameHash(buildFolder, fileName);
  134. memo[key] = gzipSize(contents);
  135. return memo;
  136. }, {});
  137. }
  138. resolve({
  139. root: buildFolder,
  140. sizes: sizes || {},
  141. });
  142. });
  143. });
  144. }
  145. module.exports = {
  146. measureFileSizesBeforeBuild: measureFileSizesBeforeBuild,
  147. printFileSizesAfterBuild: printFileSizesAfterBuild,
  148. };