build.js 7.72 KB
Newer Older
1
// @remove-on-eject-begin
Christopher Chedeau's avatar
Christopher Chedeau committed
2
3
4
5
6
7
8
9
/**
 * 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.
 */
10
// @remove-on-eject-end
Christopher Chedeau's avatar
Christopher Chedeau committed
11

12
// Do this as the first thing so that any code reading it knows the right env.
13
14
process.env.NODE_ENV = 'production';

Brian Ng's avatar
Brian Ng committed
15
// Load environment variables from .env file. Suppress warnings using silent
16
17
18
19
20
// 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});

21
var chalk = require('chalk');
22
var fs = require('fs-extra');
23
var path = require('path');
24
var filesize = require('filesize');
25
var gzipSize = require('gzip-size').sync;
26
var rimrafSync = require('rimraf').sync;
27
var webpack = require('webpack');
28
var config = require('../config/webpack.config.prod');
29
var paths = require('../config/paths');
30
var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
Elijah Manor's avatar
Elijah Manor committed
31
var recursive = require('recursive-readdir');
32
var stripAnsi = require('strip-ansi');
33

34
35
36
37
// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
  process.exit(1);
}
38

39
40
// Input: /User/dan/app/build/static/js/main.82be8.js
// Output: /static/js/main.js
Elijah Manor's avatar
Elijah Manor committed
41
function removeFileNameHash(fileName) {
Dan Abramov's avatar
Dan Abramov committed
42
43
44
  return fileName
    .replace(paths.appBuild, '')
    .replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3);
Elijah Manor's avatar
Elijah Manor committed
45
}
46

47
48
// Input: 1024, 2048
// Output: "(+1 KB)"
Dan Abramov's avatar
Dan Abramov committed
49
function getDifferenceLabel(currentSize, previousSize) {
50
  var FIFTY_KILOBYTES = 1024 * 50;
Elijah Manor's avatar
Elijah Manor committed
51
  var difference = currentSize - previousSize;
52
53
  var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
  if (difference >= FIFTY_KILOBYTES) {
Elijah Manor's avatar
Elijah Manor committed
54
    return chalk.red('+' + fileSize);
55
56
57
58
59
60
  } else if (difference < FIFTY_KILOBYTES && difference > 0) {
    return chalk.yellow('+' + fileSize);
  } else if (difference < 0) {
    return chalk.green(fileSize);
  } else {
    return '';
61
  }
Elijah Manor's avatar
Elijah Manor committed
62
}
63

Dan Abramov's avatar
Dan Abramov committed
64
65
// First, read the current file sizes in build directory.
// This lets us display how much they changed later.
Dan Abramov's avatar
Dan Abramov committed
66
recursive(paths.appBuild, (err, fileNames) => {
Dan Abramov's avatar
Dan Abramov committed
67
  var previousSizeMap = (fileNames || [])
Dan Abramov's avatar
Dan Abramov committed
68
    .filter(fileName => /\.(js|css)$/.test(fileName))
Elijah Manor's avatar
Elijah Manor committed
69
70
71
72
73
    .reduce((memo, fileName) => {
      var contents = fs.readFileSync(fileName);
      var key = removeFileNameHash(fileName);
      memo[key] = gzipSize(contents);
      return memo;
Dan Abramov's avatar
Dan Abramov committed
74
    }, {});
75

Elijah Manor's avatar
Elijah Manor committed
76
77
78
79
  // Remove all content but keep the directory so that
  // if you're in it, you don't end up in Trash
  rimrafSync(paths.appBuild + '/*');

Dan Abramov's avatar
Dan Abramov committed
80
  // Start the webpack build
Elijah Manor's avatar
Elijah Manor committed
81
  build(previousSizeMap);
82
83
84

  // Merge with the public folder
  copyPublicFolder();
Elijah Manor's avatar
Elijah Manor committed
85
});
86

87
// Print a detailed summary of build files.
Dan Abramov's avatar
Dan Abramov committed
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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)
    );
  });
}

121
// Create the production build and print the deployment instructions.
Elijah Manor's avatar
Elijah Manor committed
122
123
function build(previousSizeMap) {
  console.log('Creating an optimized production build...');
Dan Abramov's avatar
Dan Abramov committed
124
  webpack(config).run((err, stats) => {
Elijah Manor's avatar
Elijah Manor committed
125
126
127
128
    if (err) {
      console.error('Failed to create a production build. Reason:');
      console.error(err.message || err);
      process.exit(1);
129
    }
130

Elijah Manor's avatar
Elijah Manor committed
131
    console.log(chalk.green('Compiled successfully.'));
132
    console.log();
Elijah Manor's avatar
Elijah Manor committed
133
134

    console.log('File sizes after gzip:');
135
    console.log();
Dan Abramov's avatar
Dan Abramov committed
136
    printFileSizes(stats, previousSizeMap);
137
    console.log();
Elijah Manor's avatar
Elijah Manor committed
138
139
140

    var openCommand = process.platform === 'win32' ? 'start' : 'open';
    var homepagePath = require(paths.appPackageJson).homepage;
Dan Abramov's avatar
Dan Abramov committed
141
142
143
    var publicPath = config.output.publicPath;
    if (homepagePath && homepagePath.indexOf('.github.io/') !== -1) {
      // "homepage": "http://user.github.io/project"
Dan Abramov's avatar
Dan Abramov committed
144
145
146
147
148
      console.log('The project was built assuming it is hosted at ' + chalk.green(publicPath) + '.');
      console.log('You can control this with the ' + chalk.green('homepage') + ' field in your '  + chalk.cyan('package.json') + '.');
      console.log();
      console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
      console.log('To publish it at ' + chalk.green(homepagePath) + ', run:');
Elijah Manor's avatar
Elijah Manor committed
149
      console.log();
Dan Abramov's avatar
Dan Abramov committed
150
151
152
153
154
155
156
      console.log('  ' + chalk.cyan('git') + ' commit -am ' + chalk.yellow('"Save local changes"'));
      console.log('  ' + chalk.cyan('git') + ' checkout -B gh-pages');
      console.log('  ' + chalk.cyan('git') + ' add -f build');
      console.log('  ' + chalk.cyan('git') + ' commit -am ' + chalk.yellow('"Rebuild website"'));
      console.log('  ' + chalk.cyan('git') + ' filter-branch -f --prune-empty --subdirectory-filter build');
      console.log('  ' + chalk.cyan('git') + ' push -f origin gh-pages');
      console.log('  ' + chalk.cyan('git') + ' checkout -');
Elijah Manor's avatar
Elijah Manor committed
157
      console.log();
Dan Abramov's avatar
Dan Abramov committed
158
159
160
161
    } else if (publicPath !== '/') {
      // "homepage": "http://mywebsite.com/project"
      console.log('The project was built assuming it is hosted at ' + chalk.green(publicPath) + '.');
      console.log('You can control this with the ' + chalk.green('homepage') + ' field in your '  + chalk.cyan('package.json') + '.');
Elijah Manor's avatar
Elijah Manor committed
162
      console.log();
Dan Abramov's avatar
Dan Abramov committed
163
      console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
Elijah Manor's avatar
Elijah Manor committed
164
      console.log();
Dan Abramov's avatar
Dan Abramov committed
165
166
    } else {
      // no homepage or "homepage": "http://mywebsite.com"
Dan Abramov's avatar
Dan Abramov committed
167
168
169
170
      console.log('The project was built assuming it is hosted at the server root.');
      if (homepagePath) {
        // "homepage": "http://mywebsite.com"
        console.log('You can control this with the ' + chalk.green('homepage') + ' field in your '  + chalk.cyan('package.json') + '.');
Dan Abramov's avatar
Dan Abramov committed
171
        console.log();
Dan Abramov's avatar
Dan Abramov committed
172
173
174
175
176
177
      } else {
        // no homepage
        console.log('To override this, specify the ' + chalk.green('homepage') + ' in your '  + chalk.cyan('package.json') + '.');
        console.log('For example, add this to build it for GitHub Pages:')
        console.log();
        console.log('  ' + chalk.green('"homepage"') + chalk.cyan(': ') + chalk.green('"http://myname.github.io/myapp"') + chalk.cyan(','));
Dan Abramov's avatar
Dan Abramov committed
178
        console.log();
Dan Abramov's avatar
Dan Abramov committed
179
      }
Dan Abramov's avatar
Dan Abramov committed
180
181
182
      console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
      console.log('You may also serve it locally with a static server:')
      console.log();
Dan Abramov's avatar
Dan Abramov committed
183
184
185
      console.log('  ' + chalk.cyan('npm') +  ' install -g pushstate-server');
      console.log('  ' + chalk.cyan('pushstate-server') + ' build');
      console.log('  ' + chalk.cyan(openCommand) + ' http://localhost:9000');
Dan Abramov's avatar
Dan Abramov committed
186
      console.log();
Elijah Manor's avatar
Elijah Manor committed
187
188
189
    }
  });
}
190
191
192
193
194
195
196

function copyPublicFolder() {
  fs.copySync(paths.appPublic, paths.appBuild, {
    dereference: true,
    filter: file => file !== paths.appHtml
  });
}