build.js 8.38 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

Elijah Manor's avatar
Elijah Manor committed
147
    console.log(chalk.green('Compiled successfully.'));
148
    console.log();
Elijah Manor's avatar
Elijah Manor committed
149
150

    console.log('File sizes after gzip:');
151
    console.log();
Dan Abramov's avatar
Dan Abramov committed
152
    printFileSizes(stats, previousSizeMap);
153
    console.log();
Elijah Manor's avatar
Elijah Manor committed
154
155
156

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

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