Skip to content

Commit 278e917

Browse files
viankakrisnarandycoulman
authored andcommitted
extract generic build functions to react-dev-utils (facebook#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
1 parent b1f667b commit 278e917

File tree

5 files changed

+145
-82
lines changed

5 files changed

+145
-82
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
'use strict';
11+
12+
var fs = require('fs');
13+
var path = require('path');
14+
var chalk = require('chalk');
15+
var filesize = require('filesize');
16+
var recursive = require('recursive-readdir');
17+
var stripAnsi = require('strip-ansi');
18+
var gzipSize = require('gzip-size').sync;
19+
20+
// Prints a detailed summary of build files.
21+
function printFileSizesAfterBuild(webpackStats, previousSizeMap) {
22+
var root = previousSizeMap.root;
23+
var sizes = previousSizeMap.sizes;
24+
var assets = webpackStats
25+
.toJson()
26+
.assets.filter(asset => /\.(js|css)$/.test(asset.name))
27+
.map(asset => {
28+
var fileContents = fs.readFileSync(path.join(root, asset.name));
29+
var size = gzipSize(fileContents);
30+
var previousSize = sizes[removeFileNameHash(root, asset.name)];
31+
var difference = getDifferenceLabel(size, previousSize);
32+
return {
33+
folder: path.join('build', path.dirname(asset.name)),
34+
name: path.basename(asset.name),
35+
size: size,
36+
sizeLabel: filesize(size) + (difference ? ' (' + difference + ')' : '')
37+
};
38+
});
39+
assets.sort((a, b) => b.size - a.size);
40+
var longestSizeLabelLength = Math.max.apply(
41+
null,
42+
assets.map(a => stripAnsi(a.sizeLabel).length)
43+
);
44+
assets.forEach(asset => {
45+
var sizeLabel = asset.sizeLabel;
46+
var sizeLength = stripAnsi(sizeLabel).length;
47+
if (sizeLength < longestSizeLabelLength) {
48+
var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength);
49+
sizeLabel += rightPadding;
50+
}
51+
console.log(
52+
' ' +
53+
sizeLabel +
54+
' ' +
55+
chalk.dim(asset.folder + path.sep) +
56+
chalk.cyan(asset.name)
57+
);
58+
});
59+
}
60+
61+
function removeFileNameHash(buildFolder, fileName) {
62+
return fileName
63+
.replace(buildFolder, '')
64+
.replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3);
65+
}
66+
67+
// Input: 1024, 2048
68+
// Output: "(+1 KB)"
69+
function getDifferenceLabel(currentSize, previousSize) {
70+
var FIFTY_KILOBYTES = 1024 * 50;
71+
var difference = currentSize - previousSize;
72+
var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
73+
if (difference >= FIFTY_KILOBYTES) {
74+
return chalk.red('+' + fileSize);
75+
} else if (difference < FIFTY_KILOBYTES && difference > 0) {
76+
return chalk.yellow('+' + fileSize);
77+
} else if (difference < 0) {
78+
return chalk.green(fileSize);
79+
} else {
80+
return '';
81+
}
82+
}
83+
84+
function measureFileSizesBeforeBuild(buildFolder) {
85+
return new Promise(resolve => {
86+
recursive(buildFolder, (err, fileNames) => {
87+
var sizes;
88+
if (!err && fileNames) {
89+
sizes = fileNames
90+
.filter(fileName => /\.(js|css)$/.test(fileName))
91+
.reduce((memo, fileName) => {
92+
var contents = fs.readFileSync(fileName);
93+
var key = removeFileNameHash(buildFolder, fileName);
94+
memo[key] = gzipSize(contents);
95+
return memo;
96+
}, {});
97+
}
98+
resolve({
99+
root: buildFolder,
100+
sizes: sizes || {},
101+
});
102+
});
103+
});
104+
}
105+
106+
module.exports = {
107+
measureFileSizesBeforeBuild: measureFileSizesBeforeBuild,
108+
printFileSizesAfterBuild: printFileSizesAfterBuild,
109+
};

packages/react-dev-utils/README.md

+23
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,29 @@ clearConsole();
110110
console.log('Just cleared the screen!');
111111
```
112112

113+
#### `FileSizeReporter`
114+
115+
##### `measureFileSizesBeforeBuild(buildFolder: string): Promise<OpaqueFileSizes>`
116+
117+
Captures JS and CSS asset sizes inside the passed `buildFolder`. Save the result value to compare it after the build.
118+
119+
##### `printFileSizesAfterBuild(webpackStats: WebpackStats, previousFileSizes: OpaqueFileSizes)`
120+
121+
Prints the JS and CSS asset sizes after the build, and includes a size comparison with `previousFileSizes` that were captured earlier using `measureFileSizesBeforeBuild()`.
122+
123+
```js
124+
var {
125+
measureFileSizesBeforeBuild,
126+
printFileSizesAfterBuild,
127+
} = require('react-dev-utils/FileSizeReporter');
128+
129+
measureFileSizesBeforeBuild(buildFolder).then(previousFileSizes => {
130+
return cleanAndRebuild().then(webpackStats => {
131+
printFileSizesAfterBuild(webpackStats, previousFileSizes);
132+
});
133+
});
134+
```
135+
113136
#### `formatWebpackMessages({errors: Array<string>, warnings: Array<string>}): {errors: Array<string>, warnings: Array<string>}`
114137

115138
Extracts and prettifies warning and error messages from webpack [stats](https://github.com/webpack/docs/wiki/node.js-api#stats) object.

packages/react-dev-utils/package.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@
1111
"node": ">=4"
1212
},
1313
"files": [
14-
"clearConsole.js",
1514
"checkRequiredFiles.js",
15+
"clearConsole.js",
16+
"FileSizeReporter.js",
1617
"formatWebpackMessages.js",
1718
"getProcessForPort.js",
1819
"InterpolateHtmlPlugin.js",
19-
"openChrome.applescript",
2020
"openBrowser.js",
21+
"openChrome.applescript",
2122
"prompt.js",
2223
"WatchMissingNodeModulesPlugin.js",
2324
"webpackHotDevClient.js"
@@ -26,8 +27,11 @@
2627
"ansi-html": "0.0.5",
2728
"chalk": "1.1.3",
2829
"escape-string-regexp": "1.0.5",
30+
"filesize": "3.3.0",
31+
"gzip-size": "3.0.0",
2932
"html-entities": "1.2.0",
3033
"opn": "4.0.2",
34+
"recursive-readdir": "2.1.1",
3135
"sockjs-client": "1.0.1",
3236
"strip-ansi": "3.0.1"
3337
}

packages/react-scripts/package.json

-4
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,7 @@
4848
"eslint-plugin-react": "6.4.1",
4949
"extract-text-webpack-plugin": "1.0.1",
5050
"file-loader": "0.10.0",
51-
"filesize": "3.3.0",
5251
"fs-extra": "0.30.0",
53-
"gzip-size": "3.0.0",
5452
"html-webpack-plugin": "2.24.0",
5553
"http-proxy-middleware": "0.17.3",
5654
"identity-obj-proxy": "^3.0.0",
@@ -61,9 +59,7 @@
6159
"postcss-loader": "1.2.2",
6260
"promise": "7.1.1",
6361
"react-dev-utils": "^0.5.1",
64-
"recursive-readdir": "2.1.1",
6562
"sass-loader": "^4.0.2",
66-
"strip-ansi": "3.0.1",
6763
"style-loader": "0.13.1",
6864
"url-loader": "0.5.7",
6965
"webpack": "1.14.0",

packages/react-scripts/scripts/build.js

+7-76
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,13 @@ var chalk = require('chalk');
2222
var fs = require('fs-extra');
2323
var path = require('path');
2424
var url = require('url');
25-
var filesize = require('filesize');
26-
var gzipSize = require('gzip-size').sync;
2725
var webpack = require('webpack');
2826
var config = require('../config/webpack.config.prod');
2927
var paths = require('../config/paths');
3028
var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
31-
var recursive = require('recursive-readdir');
32-
var stripAnsi = require('strip-ansi');
29+
var FileSizeReporter = require('react-dev-utils/FileSizeReporter');
30+
var measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild;
31+
var printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
3332

3433
var useYarn = fs.existsSync(paths.yarnLockFile);
3534

@@ -38,88 +37,20 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
3837
process.exit(1);
3938
}
4039

41-
// Input: /User/dan/app/build/static/js/main.82be8.js
42-
// Output: /static/js/main.js
43-
function removeFileNameHash(fileName) {
44-
return fileName
45-
.replace(paths.appBuild, '')
46-
.replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3);
47-
}
48-
49-
// Input: 1024, 2048
50-
// Output: "(+1 KB)"
51-
function getDifferenceLabel(currentSize, previousSize) {
52-
var FIFTY_KILOBYTES = 1024 * 50;
53-
var difference = currentSize - previousSize;
54-
var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
55-
if (difference >= FIFTY_KILOBYTES) {
56-
return chalk.red('+' + fileSize);
57-
} else if (difference < FIFTY_KILOBYTES && difference > 0) {
58-
return chalk.yellow('+' + fileSize);
59-
} else if (difference < 0) {
60-
return chalk.green(fileSize);
61-
} else {
62-
return '';
63-
}
64-
}
65-
6640
// First, read the current file sizes in build directory.
6741
// This lets us display how much they changed later.
68-
recursive(paths.appBuild, (err, fileNames) => {
69-
var previousSizeMap = (fileNames || [])
70-
.filter(fileName => /\.(js|css)$/.test(fileName))
71-
.reduce((memo, fileName) => {
72-
var contents = fs.readFileSync(fileName);
73-
var key = removeFileNameHash(fileName);
74-
memo[key] = gzipSize(contents);
75-
return memo;
76-
}, {});
77-
42+
measureFileSizesBeforeBuild(paths.appBuild).then(previousFileSizes => {
7843
// Remove all content but keep the directory so that
7944
// if you're in it, you don't end up in Trash
8045
fs.emptyDirSync(paths.appBuild);
8146

8247
// Start the webpack build
83-
build(previousSizeMap);
48+
build(previousFileSizes);
8449

8550
// Merge with the public folder
8651
copyPublicFolder();
8752
});
8853

89-
// Print a detailed summary of build files.
90-
function printFileSizes(stats, previousSizeMap) {
91-
var assets = stats.toJson().assets
92-
.filter(asset => /\.(js|css)$/.test(asset.name))
93-
.map(asset => {
94-
var fileContents = fs.readFileSync(paths.appBuild + '/' + asset.name);
95-
var size = gzipSize(fileContents);
96-
var previousSize = previousSizeMap[removeFileNameHash(asset.name)];
97-
var difference = getDifferenceLabel(size, previousSize);
98-
return {
99-
folder: path.join('build', path.dirname(asset.name)),
100-
name: path.basename(asset.name),
101-
size: size,
102-
sizeLabel: filesize(size) + (difference ? ' (' + difference + ')' : '')
103-
};
104-
});
105-
assets.sort((a, b) => b.size - a.size);
106-
var longestSizeLabelLength = Math.max.apply(null,
107-
assets.map(a => stripAnsi(a.sizeLabel).length)
108-
);
109-
assets.forEach(asset => {
110-
var sizeLabel = asset.sizeLabel;
111-
var sizeLength = stripAnsi(sizeLabel).length;
112-
if (sizeLength < longestSizeLabelLength) {
113-
var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength);
114-
sizeLabel += rightPadding;
115-
}
116-
console.log(
117-
' ' + sizeLabel +
118-
' ' + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name)
119-
);
120-
});
121-
}
122-
12354
// Print out errors
12455
function printErrors(summary, errors) {
12556
console.log(chalk.red(summary));
@@ -131,7 +62,7 @@ function printErrors(summary, errors) {
13162
}
13263

13364
// Create the production build and print the deployment instructions.
134-
function build(previousSizeMap) {
65+
function build(previousFileSizes) {
13566
console.log('Creating an optimized production build...');
13667
webpack(config).run((err, stats) => {
13768
if (err) {
@@ -154,7 +85,7 @@ function build(previousSizeMap) {
15485

15586
console.log('File sizes after gzip:');
15687
console.log();
157-
printFileSizes(stats, previousSizeMap);
88+
printFileSizesAfterBuild(stats, previousFileSizes);
15889
console.log();
15990

16091
var openCommand = process.platform === 'win32' ? 'start' : 'open';

0 commit comments

Comments
 (0)