From 278e9178d6f19f7a9d628eaed1d14ad3db185dfc Mon Sep 17 00:00:00 2001 From: Ade Viankakrisna Fadlil Date: Mon, 6 Mar 2017 08:49:53 +0700 Subject: [PATCH] extract generic build functions to react-dev-utils (#1726) * Temp rename * Rename to change the case * extract generic build functions to react-dev-utils * tweak package json files and move removeFileNameHash * revert removeFileNameHash * use paths.appBuild in printFileSizes * use paths.appBuild in removeFileNameHash * change curried functions to regular functions * add fs-extra to react-dev-utils deps * move getDifferenceLabel inside printFileSizes * inline copyPublicFolder * combine printFileSizes and removeFileNameHash to fileSizeReporter * fix typo * Tweak APIs and fix issues * Fix heading * Remove missing file * Newline * Newline * Trailing space * Update FileSizeReporter.js * Update build.js --- packages/react-dev-utils/FileSizeReporter.js | 109 +++++++++++++++++++ packages/react-dev-utils/README.md | 23 ++++ packages/react-dev-utils/package.json | 8 +- packages/react-scripts/package.json | 4 - packages/react-scripts/scripts/build.js | 83 ++------------ 5 files changed, 145 insertions(+), 82 deletions(-) create mode 100644 packages/react-dev-utils/FileSizeReporter.js diff --git a/packages/react-dev-utils/FileSizeReporter.js b/packages/react-dev-utils/FileSizeReporter.js new file mode 100644 index 00000000000..08273673f92 --- /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 2d430e2a299..26d762c3597 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` + +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, warnings: Array}): {errors: Array, warnings: Array}` 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 9a0e0dec3b3..5f823f0db13 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.0.1", "strip-ansi": "3.0.1" } diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 46c27432ad1..52dbc9b6bc2 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -48,9 +48,7 @@ "eslint-plugin-react": "6.4.1", "extract-text-webpack-plugin": "1.0.1", "file-loader": "0.10.0", - "filesize": "3.3.0", "fs-extra": "0.30.0", - "gzip-size": "3.0.0", "html-webpack-plugin": "2.24.0", "http-proxy-middleware": "0.17.3", "identity-obj-proxy": "^3.0.0", @@ -61,9 +59,7 @@ "postcss-loader": "1.2.2", "promise": "7.1.1", "react-dev-utils": "^0.5.1", - "recursive-readdir": "2.1.1", "sass-loader": "^4.0.2", - "strip-ansi": "3.0.1", "style-loader": "0.13.1", "url-loader": "0.5.7", "webpack": "1.14.0", diff --git a/packages/react-scripts/scripts/build.js b/packages/react-scripts/scripts/build.js index aa8bea20530..7a16304e1d9 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...'); webpack(config).run((err, stats) => { if (err) { @@ -154,7 +85,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';