build.js 8.42 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 rimrafSync = require('rimraf').sync;
28
var webpack = require('webpack');
29
var config = require('../config/webpack.config.prod');
30
var paths = require('../config/paths');
31
var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
Elijah Manor's avatar
Elijah Manor committed
32
var recursive = require('recursive-readdir');
33
var stripAnsi = require('strip-ansi');
34

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

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

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

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

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

Elijah Manor's avatar
Elijah Manor committed
79
80
81
82
  // 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
83
  // Start the webpack build
Elijah Manor's avatar
Elijah Manor committed
84
  build(previousSizeMap);
85
86
87

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

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

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

134
// Create the production build and print the deployment instructions.
Elijah Manor's avatar
Elijah Manor committed
135
136
function build(previousSizeMap) {
  console.log('Creating an optimized production build...');
Dan Abramov's avatar
Dan Abramov committed
137
  webpack(config).run((err, stats) => {
Elijah Manor's avatar
Elijah Manor committed
138
    if (err) {
139
140
141
142
143
144
      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
145
      process.exit(1);
146
    }
147

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

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

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

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