build.js 6.8 KB
Newer Older
Christopher Chedeau's avatar
Christopher Chedeau committed
1
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
11
process.env.NODE_ENV = 'production';

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

Elijah Manor's avatar
Elijah Manor committed
24
function removeFileNameHash(fileName) {
Dan Abramov's avatar
Dan Abramov committed
25
26
27
  return fileName
    .replace(paths.appBuild, '')
    .replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3);
Elijah Manor's avatar
Elijah Manor committed
28
}
29

Dan Abramov's avatar
Dan Abramov committed
30
function getDifferenceLabel(currentSize, previousSize) {
31
  var FIFTY_KILOBYTES = 1024 * 50;
Elijah Manor's avatar
Elijah Manor committed
32
  var difference = currentSize - previousSize;
33
34
  var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
  if (difference >= FIFTY_KILOBYTES) {
Elijah Manor's avatar
Elijah Manor committed
35
    return chalk.red('+' + fileSize);
36
37
38
39
40
41
  } else if (difference < FIFTY_KILOBYTES && difference > 0) {
    return chalk.yellow('+' + fileSize);
  } else if (difference < 0) {
    return chalk.green(fileSize);
  } else {
    return '';
42
  }
Elijah Manor's avatar
Elijah Manor committed
43
}
44

Dan Abramov's avatar
Dan Abramov committed
45
46
// 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
47
recursive(paths.appBuild, (err, fileNames) => {
Dan Abramov's avatar
Dan Abramov committed
48
  var previousSizeMap = (fileNames || [])
Dan Abramov's avatar
Dan Abramov committed
49
    .filter(fileName => /\.(js|css)$/.test(fileName))
Elijah Manor's avatar
Elijah Manor committed
50
51
52
53
54
    .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
55
    }, {});
56

Elijah Manor's avatar
Elijah Manor committed
57
58
59
60
  // 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
61
  // Start the webpack build
Elijah Manor's avatar
Elijah Manor committed
62
63
  build(previousSizeMap);
});
64

Dan Abramov's avatar
Dan Abramov committed
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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)
    );
  });
}

Elijah Manor's avatar
Elijah Manor committed
99
100
function build(previousSizeMap) {
  console.log('Creating an optimized production build...');
Dan Abramov's avatar
Dan Abramov committed
101
  webpack(config).run((err, stats) => {
Elijah Manor's avatar
Elijah Manor committed
102
103
104
105
    if (err) {
      console.error('Failed to create a production build. Reason:');
      console.error(err.message || err);
      process.exit(1);
106
    }
107

Elijah Manor's avatar
Elijah Manor committed
108
    console.log(chalk.green('Compiled successfully.'));
109
    console.log();
Elijah Manor's avatar
Elijah Manor committed
110
111

    console.log('File sizes after gzip:');
112
    console.log();
Dan Abramov's avatar
Dan Abramov committed
113
    printFileSizes(stats, previousSizeMap);
114
    console.log();
Elijah Manor's avatar
Elijah Manor committed
115
116
117

    var openCommand = process.platform === 'win32' ? 'start' : 'open';
    var homepagePath = require(paths.appPackageJson).homepage;
Dan Abramov's avatar
Dan Abramov committed
118
119
120
    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
121
122
123
124
125
      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
126
      console.log();
Dan Abramov's avatar
Dan Abramov committed
127
128
129
130
131
132
133
      console.log('  ' + chalk.blue('git') + chalk.cyan(' commit -am ') + chalk.yellow('"Save local changes"'));
      console.log('  ' + chalk.blue('git') + chalk.cyan(' checkout -B gh-pages'));
      console.log('  ' + chalk.blue('git') + chalk.cyan(' add -f build'));
      console.log('  ' + chalk.blue('git') + chalk.cyan(' commit -am ' + chalk.yellow('"Rebuild website"')));
      console.log('  ' + chalk.blue('git') + chalk.cyan(' filter-branch -f --prune-empty --subdirectory-filter build'));
      console.log('  ' + chalk.blue('git') + chalk.cyan(' push -f origin gh-pages'));
      console.log('  ' + chalk.blue('git') + chalk.cyan(' checkout -'));
Elijah Manor's avatar
Elijah Manor committed
134
      console.log();
Dan Abramov's avatar
Dan Abramov committed
135
136
137
138
    } 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
139
      console.log();
Dan Abramov's avatar
Dan Abramov committed
140
      console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
Elijah Manor's avatar
Elijah Manor committed
141
      console.log();
Dan Abramov's avatar
Dan Abramov committed
142
143
    } else {
      // no homepage or "homepage": "http://mywebsite.com"
Dan Abramov's avatar
Dan Abramov committed
144
145
146
147
      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
148
        console.log();
Dan Abramov's avatar
Dan Abramov committed
149
150
151
152
153
154
      } 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
155
        console.log();
Dan Abramov's avatar
Dan Abramov committed
156
      }
Dan Abramov's avatar
Dan Abramov committed
157
158
159
160
161
162
163
      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();
      console.log('  ' + chalk.blue('npm') +  chalk.cyan(' install -g pushstate-server'));
      console.log('  ' + chalk.blue('pushstate-server') + chalk.cyan(' build'));
      console.log('  ' + chalk.blue(openCommand) + chalk.cyan(' http://localhost:9000'));
      console.log();
Elijah Manor's avatar
Elijah Manor committed
164
165
166
    }
  });
}