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.'); }); }