Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extract generic build functions to react-dev-utils #1726

Merged
merged 21 commits into from
Mar 6, 2017
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions packages/react-dev-utils/FileSizeReporter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* 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,
};

23 changes: 23 additions & 0 deletions packages/react-dev-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
13 changes: 9 additions & 4 deletions packages/react-dev-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,30 @@
"engines": {
"node": ">=4"
},
"files": [
"clearConsole.js",
"files": [
"checkRequiredFiles.js",
"clearConsole.js",
"FileSizeReporter.js",
"formatWebpackMessages.js",
"getProcessForPort.js",
"InterpolateHtmlPlugin.js",
"openChrome.applescript",
"openBrowser.js",
"openChrome.applescript",
"prompt.js",
"removeFileNameHash.js",
"WatchMissingNodeModulesPlugin.js",
"webpackHotDevClient.js"
],
"dependencies": {
"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"
}
}
}
4 changes: 0 additions & 4 deletions packages/react-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,14 @@
"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",
"object-assign": "4.1.1",
"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",
Expand Down
86 changes: 9 additions & 77 deletions packages/react-scripts/scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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));
Expand All @@ -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;
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -244,4 +175,5 @@ function copyPublicFolder() {
dereference: true,
filter: file => file !== paths.appHtml
});
}
};