1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | process.env.NODE_ENV = 'production';
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | require('dotenv').config({silent: true});
|
20 |
|
21 | var chalk = require('chalk');
|
22 | var fs = require('fs-extra');
|
23 | var path = require('path');
|
24 | var url = require('url');
|
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');
|
31 | var recursive = require('recursive-readdir');
|
32 | var stripAnsi = require('strip-ansi');
|
33 |
|
34 | var useYarn = fs.existsSync(paths.yarnLockFile);
|
35 |
|
36 |
|
37 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
38 | process.exit(1);
|
39 | }
|
40 |
|
41 |
|
42 |
|
43 | function removeFileNameHash(fileName) {
|
44 | return fileName
|
45 | .replace(paths.appBuild, '')
|
46 | .replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3);
|
47 | }
|
48 |
|
49 |
|
50 |
|
51 | function getDifferenceLabel(currentSize, previousSize) {
|
52 | var FIFTY_KILOBYTES = 1024 * 50;
|
53 | var difference = currentSize - previousSize;
|
54 | var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
|
55 | if (difference >= FIFTY_KILOBYTES) {
|
56 | return chalk.red('+' + fileSize);
|
57 | } else if (difference < FIFTY_KILOBYTES && difference > 0) {
|
58 | return chalk.yellow('+' + fileSize);
|
59 | } else if (difference < 0) {
|
60 | return chalk.green(fileSize);
|
61 | } else {
|
62 | return '';
|
63 | }
|
64 | }
|
65 |
|
66 |
|
67 |
|
68 | recursive(paths.appBuild, (err, fileNames) => {
|
69 | var previousSizeMap = (fileNames || [])
|
70 | .filter(fileName => /\.(js|css)$/.test(fileName))
|
71 | .reduce((memo, fileName) => {
|
72 | var contents = fs.readFileSync(fileName);
|
73 | var key = removeFileNameHash(fileName);
|
74 | memo[key] = gzipSize(contents);
|
75 | return memo;
|
76 | }, {});
|
77 |
|
78 |
|
79 |
|
80 | fs.emptyDirSync(paths.appBuild);
|
81 |
|
82 |
|
83 | build(previousSizeMap);
|
84 |
|
85 |
|
86 | copyPublicFolder();
|
87 | });
|
88 |
|
89 |
|
90 | function printFileSizes(stats, previousSizeMap) {
|
91 | var assets = stats.toJson().assets
|
92 | .filter(asset => /\.(js|css)$/.test(asset.name))
|
93 | .map(asset => {
|
94 | var fileContents = fs.readFileSync(paths.appBuild + '/' + asset.name);
|
95 | var size = gzipSize(fileContents);
|
96 | var previousSize = previousSizeMap[removeFileNameHash(asset.name)];
|
97 | var difference = getDifferenceLabel(size, previousSize);
|
98 | return {
|
99 | folder: path.join('build', path.dirname(asset.name)),
|
100 | name: path.basename(asset.name),
|
101 | size: size,
|
102 | sizeLabel: filesize(size) + (difference ? ' (' + difference + ')' : '')
|
103 | };
|
104 | });
|
105 | assets.sort((a, b) => b.size - a.size);
|
106 | var longestSizeLabelLength = Math.max.apply(null,
|
107 | assets.map(a => stripAnsi(a.sizeLabel).length)
|
108 | );
|
109 | assets.forEach(asset => {
|
110 | var sizeLabel = asset.sizeLabel;
|
111 | var sizeLength = stripAnsi(sizeLabel).length;
|
112 | if (sizeLength < longestSizeLabelLength) {
|
113 | var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength);
|
114 | sizeLabel += rightPadding;
|
115 | }
|
116 | console.log(
|
117 | ' ' + sizeLabel +
|
118 | ' ' + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name)
|
119 | );
|
120 | });
|
121 | }
|
122 |
|
123 |
|
124 | function printErrors(summary, errors) {
|
125 | console.log(chalk.red(summary));
|
126 | console.log();
|
127 | errors.forEach(err => {
|
128 | if (err.loaderSource === 'ts-loader') {
|
129 | if (err.file) {
|
130 | console.log('Error in ' + err.file);
|
131 | } else if (err.module) {
|
132 | console.log('Error in ' + err.module.userRequest);
|
133 | }
|
134 | }
|
135 |
|
136 | console.log(err.message || err);
|
137 | console.log();
|
138 | });
|
139 | }
|
140 |
|
141 |
|
142 | function build(previousSizeMap) {
|
143 | console.log('Creating an optimized production build...');
|
144 | webpack(config).run((err, stats) => {
|
145 | if (err) {
|
146 | printErrors('Failed to compile.', [err]);
|
147 | process.exit(1);
|
148 | }
|
149 |
|
150 | if (stats.compilation.errors.length) {
|
151 | printErrors('Failed to compile.', stats.compilation.errors);
|
152 | process.exit(1);
|
153 | }
|
154 |
|
155 | if (process.env.FAIL_ON_WARNING && process.env.CI && stats.compilation.warnings.length) {
|
156 | printErrors('Failed to compile. When process.env.CI = true, warnings are treated as failures. Most CI servers set this automatically.', stats.compilation.warnings);
|
157 | process.exit(1);
|
158 | }
|
159 |
|
160 | console.log(chalk.green('Compiled successfully.'));
|
161 | console.log();
|
162 |
|
163 | console.log('File sizes after gzip:');
|
164 | console.log();
|
165 | printFileSizes(stats, previousSizeMap);
|
166 | console.log();
|
167 |
|
168 | var openCommand = process.platform === 'win32' ? 'start' : 'open';
|
169 | var appPackage = require(paths.appPackageJson);
|
170 | var publicUrl = paths.publicUrl;
|
171 | var publicPath = config.output.publicPath;
|
172 | var publicPathname = url.parse(publicPath).pathname;
|
173 | if (publicUrl && publicUrl.indexOf('.github.io/') !== -1) {
|
174 |
|
175 | console.log('The project was built assuming it is hosted at ' + chalk.green(publicPathname) + '.');
|
176 | console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.');
|
177 | console.log();
|
178 | console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
|
179 | console.log('To publish it at ' + chalk.green(publicUrl) + ', run:');
|
180 |
|
181 | if (typeof appPackage.scripts.deploy === 'undefined') {
|
182 | console.log();
|
183 | if (useYarn) {
|
184 | console.log(' ' + chalk.cyan('yarn') + ' add --dev gh-pages');
|
185 | } else {
|
186 | console.log(' ' + chalk.cyan('npm') + ' install --save-dev gh-pages');
|
187 | }
|
188 | console.log();
|
189 | console.log('Add the following script in your ' + chalk.cyan('package.json') + '.');
|
190 | console.log();
|
191 | console.log(' ' + chalk.dim('// ...'));
|
192 | console.log(' ' + chalk.yellow('"scripts"') + ': {');
|
193 | console.log(' ' + chalk.dim('// ...'));
|
194 | console.log(' ' + chalk.yellow('"predeploy"') + ': ' + chalk.yellow('"npm run build",'));
|
195 | console.log(' ' + chalk.yellow('"deploy"') + ': ' + chalk.yellow('"gh-pages -d build"'));
|
196 | console.log(' }');
|
197 | console.log();
|
198 | console.log('Then run:');
|
199 | }
|
200 | console.log();
|
201 | console.log(' ' + chalk.cyan(useYarn ? 'yarn' : 'npm') + ' run deploy');
|
202 | console.log();
|
203 | } else if (publicPath !== '/') {
|
204 |
|
205 | console.log('The project was built assuming it is hosted at ' + chalk.green(publicPath) + '.');
|
206 | console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.');
|
207 | console.log();
|
208 | console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
|
209 | console.log();
|
210 | } else {
|
211 | if (publicUrl) {
|
212 |
|
213 | console.log('The project was built assuming it is hosted at ' + chalk.green(publicUrl) + '.');
|
214 | console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.');
|
215 | console.log();
|
216 | } else {
|
217 |
|
218 | console.log('The project was built assuming it is hosted at the server root.');
|
219 | console.log('To override this, specify the ' + chalk.green('homepage') + ' in your ' + chalk.cyan('package.json') + '.');
|
220 | console.log('For example, add this to build it for GitHub Pages:')
|
221 | console.log();
|
222 | console.log(' ' + chalk.green('"homepage"') + chalk.cyan(': ') + chalk.green('"http://myname.github.io/myapp"') + chalk.cyan(','));
|
223 | console.log();
|
224 | }
|
225 | console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
|
226 | console.log('You may also serve it locally with a static server:')
|
227 | console.log();
|
228 | if (useYarn) {
|
229 | console.log(' ' + chalk.cyan('yarn') + ' global add pushstate-server');
|
230 | } else {
|
231 | console.log(' ' + chalk.cyan('npm') + ' install -g pushstate-server');
|
232 | }
|
233 | console.log(' ' + chalk.cyan('pushstate-server') + ' build');
|
234 | console.log(' ' + chalk.cyan(openCommand) + ' http://localhost:9000');
|
235 | console.log();
|
236 | }
|
237 | });
|
238 | }
|
239 |
|
240 | function copyPublicFolder() {
|
241 | fs.copySync(paths.appPublic, paths.appBuild, {
|
242 | dereference: true,
|
243 | filter: file => file !== paths.appHtml
|
244 | });
|
245 | }
|