diff --git a/packages/react-dev-utils/FileSizeReporter.js b/packages/react-dev-utils/FileSizeReporter.js
new file mode 100644
index 0000000000000000000000000000000000000000..08273673f92bd0f97e3390d812dbb1c53dc2681e
--- /dev/null
+++ b/packages/react-dev-utils/FileSizeReporter.js
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+'use strict';
+
+var fs = require('fs');
+var path = require('path');
+var chalk = require('chalk');
+var filesize = require('filesize');
+var recursive = require('recursive-readdir');
+var stripAnsi = require('strip-ansi');
+var gzipSize = require('gzip-size').sync;
+
+// Prints a detailed summary of build files.
+function printFileSizesAfterBuild(webpackStats, previousSizeMap) {
+  var root = previousSizeMap.root;
+  var sizes = previousSizeMap.sizes;
+  var assets = webpackStats
+    .toJson()
+    .assets.filter(asset => /\.(js|css)$/.test(asset.name))
+    .map(asset => {
+      var fileContents = fs.readFileSync(path.join(root, asset.name));
+      var size = gzipSize(fileContents);
+      var previousSize = sizes[removeFileNameHash(root, asset.name)];
+      var difference = getDifferenceLabel(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 => stripAnsi(a.sizeLabel).length)
+  );
+  assets.forEach(asset => {
+    var sizeLabel = asset.sizeLabel;
+    var sizeLength = stripAnsi(sizeLabel).length;
+    if (sizeLength < longestSizeLabelLength) {
+      var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength);
+      sizeLabel += rightPadding;
+    }
+    console.log(
+      '  ' +
+        sizeLabel +
+        '  ' +
+        chalk.dim(asset.folder + path.sep) +
+        chalk.cyan(asset.name)
+    );
+  });
+}
+
+function removeFileNameHash(buildFolder, fileName) {
+  return fileName
+    .replace(buildFolder, '')
+    .replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3);
+}
+
+// Input: 1024, 2048
+// Output: "(+1 KB)"
+function getDifferenceLabel(currentSize, previousSize) {
+  var FIFTY_KILOBYTES = 1024 * 50;
+  var difference = currentSize - previousSize;
+  var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
+  if (difference >= FIFTY_KILOBYTES) {
+    return chalk.red('+' + fileSize);
+  } else if (difference < FIFTY_KILOBYTES && difference > 0) {
+    return chalk.yellow('+' + fileSize);
+  } else if (difference < 0) {
+    return chalk.green(fileSize);
+  } else {
+    return '';
+  }
+}
+
+function measureFileSizesBeforeBuild(buildFolder) {
+  return new Promise(resolve => {
+    recursive(buildFolder, (err, fileNames) => {
+      var sizes;
+      if (!err && fileNames) {
+        sizes = fileNames
+          .filter(fileName => /\.(js|css)$/.test(fileName))
+          .reduce((memo, fileName) => {
+            var contents = fs.readFileSync(fileName);
+            var key = removeFileNameHash(buildFolder, fileName);
+            memo[key] = gzipSize(contents);
+            return memo;
+          }, {});
+      }
+      resolve({
+        root: buildFolder,
+        sizes: sizes || {},
+      });
+    });
+  });
+}
+
+module.exports = {
+  measureFileSizesBeforeBuild: measureFileSizesBeforeBuild,
+  printFileSizesAfterBuild: printFileSizesAfterBuild,
+};
diff --git a/packages/react-dev-utils/README.md b/packages/react-dev-utils/README.md
index 2d430e2a2995c1c1380b8cae9a2c55e2c77f1fb8..26d762c35974016548fb59b32e412f9777875adf 100644
--- a/packages/react-dev-utils/README.md
+++ b/packages/react-dev-utils/README.md
@@ -110,6 +110,29 @@ clearConsole();
 console.log('Just cleared the screen!');
 ```
 
+#### `FileSizeReporter`
+
+##### `measureFileSizesBeforeBuild(buildFolder: string): Promise<OpaqueFileSizes>`
+
+Captures JS and CSS asset sizes inside the passed `buildFolder`. Save the result value to compare it after the build.
+
+##### `printFileSizesAfterBuild(webpackStats: WebpackStats, previousFileSizes: OpaqueFileSizes)`
+
+Prints the JS and CSS asset sizes after the build, and includes a size comparison with `previousFileSizes` that were captured earlier using `measureFileSizesBeforeBuild()`.
+
+```js
+var {
+  measureFileSizesBeforeBuild,
+  printFileSizesAfterBuild,
+} = require('react-dev-utils/FileSizeReporter');
+
+measureFileSizesBeforeBuild(buildFolder).then(previousFileSizes => {
+  return cleanAndRebuild().then(webpackStats => {
+    printFileSizesAfterBuild(webpackStats, previousFileSizes);
+  });
+});
+```
+
 #### `formatWebpackMessages({errors: Array<string>, warnings: Array<string>}): {errors: Array<string>, warnings: Array<string>}`
 
 Extracts and prettifies warning and error messages from webpack [stats](https://github.com/webpack/docs/wiki/node.js-api#stats) object.
diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json
index aae3b83ff016c90ecf232ee125850434a28efeee..b983d80b00858e4aee1aa571be315a800eed7774 100644
--- a/packages/react-dev-utils/package.json
+++ b/packages/react-dev-utils/package.json
@@ -11,13 +11,14 @@
     "node": ">=4"
   },
   "files": [
-    "clearConsole.js",
     "checkRequiredFiles.js",
+    "clearConsole.js",
+    "FileSizeReporter.js",
     "formatWebpackMessages.js",
     "getProcessForPort.js",
     "InterpolateHtmlPlugin.js",
-    "openChrome.applescript",
     "openBrowser.js",
+    "openChrome.applescript",
     "prompt.js",
     "WatchMissingNodeModulesPlugin.js",
     "webpackHotDevClient.js"
@@ -26,8 +27,11 @@
     "ansi-html": "0.0.5",
     "chalk": "1.1.3",
     "escape-string-regexp": "1.0.5",
+    "filesize": "3.3.0",
+    "gzip-size": "3.0.0",
     "html-entities": "1.2.0",
     "opn": "4.0.2",
+    "recursive-readdir": "2.1.1",
     "sockjs-client": "1.1.2",
     "strip-ansi": "3.0.1"
   }
diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json
index e0ae953e95f1836d0575b578fa5558d5e4a993a0..17a1c2fe59aece070793479744aaa8b4e3be91be 100644
--- a/packages/react-scripts/package.json
+++ b/packages/react-scripts/package.json
@@ -46,9 +46,7 @@
     "eslint-plugin-react": "6.4.1",
     "extract-text-webpack-plugin": "2.0.0",
     "file-loader": "0.10.0",
-    "filesize": "3.3.0",
     "fs-extra": "0.30.0",
-    "gzip-size": "3.0.0",
     "html-webpack-plugin": "2.28.0",
     "http-proxy-middleware": "0.17.3",
     "jest": "18.1.0",
@@ -56,8 +54,6 @@
     "postcss-loader": "1.3.1",
     "promise": "7.1.1",
     "react-dev-utils": "^0.5.1",
-    "recursive-readdir": "2.1.1",
-    "strip-ansi": "3.0.1",
     "style-loader": "0.13.1",
     "url-loader": "0.5.7",
     "webpack": "2.2.1",
diff --git a/packages/react-scripts/scripts/build.js b/packages/react-scripts/scripts/build.js
index 6892fcd6aff32603a2097445a208d9a273ef8cfa..b57a9dfb8218d30dc371eb6c28d9dece9b1ffe41 100644
--- a/packages/react-scripts/scripts/build.js
+++ b/packages/react-scripts/scripts/build.js
@@ -22,14 +22,13 @@ var chalk = require('chalk');
 var fs = require('fs-extra');
 var path = require('path');
 var url = require('url');
-var filesize = require('filesize');
-var gzipSize = require('gzip-size').sync;
 var webpack = require('webpack');
 var config = require('../config/webpack.config.prod');
 var paths = require('../config/paths');
 var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
-var recursive = require('recursive-readdir');
-var stripAnsi = require('strip-ansi');
+var FileSizeReporter = require('react-dev-utils/FileSizeReporter');
+var measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild;
+var printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
 
 var useYarn = fs.existsSync(paths.yarnLockFile);
 
@@ -38,88 +37,20 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
   process.exit(1);
 }
 
-// Input: /User/dan/app/build/static/js/main.82be8.js
-// Output: /static/js/main.js
-function removeFileNameHash(fileName) {
-  return fileName
-    .replace(paths.appBuild, '')
-    .replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3);
-}
-
-// Input: 1024, 2048
-// Output: "(+1 KB)"
-function getDifferenceLabel(currentSize, previousSize) {
-  var FIFTY_KILOBYTES = 1024 * 50;
-  var difference = currentSize - previousSize;
-  var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
-  if (difference >= FIFTY_KILOBYTES) {
-    return chalk.red('+' + fileSize);
-  } else if (difference < FIFTY_KILOBYTES && difference > 0) {
-    return chalk.yellow('+' + fileSize);
-  } else if (difference < 0) {
-    return chalk.green(fileSize);
-  } else {
-    return '';
-  }
-}
-
 // First, read the current file sizes in build directory.
 // This lets us display how much they changed later.
-recursive(paths.appBuild, (err, 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;
-    }, {});
-
+measureFileSizesBeforeBuild(paths.appBuild).then(previousFileSizes => {
   // Remove all content but keep the directory so that
   // if you're in it, you don't end up in Trash
   fs.emptyDirSync(paths.appBuild);
 
   // Start the webpack build
-  build(previousSizeMap);
+  build(previousFileSizes);
 
   // Merge with the public folder
   copyPublicFolder();
 });
 
-// Print a detailed summary of build files.
-function printFileSizes(stats, previousSizeMap) {
-  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 = getDifferenceLabel(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 => stripAnsi(a.sizeLabel).length)
-  );
-  assets.forEach(asset => {
-    var sizeLabel = asset.sizeLabel;
-    var sizeLength = stripAnsi(sizeLabel).length;
-    if (sizeLength < longestSizeLabelLength) {
-      var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength);
-      sizeLabel += rightPadding;
-    }
-    console.log(
-      '  ' + sizeLabel +
-      '  ' + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name)
-    );
-  });
-}
-
 // Print out errors
 function printErrors(summary, errors) {
   console.log(chalk.red(summary));
@@ -131,7 +62,7 @@ function printErrors(summary, errors) {
 }
 
 // Create the production build and print the deployment instructions.
-function build(previousSizeMap) {
+function build(previousFileSizes) {
   console.log('Creating an optimized production build...');
 
   var compiler;
@@ -163,7 +94,7 @@ function build(previousSizeMap) {
 
     console.log('File sizes after gzip:');
     console.log();
-    printFileSizes(stats, previousSizeMap);
+    printFileSizesAfterBuild(stats, previousFileSizes);
     console.log();
 
     var openCommand = process.platform === 'win32' ? 'start' : 'open';