' +
+ 'This error occurred during the build time and cannot be dismissed.
';
});
}
diff --git a/packages/react-error-overlay/src/components/additional.js b/packages/react-error-overlay/src/components/additional.js
index 4853c922c71..080f68f1dbb 100644
--- a/packages/react-error-overlay/src/components/additional.js
+++ b/packages/react-error-overlay/src/components/additional.js
@@ -16,16 +16,14 @@ function updateAdditional(
additionalReference.removeChild(additionalReference.lastChild);
}
- let text = ' ';
if (totalErrors <= 1) {
- additionalReference.appendChild(document.createTextNode(text));
return;
}
- text = `Errors ${currentError} of ${totalErrors}`;
+
const span = document.createElement('span');
- span.appendChild(document.createTextNode(text));
const group = document.createElement('span');
applyStyles(group, groupStyle);
+
const left = document.createElement('button');
applyStyles(left, groupElemLeft);
left.addEventListener('click', function(e: MouseEvent) {
@@ -34,6 +32,7 @@ function updateAdditional(
});
left.appendChild(document.createTextNode('←'));
enableTabClick(left);
+
const right = document.createElement('button');
applyStyles(right, groupElemRight);
right.addEventListener('click', function(e: MouseEvent) {
@@ -42,9 +41,14 @@ function updateAdditional(
});
right.appendChild(document.createTextNode('→'));
enableTabClick(right);
+
group.appendChild(left);
group.appendChild(right);
span.appendChild(group);
+
+ const text = `${currentError} of ${totalErrors} errors on the page`;
+ span.appendChild(document.createTextNode(text));
+
additionalReference.appendChild(span);
}
diff --git a/packages/react-error-overlay/src/components/close.js b/packages/react-error-overlay/src/components/close.js
index e868cc0d4ca..bba65131bd4 100644
--- a/packages/react-error-overlay/src/components/close.js
+++ b/packages/react-error-overlay/src/components/close.js
@@ -2,9 +2,10 @@
import { applyStyles } from '../utils/dom/css';
import { hintsStyle, hintStyle, closeButtonStyle } from '../styles';
-function createHint(document: Document, hint: string) {
+function createHint(document: Document, hint: string, title: string) {
const span = document.createElement('span');
span.appendChild(document.createTextNode(hint));
+ span.setAttribute('title', title);
applyStyles(span, hintStyle);
return span;
}
@@ -14,7 +15,7 @@ function createClose(document: Document, callback: CloseCallback) {
const hints = document.createElement('div');
applyStyles(hints, hintsStyle);
- const close = createHint(document, '×');
+ const close = createHint(document, '×', 'Click or press Escape to dismiss.');
close.addEventListener('click', () => callback());
applyStyles(close, closeButtonStyle);
hints.appendChild(close);
diff --git a/packages/react-error-overlay/src/components/footer.js b/packages/react-error-overlay/src/components/footer.js
index 9ddfaf57c7e..bfedeef0d44 100644
--- a/packages/react-error-overlay/src/components/footer.js
+++ b/packages/react-error-overlay/src/components/footer.js
@@ -7,7 +7,7 @@ function createFooter(document: Document) {
applyStyles(div, footerStyle);
div.appendChild(
document.createTextNode(
- 'This screen is visible only in development. It will not appear when the app crashes in production.'
+ 'This screen is visible only in development. It will not appear if the app crashes in production.'
)
);
div.appendChild(document.createElement('br'));
diff --git a/packages/react-error-overlay/src/components/frame.js b/packages/react-error-overlay/src/components/frame.js
index 70465a2eda0..a9ea0ea37da 100644
--- a/packages/react-error-overlay/src/components/frame.js
+++ b/packages/react-error-overlay/src/components/frame.js
@@ -120,6 +120,7 @@ function frameDiv(
if (typeof onSourceClick === 'function') {
let handler = onSourceClick;
+ enableTabClick(frameAnchor);
frameAnchor.style.cursor = 'pointer';
frameAnchor.addEventListener('click', function() {
handler();
diff --git a/packages/react-error-overlay/src/components/overlay.js b/packages/react-error-overlay/src/components/overlay.js
index 8633524be13..1626fcdee57 100644
--- a/packages/react-error-overlay/src/components/overlay.js
+++ b/packages/react-error-overlay/src/components/overlay.js
@@ -1,6 +1,11 @@
/* @flow */
import { applyStyles } from '../utils/dom/css';
-import { overlayStyle, headerStyle, additionalStyle } from '../styles';
+import {
+ containerStyle,
+ overlayStyle,
+ headerStyle,
+ additionalStyle,
+} from '../styles';
import { createClose } from './close';
import { createFrames } from './frames';
import { createFooter } from './footer';
@@ -28,24 +33,12 @@ function createOverlay(
// Create overlay
const overlay = document.createElement('div');
applyStyles(overlay, overlayStyle);
- overlay.appendChild(createClose(document, closeCallback));
// Create container
const container = document.createElement('div');
- container.className = 'cra-container';
+ applyStyles(container, containerStyle);
overlay.appendChild(container);
-
- // Create additional
- const additional = document.createElement('div');
- applyStyles(additional, additionalStyle);
- container.appendChild(additional);
- updateAdditional(
- document,
- additional,
- currentError,
- totalErrors,
- switchCallback
- );
+ container.appendChild(createClose(document, closeCallback));
// Create header
const header = document.createElement('div');
@@ -71,6 +64,18 @@ function createOverlay(
header.appendChild(document.createTextNode(finalMessage));
container.appendChild(header);
+ // Create "Errors X of Y" in case of multiple errors
+ const additional = document.createElement('div');
+ applyStyles(additional, additionalStyle);
+ updateAdditional(
+ document,
+ additional,
+ currentError,
+ totalErrors,
+ switchCallback
+ );
+ container.appendChild(additional);
+
// Create trace
container.appendChild(
createFrames(document, frames, frameSettings, contextSize, name)
diff --git a/packages/react-error-overlay/src/overlay.js b/packages/react-error-overlay/src/overlay.js
index fe29a6c7b36..cfcd1182911 100644
--- a/packages/react-error-overlay/src/overlay.js
+++ b/packages/react-error-overlay/src/overlay.js
@@ -35,7 +35,7 @@ import type { ErrorRecordReference } from './utils/errorRegister';
import type { StackFrame } from './utils/stack-frame';
import { iframeStyle } from './styles';
-import { injectCss, applyStyles } from './utils/dom/css';
+import { applyStyles } from './utils/dom/css';
import { createOverlay } from './components/overlay';
import { updateAdditional } from './components/additional';
@@ -45,33 +45,6 @@ let additionalReference = null;
let errorReferences: ErrorRecordReference[] = [];
let currReferenceIndex: number = -1;
-const css = [
- '.cra-container {',
- ' padding-right: 15px;',
- ' padding-left: 15px;',
- ' margin-right: auto;',
- ' margin-left: auto;',
- '}',
- '',
- '@media (min-width: 768px) {',
- ' .cra-container {',
- ' width: calc(750px - 6em);',
- ' }',
- '}',
- '',
- '@media (min-width: 992px) {',
- ' .cra-container {',
- ' width: calc(970px - 6em);',
- ' }',
- '}',
- '',
- '@media (min-width: 1200px) {',
- ' .cra-container {',
- ' width: calc(1170px - 6em);',
- ' }',
- '}',
-].join('\n');
-
function render(name: ?string, message: string, resolvedFrames: StackFrame[]) {
disposeCurrentView();
@@ -105,9 +78,13 @@ function render(name: ?string, message: string, resolvedFrames: StackFrame[]) {
keyEventHandler(type => shortcutHandler(type), event);
};
}
- injectCss(iframeReference.contentDocument, css);
if (document.body != null) {
- document.body.appendChild(overlay);
+ document.body.style.margin = '0';
+ // Keep popup within body boundaries for iOS Safari
+ // $FlowFixMe
+ document.body.style['max-width'] = '100vw';
+
+ (document.body: any).appendChild(overlay);
}
additionalReference = additional;
};
diff --git a/packages/react-error-overlay/src/styles.js b/packages/react-error-overlay/src/styles.js
index 855778cbd13..8e6f0d07bc5 100644
--- a/packages/react-error-overlay/src/styles.js
+++ b/packages/react-error-overlay/src/styles.js
@@ -1,43 +1,50 @@
/* @flow */
const black = '#293238',
darkGray = '#878e91',
- lightGray = '#fafafa',
red = '#ce1126',
lightRed = '#fccfcf',
- yellow = '#fbf5b4';
+ yellow = '#fbf5b4',
+ white = '#ffffff';
const iframeStyle = {
- 'background-color': lightGray,
position: 'fixed',
- top: '1em',
- left: '1em',
- bottom: '1em',
- right: '1em',
- width: 'calc(100% - 2em)',
- height: 'calc(100% - 2em)',
+ top: '0',
+ left: '0',
+ width: '100%',
+ height: '100%',
border: 'none',
- 'border-radius': '3px',
- 'box-shadow': '0 0 6px 0 rgba(0, 0, 0, 0.5)',
'z-index': 1337,
};
const overlayStyle = {
+ width: '100%',
+ height: '100%',
'box-sizing': 'border-box',
- padding: '4rem',
+ 'text-align': 'center',
+ 'background-color': white,
+};
+
+const containerStyle = {
+ position: 'relative',
+ display: 'inline-flex',
+ 'flex-direction': 'column',
+ height: '100%',
+ width: '1024px',
+ 'max-width': '100%',
+ 'overflow-x': 'hidden',
+ 'overflow-y': 'auto',
+ padding: '0.5rem',
+ 'box-sizing': 'border-box',
+ 'text-align': 'start',
'font-family': 'Consolas, Menlo, monospace',
- color: black,
+ 'font-size': '11px',
'white-space': 'pre-wrap',
- overflow: 'auto',
- 'overflow-x': 'hidden',
'word-break': 'break-word',
'line-height': 1.5,
+ color: black,
};
const hintsStyle = {
- 'font-size': '0.8em',
- 'margin-top': '-3em',
- 'margin-bottom': '3em',
- 'text-align': 'right',
color: darkGray,
};
@@ -47,34 +54,36 @@ const hintStyle = {
};
const closeButtonStyle = {
- 'font-size': '26px',
color: black,
- padding: '0.5em 1em',
+ 'line-height': '1rem',
+ 'font-size': '1.5rem',
+ padding: '1rem',
cursor: 'pointer',
position: 'absolute',
right: 0,
top: 0,
};
-const additionalStyle = {
- 'margin-bottom': '1.5em',
- 'margin-top': '-4em',
-};
+const additionalStyle = {};
const headerStyle = {
- 'font-size': '1.7em',
- 'font-weight': 'bold',
+ 'font-size': '2em',
+ 'font-family': 'sans-serif',
color: red,
'white-space': 'pre-wrap',
+ margin: '0.75rem 2rem 0 0', // Prevent overlap with close button
+ flex: '0 0 auto',
+ 'max-height': '35%',
+ overflow: 'auto',
};
const functionNameStyle = {
'margin-top': '1em',
- 'font-size': '1.2em',
};
const linkStyle = {
'font-size': '0.9em',
+ 'margin-bottom': '0.9em',
};
const anchorStyle = {
@@ -84,11 +93,12 @@ const anchorStyle = {
const traceStyle = {
'font-size': '1em',
+ flex: '0 1 auto',
+ 'min-height': '0px',
+ overflow: 'auto',
};
-const depStyle = {
- 'font-size': '1.2em',
-};
+const depStyle = {};
const primaryErrorStyle = {
'background-color': lightRed,
@@ -100,19 +110,18 @@ const secondaryErrorStyle = {
const omittedFramesStyle = {
color: black,
- 'font-size': '0.9em',
- margin: '1.5em 0',
cursor: 'pointer',
};
const preStyle = {
display: 'block',
padding: '0.5em',
- 'margin-top': '1.5em',
- 'margin-bottom': '0px',
+ 'margin-top': '0.5em',
+ 'margin-bottom': '0.5em',
'overflow-x': 'auto',
- 'font-size': '1.1em',
- 'white-space': 'pre',
+ 'white-space': 'pre-wrap',
+ 'border-radius': '0.25rem',
+ 'background-color': 'rgba(206, 17, 38, .05)',
};
const toggleStyle = {
@@ -130,7 +139,7 @@ const hiddenStyle = {
};
const groupStyle = {
- 'margin-left': '1em',
+ 'margin-right': '1em',
};
const _groupElemStyle = {
@@ -152,15 +161,18 @@ const groupElemLeft = Object.assign({}, _groupElemStyle, {
const groupElemRight = Object.assign({}, _groupElemStyle, {
'border-top-left-radius': '0px',
'border-bottom-left-radius': '0px',
- 'margin-left': '-1px',
+ 'margin-right': '-1px',
});
const footerStyle = {
- 'text-align': 'center',
+ 'font-family': 'sans-serif',
color: darkGray,
+ 'margin-top': '0.5rem',
+ flex: '0 0 auto',
};
export {
+ containerStyle,
iframeStyle,
overlayStyle,
hintsStyle,
diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js
index 98e57ea9e5b..540d54abf5c 100644
--- a/packages/react-scripts/config/webpack.config.dev.js
+++ b/packages/react-scripts/config/webpack.config.dev.js
@@ -18,6 +18,7 @@ const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter');
+const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getClientEnvironment = require('./env');
const paths = require('./paths');
@@ -106,6 +107,14 @@ module.exports = {
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
},
+ plugins: [
+ // Prevents users from importing files from outside of src/ (or node_modules/).
+ // This often causes confusion because we only process files within src/ with babel.
+ // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
+ // please link the files into your node_modules/ and let module-resolution kick in.
+ // Make sure your source files are compiled, as they will not be processed in any way.
+ new ModuleScopePlugin(paths.appSrc),
+ ],
},
module: {
strictExportPresence: true,
@@ -137,10 +146,10 @@ module.exports = {
include: paths.appSrc,
},
// ** ADDING/UPDATING LOADERS **
- // The "url" loader handles all assets unless explicitly excluded.
+ // The "file" loader handles all assets unless explicitly excluded.
// The `exclude` list *must* be updated with every change to loader extensions.
// When adding a new loader, you must add its `test`
- // as a new entry in the `exclude` list for "url" loader.
+ // as a new entry in the `exclude` list for "file" loader.
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
@@ -224,7 +233,7 @@ module.exports = {
],
},
// ** STOP ** Are you adding a new loader?
- // Remember to add the new extension(s) to the "url" loader exclusion list.
+ // Remember to add the new extension(s) to the "file" loader exclusion list.
],
},
plugins: [
@@ -252,6 +261,12 @@ module.exports = {
// makes the discovery automatic so you don't have to restart.
// See https://github.com/facebookincubator/create-react-app/issues/186
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
+ // Moment.js is an extremely popular library that bundles large locale files
+ // by default due to how Webpack interprets its code. This is a practical
+ // solution that requires the user to opt into importing specific locales.
+ // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
+ // You can remove this if you don't use Moment.js:
+ new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js
index 7615894a755..ac672a4099a 100644
--- a/packages/react-scripts/config/webpack.config.prod.js
+++ b/packages/react-scripts/config/webpack.config.prod.js
@@ -17,7 +17,9 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
+const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter');
+const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const paths = require('./paths');
const getClientEnvironment = require('./env');
@@ -103,6 +105,14 @@ module.exports = {
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
},
+ plugins: [
+ // Prevents users from importing files from outside of src/ (or node_modules/).
+ // This often causes confusion because we only process files within src/ with babel.
+ // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
+ // please link the files into your node_modules/ and let module-resolution kick in.
+ // Make sure your source files are compiled, as they will not be processed in any way.
+ new ModuleScopePlugin(paths.appSrc),
+ ],
},
module: {
strictExportPresence: true,
@@ -136,10 +146,10 @@ module.exports = {
include: paths.appSrc,
},
// ** ADDING/UPDATING LOADERS **
- // The "url" loader handles all assets unless explicitly excluded.
+ // The "file" loader handles all assets unless explicitly excluded.
// The `exclude` list *must* be updated with every change to loader extensions.
// When adding a new loader, you must add its `test`
- // as a new entry in the `exclude` list in the "url" loader.
+ // as a new entry in the `exclude` list in the "file" loader.
// "file" loader makes sure those assets end up in the `build` folder.
// When you `import` an asset, you get its filename.
@@ -234,7 +244,7 @@ module.exports = {
// Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
},
// ** STOP ** Are you adding a new loader?
- // Remember to add the new extension(s) to the "url" loader exclusion list.
+ // Remember to add the new extension(s) to the "file" loader exclusion list.
],
},
plugins: [
@@ -270,6 +280,10 @@ module.exports = {
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
+ // This feature has been reported as buggy a few times, such as:
+ // https://github.com/mishoo/UglifyJS2/issues/1964
+ // We'll wait with enabling it by default until it is more solid.
+ reduce_vars: false,
},
output: {
comments: false,
@@ -286,6 +300,32 @@ module.exports = {
new ManifestPlugin({
fileName: 'asset-manifest.json',
}),
+ // Generate a service worker script that will precache, and keep up to date,
+ // the HTML & assets that are part of the Webpack build.
+ new SWPrecacheWebpackPlugin({
+ // By default, a cache-busting query parameter is appended to requests
+ // used to populate the caches, to ensure the responses are fresh.
+ // If a URL is already hashed by Webpack, then there is no concern
+ // about it being stale, and the cache-busting can be skipped.
+ dontCacheBustUrlsMatching: /\.\w{8}\./,
+ filename: 'service-worker.js',
+ logger(message) {
+ if (message.indexOf('Total precache size is') === 0) {
+ // This message occurs for every build and is a bit too noisy.
+ return;
+ }
+ console.log(message);
+ },
+ minify: true,
+ navigateFallback: publicUrl + '/index.html',
+ staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
+ }),
+ // Moment.js is an extremely popular library that bundles large locale files
+ // by default due to how Webpack interprets its code. This is a practical
+ // solution that requires the user to opt into importing specific locales.
+ // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
+ // You can remove this if you don't use Moment.js:
+ new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json
index eaab334d4c3..cdc70febc60 100644
--- a/packages/react-scripts/package.json
+++ b/packages/react-scripts/package.json
@@ -26,7 +26,7 @@
"autoprefixer": "7.1.0",
"babel-core": "6.24.1",
"babel-eslint": "7.2.3",
- "babel-jest": "20.0.1",
+ "babel-jest": "20.0.3",
"babel-loader": "7.0.0",
"babel-preset-react-app": "^3.0.0",
"babel-runtime": "6.23.0",
@@ -48,7 +48,7 @@
"fs-extra": "3.0.1",
"html-webpack-plugin": "2.28.0",
"inquirer": "3.0.6",
- "jest": "20.0.1",
+ "jest": "20.0.3",
"object-assign": "4.1.1",
"postcss-flexbugs-fixes": "3.0.0",
"postcss-loader": "2.0.5",
@@ -56,6 +56,7 @@
"react-dev-utils": "^1.0.0",
"react-error-overlay": "^1.0.0",
"style-loader": "0.17.0",
+ "sw-precache-webpack-plugin": "0.9.1",
"url-loader": "0.5.8",
"webpack": "2.5.1",
"webpack-dev-server": "2.4.5",
diff --git a/packages/react-scripts/scripts/build.js b/packages/react-scripts/scripts/build.js
index da9004f9ee6..8a4149370d2 100644
--- a/packages/react-scripts/scripts/build.js
+++ b/packages/react-scripts/scripts/build.js
@@ -23,14 +23,15 @@ process.on('unhandledRejection', err => {
// Ensure environment variables are read.
require('../config/env');
+const path = require('path');
const chalk = require('chalk');
const fs = require('fs-extra');
-const path = require('path');
-const url = require('url');
const webpack = require('webpack');
const config = require('../config/webpack.config.prod');
const paths = require('../config/paths');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
+const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
+const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
const measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild;
@@ -44,159 +45,74 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
// First, read the current file sizes in build directory.
// This lets us display how much they changed later.
-measureFileSizesBeforeBuild(paths.appBuild).then(previousFileSizes => {
- // Remove all content but keep the directory so that
- // if you're in it, you don't end up in Trash
- fs.emptyDirSync(paths.appBuild);
-
- // Start the webpack build
- build(previousFileSizes);
-
- // Merge with the public folder
- copyPublicFolder();
-});
-
-// Print out errors
-function printErrors(summary, errors) {
- console.log(chalk.red(summary));
- console.log();
- errors.forEach(err => {
- console.log(err.message || err);
- console.log();
- });
-}
-
-// Create the production build and print the deployment instructions.
-function build(previousFileSizes) {
- console.log('Creating an optimized production build...');
-
- let compiler;
- try {
- compiler = webpack(config);
- } catch (err) {
- printErrors('Failed to compile.', [err]);
- process.exit(1);
- }
-
- compiler.run((err, stats) => {
- if (err) {
- printErrors('Failed to compile.', [err]);
- process.exit(1);
- }
+measureFileSizesBeforeBuild(paths.appBuild)
+ .then(previousFileSizes => {
+ // Remove all content but keep the directory so that
+ // if you're in it, you don't end up in Trash
+ fs.emptyDirSync(paths.appBuild);
+ // Merge with the public folder
+ copyPublicFolder();
+ // Start the webpack build
+ return build(previousFileSizes);
+ })
+ .then(
+ ({ stats, previousFileSizes }) => {
+ console.log(chalk.green('Compiled successfully.'));
+ console.log();
- if (stats.compilation.errors.length) {
- printErrors('Failed to compile.', stats.compilation.errors);
- process.exit(1);
- }
+ console.log('File sizes after gzip:');
+ console.log();
+ printFileSizesAfterBuild(stats, previousFileSizes);
+ console.log();
- if (process.env.CI && stats.compilation.warnings.length) {
- printErrors(
- 'Failed to compile. When process.env.CI = true, warnings are treated as failures. Most CI servers set this automatically.',
- stats.compilation.warnings
+ const appPackage = require(paths.appPackageJson);
+ const publicUrl = paths.publicUrl;
+ const publicPath = config.output.publicPath;
+ const buildFolder = path.relative(process.cwd(), paths.appBuild);
+ printHostingInstructions(
+ appPackage,
+ publicUrl,
+ publicPath,
+ buildFolder,
+ useYarn
);
+ },
+ err => {
+ console.log(chalk.red('Failed to compile.'));
+ console.log();
+ console.log(err.message || err);
+ console.log();
process.exit(1);
}
+ );
- console.log(chalk.green('Compiled successfully.'));
- console.log();
-
- console.log('File sizes after gzip:');
- console.log();
- printFileSizesAfterBuild(stats, previousFileSizes);
- console.log();
+// Create the production build and print the deployment instructions.
+function build(previousFileSizes) {
+ console.log('Creating an optimized production build...');
- const appPackage = require(paths.appPackageJson);
- const publicUrl = paths.publicUrl;
- const publicPath = config.output.publicPath;
- const publicPathname = url.parse(publicPath).pathname;
- if (publicUrl && publicUrl.indexOf('.github.io/') !== -1) {
- // "homepage": "http://user.github.io/project"
- console.log(
- `The project was built assuming it is hosted at ${chalk.green(publicPathname)}.`
- );
- 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(publicUrl)}, run:`);
- // If script deploy has been added to package.json, skip the instructions
- if (typeof appPackage.scripts.deploy === 'undefined') {
- console.log();
- if (useYarn) {
- console.log(` ${chalk.cyan('yarn')} add --dev gh-pages`);
- } else {
- console.log(` ${chalk.cyan('npm')} install --save-dev gh-pages`);
- }
- 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('// ...')}`);
- console.log(
- ` ${chalk.yellow('"predeploy"')}: ${chalk.yellow('"npm run build",')}`
- );
- console.log(
- ` ${chalk.yellow('"deploy"')}: ${chalk.yellow('"gh-pages -d build"')}`
- );
- console.log(' }');
- console.log();
- console.log('Then run:');
+ let compiler = webpack(config);
+ return new Promise((resolve, reject) => {
+ compiler.run((err, stats) => {
+ if (err) {
+ return reject(err);
}
- console.log();
- console.log(` ${chalk.cyan(useYarn ? 'yarn' : 'npm')} run deploy`);
- console.log();
- } 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')}.`
- );
- console.log();
- console.log(`The ${chalk.cyan('build')} folder is ready to be deployed.`);
- console.log();
- } else {
- if (publicUrl) {
- // "homepage": "http://mywebsite.com"
- console.log(
- `The project was built assuming it is hosted at ${chalk.green(publicUrl)}.`
- );
- console.log(
- `You can control this with the ${chalk.green('homepage')} field in your ${chalk.cyan('package.json')}.`
- );
- console.log();
- } else {
- // no homepage
- console.log(
- 'The project was built assuming it is hosted at the server root.'
- );
- 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:');
+ const messages = formatWebpackMessages(stats.toJson({}, true));
+ if (messages.errors.length) {
+ return reject(new Error(messages.errors.join('\n\n')));
+ }
+ if (process.env.CI && messages.warnings.length) {
console.log();
console.log(
- ` ${chalk.green('"homepage"')} ${chalk.cyan(':')} ${chalk.green('"http://myname.github.io/myapp"')}${chalk.cyan(',')}`
+ chalk.yellow(
+ 'Treating warnings as errors because process.env.CI = true.\n' +
+ 'Most CI servers set it automatically.'
+ )
);
console.log();
+ return reject(new Error(messages.warnings.join('\n\n')));
}
- const build = path.relative(process.cwd(), paths.appBuild);
- console.log(`The ${chalk.cyan(build)} folder is ready to be deployed.`);
- console.log('You may serve it with a static server:');
- console.log();
- if (useYarn) {
- console.log(` ${chalk.cyan('yarn')} global add serve`);
- } else {
- console.log(` ${chalk.cyan('npm')} install -g serve`);
- }
- console.log(` ${chalk.cyan('serve')} -s build`);
- console.log();
- }
+ return resolve({ stats, previousFileSizes });
+ });
});
}
diff --git a/packages/react-scripts/template/README.md b/packages/react-scripts/template/README.md
index c178e573816..50bbf7b694a 100644
--- a/packages/react-scripts/template/README.md
+++ b/packages/react-scripts/template/README.md
@@ -61,6 +61,10 @@ You can find the most recent version of this guide [here](https://github.com/fac
- [Editor Integration](#editor-integration)
- [Developing Components in Isolation](#developing-components-in-isolation)
- [Making a Progressive Web App](#making-a-progressive-web-app)
+
- [Deployment](#deployment)
- [Static Server](#static-server)
- [Other Solutions](#other-solutions)
@@ -81,6 +85,7 @@ You can find the most recent version of this guide [here](https://github.com/fac
- [`npm test` hangs on macOS Sierra](#npm-test-hangs-on-macos-sierra)
- [`npm run build` silently fails](#npm-run-build-silently-fails)
- [`npm run build` fails on Heroku](#npm-run-build-fails-on-heroku)
+ - [Moment.js locales are missing](#momentjs-locales-are-missing)
- [Something Missing?](#something-missing)
## Updating to New Releases
@@ -1215,6 +1220,103 @@ Learn more about React Storybook:
You can turn your React app into a [Progressive Web App](https://developers.google.com/web/progressive-web-apps/) by following the steps in [this repository](https://github.com/jeffposnick/create-react-pwa).
+
+
## Deployment
`npm run build` creates a `build` directory with a production build of your app. Set up your favourite HTTP server so that a visitor to your site is served `index.html`, and requests to static paths like `/static/js/main..js` are served with the contents of the `/static/js/main..js` file.
@@ -1290,6 +1392,19 @@ It will get copied to the `build` folder when you run `npm run build`.
Now requests to `/todos/42` will be handled correctly both in development and in production.
+
+
### Building for Relative Paths
By default, Create React App produces a build assuming your app is hosted at the server root.
@@ -1616,6 +1731,32 @@ It is reported that `npm run build` can fail on machines with no swap space, whi
This may be a problem with case sensitive filenames.
Please refer to [this section](#resolving-heroku-deployment-errors).
+### Moment.js locales are missing
+
+If you use a [Moment.js](https://momentjs.com/), you might notice that only the English locale is available by default. This is because the locale files are large, and you probably only need a subset of [all the locales provided by Moment.js](https://momentjs.com/#multiple-locale-support).
+
+To add a specific Moment.js locale to your bundle, you need to import it explicitly.
+For example:
+
+```js
+import moment from 'moment';
+import 'moment/locale/fr';
+```
+
+If import multiple locales this way, you can later switch between them by calling `moment.locale()` with the locale name:
+
+```js
+import moment from 'moment';
+import 'moment/locale/fr';
+import 'moment/locale/es';
+
+// ...
+
+moment.locale('fr');
+```
+
+This will only work for locales that have been explicitly imported before.
+
## Something Missing?
If you have ideas for more “How To” recipes that should be on this page, [let us know](https://github.com/facebookincubator/create-react-app/issues) or [contribute some!](https://github.com/facebookincubator/create-react-app/edit/master/packages/react-scripts/template/README.md)
diff --git a/packages/react-scripts/template/public/index.html b/packages/react-scripts/template/public/index.html
index 7f3e83f4e43..fc8689a2a29 100644
--- a/packages/react-scripts/template/public/index.html
+++ b/packages/react-scripts/template/public/index.html
@@ -3,9 +3,15 @@
+
+
+