build.js 8.74 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');
Ville Immonen's avatar
Ville Immonen committed
24
var pathExists = require('path-exists');
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

Ville Immonen's avatar
Ville Immonen committed
34
35
var useYarn = pathExists.sync(paths.yarnLockFile);

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...');
Dan Abramov's avatar
Dan Abramov committed
136
  webpack(config).run((err, stats) => {
Elijah Manor's avatar
Elijah Manor committed
137
    if (err) {
138
139
140
141
142
143
      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
144
      process.exit(1);
145
    }
146

147
148
149
150
151
    if (process.env.CI && stats.compilation.warnings.length) {
     printErrors('Failed to compile.', stats.compilation.warnings);
     process.exit(1);
   }

Elijah Manor's avatar
Elijah Manor committed
152
    console.log(chalk.green('Compiled successfully.'));
153
    console.log();
Elijah Manor's avatar
Elijah Manor committed
154
155

    console.log('File sizes after gzip:');
156
    console.log();
Dan Abramov's avatar
Dan Abramov committed
157
    printFileSizes(stats, previousSizeMap);
158
    console.log();
Elijah Manor's avatar
Elijah Manor committed
159
160

    var openCommand = process.platform === 'win32' ? 'start' : 'open';
161
162
    var appPackage  = require(paths.appPackageJson);
    var homepagePath = appPackage.homepage;
Dan Abramov's avatar
Dan Abramov committed
163
164
165
    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
166
167
168
169
170
      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:');
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
      // 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('// ...'));
        console.log('      ' + chalk.yellow('"deploy"') + ': ' + chalk.yellow('"npm run build&&gh-pages -d build"'));
        console.log('    }');
        console.log();
        console.log('Then run:');
Ville Immonen's avatar
Ville Immonen committed
189
      }
190
      console.log();
Ville Immonen's avatar
Ville Immonen committed
191
      console.log('  ' + chalk.cyan(useYarn ? 'yarn' : 'npm') +  ' run deploy');
Elijah Manor's avatar
Elijah Manor committed
192
      console.log();
Dan Abramov's avatar
Dan Abramov committed
193
194
195
196
    } 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
197
      console.log();
Dan Abramov's avatar
Dan Abramov committed
198
      console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
Elijah Manor's avatar
Elijah Manor committed
199
      console.log();
Dan Abramov's avatar
Dan Abramov committed
200
201
    } else {
      // no homepage or "homepage": "http://mywebsite.com"
Dan Abramov's avatar
Dan Abramov committed
202
203
204
205
      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
206
        console.log();
Dan Abramov's avatar
Dan Abramov committed
207
208
209
210
211
212
      } 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
213
        console.log();
Dan Abramov's avatar
Dan Abramov committed
214
      }
Dan Abramov's avatar
Dan Abramov committed
215
216
217
      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();
Ville Immonen's avatar
Ville Immonen committed
218
219
220
221
222
      if (useYarn) {
        console.log('  ' + chalk.cyan('yarn') +  ' global add pushstate-server');
      } else {
        console.log('  ' + chalk.cyan('npm') +  ' install -g pushstate-server');
      }
Dan Abramov's avatar
Dan Abramov committed
223
224
      console.log('  ' + chalk.cyan('pushstate-server') + ' build');
      console.log('  ' + chalk.cyan(openCommand) + ' http://localhost:9000');
Dan Abramov's avatar
Dan Abramov committed
225
      console.log();
Elijah Manor's avatar
Elijah Manor committed
226
227
228
    }
  });
}
229
230
231
232
233
234
235

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