build.js 7 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
// Do this as the first thing so that any code reading it knows the right env.
11
12
process.env.NODE_ENV = 'production';

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

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

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

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

Elijah Manor's avatar
Elijah Manor committed
62
63
64
65
  // 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
66
  // Start the webpack build
Elijah Manor's avatar
Elijah Manor committed
67
68
  build(previousSizeMap);
});
69

70
// Print a detailed summary of build files.
Dan Abramov's avatar
Dan Abramov committed
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
99
100
101
102
103
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)
    );
  });
}

104
// Create the production build and print the deployment instructions.
Elijah Manor's avatar
Elijah Manor committed
105
106
function build(previousSizeMap) {
  console.log('Creating an optimized production build...');
Dan Abramov's avatar
Dan Abramov committed
107
  webpack(config).run((err, stats) => {
Elijah Manor's avatar
Elijah Manor committed
108
109
110
111
    if (err) {
      console.error('Failed to create a production build. Reason:');
      console.error(err.message || err);
      process.exit(1);
112
    }
113

Elijah Manor's avatar
Elijah Manor committed
114
    console.log(chalk.green('Compiled successfully.'));
115
    console.log();
Elijah Manor's avatar
Elijah Manor committed
116
117

    console.log('File sizes after gzip:');
118
    console.log();
Dan Abramov's avatar
Dan Abramov committed
119
    printFileSizes(stats, previousSizeMap);
120
    console.log();
Elijah Manor's avatar
Elijah Manor committed
121
122
123

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