build.js 9.22 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 url = require('url');
25
var filesize = require('filesize');
26
var gzipSize = require('gzip-size').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
var useYarn = fs.existsSync(paths.yarnLockFile);
Ville Immonen's avatar
Ville Immonen committed
35

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

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

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

Dan Abramov's avatar
Dan Abramov committed
66
67
// 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
68
recursive(paths.appBuild, (err, fileNames) => {
Dan Abramov's avatar
Dan Abramov committed
69
  var previousSizeMap = (fileNames || [])
Dan Abramov's avatar
Dan Abramov committed
70
    .filter(fileName => /\.(js|css)$/.test(fileName))
Elijah Manor's avatar
Elijah Manor committed
71
72
73
74
75
    .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
76
    }, {});
77

Elijah Manor's avatar
Elijah Manor committed
78
79
  // Remove all content but keep the directory so that
  // if you're in it, you don't end up in Trash
80
  fs.emptyDirSync(paths.appBuild);
Elijah Manor's avatar
Elijah Manor committed
81

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

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

89
// Print a detailed summary of build files.
Dan Abramov's avatar
Dan Abramov committed
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
121
122
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)
    );
  });
}

123
124
125
126
127
128
129
130
131
132
// Print out errors
function printErrors(summary, errors) {
  console.log(chalk.red(summary));
  console.log();
  errors.forEach(err => {
    console.log(err.message || err);
    console.log();
  });
}

133
// Create the production build and print the deployment instructions.
Elijah Manor's avatar
Elijah Manor committed
134
135
function build(previousSizeMap) {
  console.log('Creating an optimized production build...');
136
137
138
139
140
141
142
143
144
145

  var compiler;
  try {
    compiler = webpack(config);
  } catch (err) {
    printErrors('Failed to compile.', [err]);
    process.exit(1);
  }

  compiler.run((err, stats) => {
Elijah Manor's avatar
Elijah Manor committed
146
    if (err) {
147
148
149
150
151
152
      printErrors('Failed to compile.', [err]);
      process.exit(1);
    }

    if (stats.compilation.errors.length) {
      printErrors('Failed to compile.', stats.compilation.errors);
Elijah Manor's avatar
Elijah Manor committed
153
      process.exit(1);
154
    }
155

156
    if (process.env.CI && stats.compilation.warnings.length) {
157
     printErrors('Failed to compile. When process.env.CI = true, warnings are treated as failures. Most CI servers set this automatically.', stats.compilation.warnings);
158
159
160
     process.exit(1);
   }

Elijah Manor's avatar
Elijah Manor committed
161
    console.log(chalk.green('Compiled successfully.'));
162
    console.log();
Elijah Manor's avatar
Elijah Manor committed
163
164

    console.log('File sizes after gzip:');
165
    console.log();
Dan Abramov's avatar
Dan Abramov committed
166
    printFileSizes(stats, previousSizeMap);
167
    console.log();
Elijah Manor's avatar
Elijah Manor committed
168
169

    var openCommand = process.platform === 'win32' ? 'start' : 'open';
170
    var appPackage  = require(paths.appPackageJson);
171
    var publicUrl = paths.publicUrl;
Dan Abramov's avatar
Dan Abramov committed
172
    var publicPath = config.output.publicPath;
173
174
    var publicPathname = url.parse(publicPath).pathname;
    if (publicUrl && publicUrl.indexOf('.github.io/') !== -1) {
Dan Abramov's avatar
Dan Abramov committed
175
      // "homepage": "http://user.github.io/project"
176
      console.log('The project was built assuming it is hosted at ' + chalk.green(publicPathname) + '.');
Dan Abramov's avatar
Dan Abramov committed
177
178
179
      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.');
180
      console.log('To publish it at ' + chalk.green(publicUrl) + ', run:');
181
182
183
184
185
186
187
188
189
190
191
192
193
194
      // If script deploy has been added to package.json, skip the instructions
      if (typeof appPackage.scripts.deploy === 'undefined') {
        console.log();
        if (useYarn) {
          console.log('  ' + chalk.cyan('yarn') +  ' add --dev gh-pages');
        } else {
          console.log('  ' + chalk.cyan('npm') +  ' install --save-dev gh-pages');
        }
        console.log();
        console.log('Add the following script in your ' + chalk.cyan('package.json') + '.');
        console.log();
        console.log('    ' + chalk.dim('// ...'));
        console.log('    ' + chalk.yellow('"scripts"') + ': {');
        console.log('      ' + chalk.dim('// ...'));
195
196
        console.log('      ' + chalk.yellow('"predeploy"') + ': ' + chalk.yellow('"npm run build",'));
        console.log('      ' + chalk.yellow('"deploy"') + ': ' + chalk.yellow('"gh-pages -d build"'));
197
198
199
        console.log('    }');
        console.log();
        console.log('Then run:');
Ville Immonen's avatar
Ville Immonen committed
200
      }
201
      console.log();
Ville Immonen's avatar
Ville Immonen committed
202
      console.log('  ' + chalk.cyan(useYarn ? 'yarn' : 'npm') +  ' run deploy');
Elijah Manor's avatar
Elijah Manor committed
203
      console.log();
Dan Abramov's avatar
Dan Abramov committed
204
205
206
207
    } 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
208
      console.log();
Dan Abramov's avatar
Dan Abramov committed
209
      console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
Elijah Manor's avatar
Elijah Manor committed
210
      console.log();
Dan Abramov's avatar
Dan Abramov committed
211
    } else {
212
      if (publicUrl) {
Dan Abramov's avatar
Dan Abramov committed
213
        // "homepage": "http://mywebsite.com"
214
        console.log('The project was built assuming it is hosted at ' + chalk.green(publicUrl) +  '.');
Dan Abramov's avatar
Dan Abramov committed
215
        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
216
        console.log();
Dan Abramov's avatar
Dan Abramov committed
217
218
      } else {
        // no homepage
219
        console.log('The project was built assuming it is hosted at the server root.');
Dan Abramov's avatar
Dan Abramov committed
220
221
222
223
        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
224
        console.log();
Dan Abramov's avatar
Dan Abramov committed
225
      }
226
227
      var build = path.relative(process.cwd(), paths.appBuild);
      console.log('The ' + chalk.cyan(build) + ' folder is ready to be deployed.');
Dan Abramov's avatar
Dan Abramov committed
228
229
      console.log('You may also serve it locally with a static server:')
      console.log();
Ville Immonen's avatar
Ville Immonen committed
230
231
232
233
234
      if (useYarn) {
        console.log('  ' + chalk.cyan('yarn') +  ' global add pushstate-server');
      } else {
        console.log('  ' + chalk.cyan('npm') +  ' install -g pushstate-server');
      }
235
      console.log('  ' + chalk.cyan('pushstate-server') + ' ' + build);
Dan Abramov's avatar
Dan Abramov committed
236
      console.log('  ' + chalk.cyan(openCommand) + ' http://localhost:' + (process.env.PORT || 9000));
Dan Abramov's avatar
Dan Abramov committed
237
      console.log();
Elijah Manor's avatar
Elijah Manor committed
238
239
240
    }
  });
}
241
242
243
244
245
246
247

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