scripts/build.js (97 lines of code) (raw):
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Do this as the first thing so that any code reading it knows the right env.
process.env.NODE_ENV = 'production';
// Load environment variables from .env file. Surpress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set.
// https://github.com/motdotla/dotenv
require('dotenv').config({silent: true});
var chalk = require('chalk');
var fs = require('fs-extra');
var path = require('path');
var filesize = require('filesize');
var gzipSize = require('gzip-size').sync;
var rimrafSync = require('rimraf').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');
// Warn and crash if required files are missing
if (!checkRequiredFiles([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.appDist, '')
.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.appDist, (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;
}, {});
// Remove all content but keep the directory so that
// if you're in it, you don't end up in Trash
rimrafSync(paths.appDist + '/*');
// Start the webpack build
build(previousSizeMap);
});
// 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.appDist + '/' + asset.name);
var size = gzipSize(fileContents);
var previousSize = previousSizeMap[removeFileNameHash(asset.name)];
var difference = getDifferenceLabel(size, previousSize);
return {
folder: path.join(paths.appDist, 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)
);
});
}
// Create the production build and print the deployment instructions.
function build(previousSizeMap) {
console.log('Creating an optimized production build...');
webpack(config).run((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('Compiled successfully.'));
console.log();
console.log('File sizes after gzip:');
console.log();
printFileSizes(stats, previousSizeMap);
console.log();
console.log('The project is now ready to be published to npm.');
});
}