From cfc4325e4c562fec974742fcb1afe9e930a2f026 Mon Sep 17 00:00:00 2001
From: Elijah Manor <elijah.manor@gmail.com>
Date: Wed, 3 Aug 2016 01:01:58 -0500
Subject: [PATCH] Display Build Size Difference

---
 package.json     |   1 +
 scripts/build.js | 164 +++++++++++++++++++++++++++++------------------
 2 files changed, 101 insertions(+), 64 deletions(-)

diff --git a/package.json b/package.json
index 15eaec523..e80008d84 100644
--- a/package.json
+++ b/package.json
@@ -66,6 +66,7 @@
     "opn": "4.0.2",
     "postcss-loader": "0.9.1",
     "promise": "7.1.1",
+    "recursive-readdir": "^2.0.0",
     "rimraf": "2.5.4",
     "style-loader": "0.13.1",
     "url-loader": "0.5.7",
diff --git a/scripts/build.js b/scripts/build.js
index e57e040bd..11421deaa 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -18,79 +18,115 @@ var rimrafSync = require('rimraf').sync;
 var webpack = require('webpack');
 var config = require('../config/webpack.config.prod');
 var paths = require('../config/paths');
+var recursive = require('recursive-readdir');
 
-// Remove all content but keep the directory so that
-// if you're in it, you don't end up in Trash
-rimrafSync(paths.appBuild + '/*');
+function removeFileNameHash(fileName) {
+  return fileName.replace(paths.appBuild, '')
+    .replace(/\/?(.*)(\.\w+)(\.js|\.css)/, function(match, p1, p2, p3) {
+      return p1 + p3;
+    });
+}
 
-console.log('Creating an optimized production build...');
-webpack(config).run(function(err, stats) {
-  if (err) {
-    console.error('Failed to create a production build. Reason:');
-    console.error(err.message || err);
-    process.exit(1);
+function sizeDifference(currentSize, previousSize) {
+  if (previousSize === undefined) { return ''; }
+  var difference = currentSize - previousSize;
+  var fileSize = filesize(difference);
+  if (difference > 0) {
+    return chalk.red('+' + fileSize);
+  } else if (difference <= 0){
+    return chalk.green((difference === 0 ? '+' : '') + fileSize);
   }
+}
 
-  console.log(chalk.green('Compiled successfully.'));
-  console.log();
+recursive(paths.appBuild, function (err, fileNames) {
+  fileNames = fileNames || [];
+  var previousSizeMap = fileNames.filter(fileName => /\.(js|css)$/.test(fileName))
+    .reduce((memo, fileName) => {
+      var contents = fs.readFileSync(fileName);
+      var key = removeFileNameHash(fileName);
+      memo[key] = gzipSize(contents);
+      return memo;
+    }, {} );
 
-  console.log('File sizes after gzip:');
-  console.log();
-  var assets = stats.toJson().assets
-    .filter(asset => /\.(js|css)$/.test(asset.name))
-    .map(asset => {
-      var fileContents = fs.readFileSync(paths.appBuild + '/' + asset.name);
-      var size = gzipSize(fileContents);
-      return {
-        folder: path.join('build', path.dirname(asset.name)),
-        name: path.basename(asset.name),
-        size: size,
-        sizeLabel: filesize(size)
-      };
-    });
-  assets.sort((a, b) => b.size - a.size);
+  // Remove all content but keep the directory so that
+  // if you're in it, you don't end up in Trash
+  rimrafSync(paths.appBuild + '/*');
+
+  build(previousSizeMap);
+});
 
-  var longestSizeLabelLength = Math.max.apply(null,
-    assets.map(a => a.sizeLabel.length)
-  );
-  assets.forEach(asset => {
-    var sizeLabel = asset.sizeLabel;
-    if (sizeLabel.length < longestSizeLabelLength) {
-      var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLabel.length);
-      sizeLabel += rightPadding;
+function build(previousSizeMap) {
+  console.log('Creating an optimized production build...');
+  webpack(config).run(function(err, stats) {
+    if (err) {
+      console.error('Failed to create a production build. Reason:');
+      console.error(err.message || err);
+      process.exit(1);
     }
-    console.log(
-      '  ' + chalk.green(sizeLabel) +
-      '  ' + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name)
-    );
-  });
-  console.log();
 
-  var openCommand = process.platform === 'win32' ? 'start' : 'open';
-  var homepagePath = require(paths.appPackageJson).homepage;
-  if (homepagePath) {
-    console.log('You can now publish them at ' + homepagePath + '.');
-    console.log('For example, if you use GitHub Pages:');
+    console.log(chalk.green('Compiled successfully.'));
     console.log();
-    console.log('  git commit -am "Save local changes"');
-    console.log('  git checkout -B gh-pages');
-    console.log('  git add -f build');
-    console.log('  git commit -am "Rebuild website"');
-    console.log('  git filter-branch -f --prune-empty --subdirectory-filter build');
-    console.log('  git push -f origin gh-pages');
-    console.log('  git checkout -');
+
+    console.log('File sizes after gzip:');
     console.log();
-  } else {
-    console.log('You can now serve them with any static server.');
-    console.log('For example:');
+    var assets = stats.toJson().assets
+      .filter(asset => /\.(js|css)$/.test(asset.name))
+      .map(asset => {
+        var fileContents = fs.readFileSync(paths.appBuild + '/' + asset.name);
+        var size = gzipSize(fileContents);
+        var previousSize = previousSizeMap[removeFileNameHash(asset.name)];
+        var difference = sizeDifference(size, previousSize);
+        return {
+          folder: path.join('build', path.dirname(asset.name)),
+          name: path.basename(asset.name),
+          size: size,
+          sizeLabel: filesize(size) + (difference ? ' (' + difference + ')' : '')
+        };
+      });
+    assets.sort((a, b) => b.size - a.size);
+
+    var longestSizeLabelLength = Math.max.apply(null,
+      assets.map(a => a.sizeLabel.length)
+    );
+    assets.forEach(asset => {
+      var sizeLabel = asset.sizeLabel;
+      if (sizeLabel.length < longestSizeLabelLength) {
+        var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLabel.length);
+        sizeLabel += rightPadding;
+      }
+      console.log(
+        '  ' + chalk.yellow(sizeLabel) +
+        '  ' + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name)
+      );
+    });
     console.log();
-    console.log('  npm install -g pushstate-server');
-    console.log('  pushstate-server build');
-    console.log('  ' + openCommand + ' http://localhost:9000');
+
+    var openCommand = process.platform === 'win32' ? 'start' : 'open';
+    var homepagePath = require(paths.appPackageJson).homepage;
+    if (homepagePath) {
+      console.log('You can now publish them at ' + homepagePath + '.');
+      console.log('For example, if you use GitHub Pages:');
+      console.log();
+      console.log('  git commit -am "Save local changes"');
+      console.log('  git checkout -B gh-pages');
+      console.log('  git add -f build');
+      console.log('  git commit -am "Rebuild website"');
+      console.log('  git filter-branch -f --prune-empty --subdirectory-filter build');
+      console.log('  git push -f origin gh-pages');
+      console.log('  git checkout -');
+      console.log();
+    } else {
+      console.log('You can now serve them with any static server.');
+      console.log('For example:');
+      console.log();
+      console.log('  npm install -g pushstate-server');
+      console.log('  pushstate-server build');
+      console.log('  ' + openCommand + ' http://localhost:9000');
+      console.log();
+      console.log(chalk.dim('The project was built assuming it is hosted at the root.'));
+      console.log(chalk.dim('Set the "homepage" field in package.json to override this.'));
+      console.log(chalk.dim('For example, "homepage": "http://user.github.io/project".'));
+    }
     console.log();
-    console.log(chalk.dim('The project was built assuming it is hosted at the root.'));
-    console.log(chalk.dim('Set the "homepage" field in package.json to override this.'));
-    console.log(chalk.dim('For example, "homepage": "http://user.github.io/project".'));
-  }
-  console.log();
-});
+  });
+}
-- 
GitLab