build.js 7.34 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';

15
var chalk = require('chalk');
16
var fs = require('fs-extra');
17
var path = require('path');
18
var filesize = require('filesize');
19
var gzipSize = require('gzip-size').sync;
20
var rimrafSync = require('rimraf').sync;
21
var webpack = require('webpack');
22
var config = require('../config/webpack.config.prod');
23
var paths = require('../config/paths');
24
var checkRequiredFiles = require('./utils/checkRequiredFiles');
Elijah Manor's avatar
Elijah Manor committed
25
var recursive = require('recursive-readdir');
26
var stripAnsi = require('strip-ansi');
27

28
29
checkRequiredFiles();

30
31
// Input: /User/dan/app/build/static/js/main.82be8.js
// Output: /static/js/main.js
Elijah Manor's avatar
Elijah Manor committed
32
function removeFileNameHash(fileName) {
Dan Abramov's avatar
Dan Abramov committed
33
34
35
  return fileName
    .replace(paths.appBuild, '')
    .replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3);
Elijah Manor's avatar
Elijah Manor committed
36
}
37

38
39
// Input: 1024, 2048
// Output: "(+1 KB)"
Dan Abramov's avatar
Dan Abramov committed
40
function getDifferenceLabel(currentSize, previousSize) {
41
  var FIFTY_KILOBYTES = 1024 * 50;
Elijah Manor's avatar
Elijah Manor committed
42
  var difference = currentSize - previousSize;
43
44
  var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
  if (difference >= FIFTY_KILOBYTES) {
Elijah Manor's avatar
Elijah Manor committed
45
    return chalk.red('+' + fileSize);
46
47
48
49
50
51
  } else if (difference < FIFTY_KILOBYTES && difference > 0) {
    return chalk.yellow('+' + fileSize);
  } else if (difference < 0) {
    return chalk.green(fileSize);
  } else {
    return '';
52
  }
Elijah Manor's avatar
Elijah Manor committed
53
}
54

Dan Abramov's avatar
Dan Abramov committed
55
56
// 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
57
recursive(paths.appBuild, (err, fileNames) => {
Dan Abramov's avatar
Dan Abramov committed
58
  var previousSizeMap = (fileNames || [])
Dan Abramov's avatar
Dan Abramov committed
59
    .filter(fileName => /\.(js|css)$/.test(fileName))
Elijah Manor's avatar
Elijah Manor committed
60
61
62
63
64
    .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
65
    }, {});
66

Elijah Manor's avatar
Elijah Manor committed
67
68
69
70
  // 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
71
  // Start the webpack build
Elijah Manor's avatar
Elijah Manor committed
72
  build(previousSizeMap);
73
74
75

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

78
// Print a detailed summary of build files.
Dan Abramov's avatar
Dan Abramov committed
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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)
    );
  });
}

112
// Create the production build and print the deployment instructions.
Elijah Manor's avatar
Elijah Manor committed
113
114
function build(previousSizeMap) {
  console.log('Creating an optimized production build...');
Dan Abramov's avatar
Dan Abramov committed
115
  webpack(config).run((err, stats) => {
Elijah Manor's avatar
Elijah Manor committed
116
117
118
119
    if (err) {
      console.error('Failed to create a production build. Reason:');
      console.error(err.message || err);
      process.exit(1);
120
    }
121

Elijah Manor's avatar
Elijah Manor committed
122
    console.log(chalk.green('Compiled successfully.'));
123
    console.log();
Elijah Manor's avatar
Elijah Manor committed
124
125

    console.log('File sizes after gzip:');
126
    console.log();
Dan Abramov's avatar
Dan Abramov committed
127
    printFileSizes(stats, previousSizeMap);
128
    console.log();
Elijah Manor's avatar
Elijah Manor committed
129
130
131

    var openCommand = process.platform === 'win32' ? 'start' : 'open';
    var homepagePath = require(paths.appPackageJson).homepage;
Dan Abramov's avatar
Dan Abramov committed
132
133
134
    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
135
136
137
138
139
      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
140
      console.log();
Dan Abramov's avatar
Dan Abramov committed
141
142
143
144
145
146
147
      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
148
      console.log();
Dan Abramov's avatar
Dan Abramov committed
149
150
151
152
    } 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
153
      console.log();
Dan Abramov's avatar
Dan Abramov committed
154
      console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
Elijah Manor's avatar
Elijah Manor committed
155
      console.log();
Dan Abramov's avatar
Dan Abramov committed
156
157
    } else {
      // no homepage or "homepage": "http://mywebsite.com"
Dan Abramov's avatar
Dan Abramov committed
158
159
160
161
      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
162
        console.log();
Dan Abramov's avatar
Dan Abramov committed
163
164
165
166
167
168
      } 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
169
        console.log();
Dan Abramov's avatar
Dan Abramov committed
170
      }
Dan Abramov's avatar
Dan Abramov committed
171
172
173
      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
174
175
176
      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
177
      console.log();
Elijah Manor's avatar
Elijah Manor committed
178
179
180
    }
  });
}
181
182
183
184
185
186
187

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