From 4aee1f63f65710eff09e26fde78becf8cb1900c9 Mon Sep 17 00:00:00 2001 From: Jeremy Gayed Date: Wed, 3 Aug 2016 23:43:58 -0400 Subject: [PATCH 01/20] Add check-filename-webpack-plugin to warn on JSX file extension --- config/webpack.config.dev.js | 7 +++++++ package.json | 1 + template/README.md | 2 ++ 3 files changed, 10 insertions(+) diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index 89ada953ea..830ae1a8c2 100644 --- a/config/webpack.config.dev.js +++ b/config/webpack.config.dev.js @@ -12,6 +12,7 @@ var autoprefixer = require('autoprefixer'); var webpack = require('webpack'); var HtmlWebpackPlugin = require('html-webpack-plugin'); var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); +var CheckFilenamePlugin = require('check-filename-webpack-plugin'); var WatchMissingNodeModulesPlugin = require('../scripts/utils/WatchMissingNodeModulesPlugin'); var paths = require('./paths'); var env = require('./env'); @@ -110,6 +111,12 @@ module.exports = { // Note: only CSS is currently hot reloaded new webpack.HotModuleReplacementPlugin(), new CaseSensitivePathsPlugin(), + new CheckFilenamePlugin({ + regex: /\.jsx$/, + error: function(filename) { + return 'Module load aborted: .jsx extensions are not allowed, use .js extensions only. See create-react-app/issues/290 for more info.\n\tFor: ' + filename; + } + }), new WatchMissingNodeModulesPlugin(paths.appNodeModules) ] }; diff --git a/package.json b/package.json index 15eaec5237..f2fdd45791 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "babel-runtime": "6.11.6", "case-sensitive-paths-webpack-plugin": "1.1.2", "chalk": "1.1.3", + "check-filename-webpack-plugin": "^1.0.0", "cross-spawn": "4.0.0", "css-loader": "0.23.1", "detect-port": "1.0.0", diff --git a/template/README.md b/template/README.md index 7cb4c5c1ed..faf2ccd371 100644 --- a/template/README.md +++ b/template/README.md @@ -59,6 +59,8 @@ You need to **put any JS and CSS files inside `src`**, or Webpack won’t see th You can, however, create more top-level directories. They will not be included in the production build so you can use them for things like documentation. +> NOTE: Only use `.js` file extensions for all JavaScript files inside `src`, including any React components. `.jsx` is explicitly not allowed, and an error will be thrown during compilation. See [create-react-app/issues/290](https://github.com/facebookincubator/create-react-app/issues/290) for more info. + ## Available Scripts In the project directory, you can run: From cfc4325e4c562fec974742fcb1afe9e930a2f026 Mon Sep 17 00:00:00 2001 From: Elijah Manor Date: Wed, 3 Aug 2016 01:01:58 -0500 Subject: [PATCH 02/20] Display Build Size Difference --- package.json | 1 + scripts/build.js | 164 +++++++++++++++++++++++++++++------------------ 2 files changed, 101 insertions(+), 64 deletions(-) diff --git a/package.json b/package.json index 15eaec5237..e80008d845 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "opn": "4.0.2", "postcss-loader": "0.9.1", "promise": "7.1.1", + "recursive-readdir": "^2.0.0", "rimraf": "2.5.4", "style-loader": "0.13.1", "url-loader": "0.5.7", diff --git a/scripts/build.js b/scripts/build.js index e57e040bd6..11421deaa7 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -18,79 +18,115 @@ var rimrafSync = require('rimraf').sync; var webpack = require('webpack'); var config = require('../config/webpack.config.prod'); var paths = require('../config/paths'); +var recursive = require('recursive-readdir'); -// Remove all content but keep the directory so that -// if you're in it, you don't end up in Trash -rimrafSync(paths.appBuild + '/*'); +function removeFileNameHash(fileName) { + return fileName.replace(paths.appBuild, '') + .replace(/\/?(.*)(\.\w+)(\.js|\.css)/, function(match, p1, p2, p3) { + return p1 + p3; + }); +} -console.log('Creating an optimized production build...'); -webpack(config).run(function(err, stats) { - if (err) { - console.error('Failed to create a production build. Reason:'); - console.error(err.message || err); - process.exit(1); +function sizeDifference(currentSize, previousSize) { + if (previousSize === undefined) { return ''; } + var difference = currentSize - previousSize; + var fileSize = filesize(difference); + if (difference > 0) { + return chalk.red('+' + fileSize); + } else if (difference <= 0){ + return chalk.green((difference === 0 ? '+' : '') + fileSize); } +} - console.log(chalk.green('Compiled successfully.')); - console.log(); +recursive(paths.appBuild, function (err, fileNames) { + fileNames = fileNames || []; + var previousSizeMap = fileNames.filter(fileName => /\.(js|css)$/.test(fileName)) + .reduce((memo, fileName) => { + var contents = fs.readFileSync(fileName); + var key = removeFileNameHash(fileName); + memo[key] = gzipSize(contents); + return memo; + }, {} ); - console.log('File sizes after gzip:'); - console.log(); - 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); - return { - folder: path.join('build', path.dirname(asset.name)), - name: path.basename(asset.name), - size: size, - sizeLabel: filesize(size) - }; - }); - assets.sort((a, b) => b.size - a.size); + // Remove all content but keep the directory so that + // if you're in it, you don't end up in Trash + rimrafSync(paths.appBuild + '/*'); + + build(previousSizeMap); +}); - var longestSizeLabelLength = Math.max.apply(null, - assets.map(a => a.sizeLabel.length) - ); - assets.forEach(asset => { - var sizeLabel = asset.sizeLabel; - if (sizeLabel.length < longestSizeLabelLength) { - var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLabel.length); - sizeLabel += rightPadding; +function build(previousSizeMap) { + console.log('Creating an optimized production build...'); + webpack(config).run(function(err, stats) { + if (err) { + console.error('Failed to create a production build. Reason:'); + console.error(err.message || err); + process.exit(1); } - console.log( - ' ' + chalk.green(sizeLabel) + - ' ' + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name) - ); - }); - console.log(); - var openCommand = process.platform === 'win32' ? 'start' : 'open'; - var homepagePath = require(paths.appPackageJson).homepage; - if (homepagePath) { - console.log('You can now publish them at ' + homepagePath + '.'); - console.log('For example, if you use GitHub Pages:'); + console.log(chalk.green('Compiled successfully.')); console.log(); - console.log(' git commit -am "Save local changes"'); - console.log(' git checkout -B gh-pages'); - console.log(' git add -f build'); - console.log(' git commit -am "Rebuild website"'); - console.log(' git filter-branch -f --prune-empty --subdirectory-filter build'); - console.log(' git push -f origin gh-pages'); - console.log(' git checkout -'); + + console.log('File sizes after gzip:'); console.log(); - } else { - console.log('You can now serve them with any static server.'); - console.log('For example:'); + 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 = sizeDifference(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 => a.sizeLabel.length) + ); + assets.forEach(asset => { + var sizeLabel = asset.sizeLabel; + if (sizeLabel.length < longestSizeLabelLength) { + var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLabel.length); + sizeLabel += rightPadding; + } + console.log( + ' ' + chalk.yellow(sizeLabel) + + ' ' + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name) + ); + }); console.log(); - console.log(' npm install -g pushstate-server'); - console.log(' pushstate-server build'); - console.log(' ' + openCommand + ' http://localhost:9000'); + + var openCommand = process.platform === 'win32' ? 'start' : 'open'; + var homepagePath = require(paths.appPackageJson).homepage; + if (homepagePath) { + console.log('You can now publish them at ' + homepagePath + '.'); + console.log('For example, if you use GitHub Pages:'); + console.log(); + console.log(' git commit -am "Save local changes"'); + console.log(' git checkout -B gh-pages'); + console.log(' git add -f build'); + console.log(' git commit -am "Rebuild website"'); + console.log(' git filter-branch -f --prune-empty --subdirectory-filter build'); + console.log(' git push -f origin gh-pages'); + console.log(' git checkout -'); + console.log(); + } else { + console.log('You can now serve them with any static server.'); + console.log('For example:'); + console.log(); + console.log(' npm install -g pushstate-server'); + console.log(' pushstate-server build'); + console.log(' ' + openCommand + ' http://localhost:9000'); + console.log(); + console.log(chalk.dim('The project was built assuming it is hosted at the root.')); + console.log(chalk.dim('Set the "homepage" field in package.json to override this.')); + console.log(chalk.dim('For example, "homepage": "http://user.github.io/project".')); + } console.log(); - console.log(chalk.dim('The project was built assuming it is hosted at the root.')); - console.log(chalk.dim('Set the "homepage" field in package.json to override this.')); - console.log(chalk.dim('For example, "homepage": "http://user.github.io/project".')); - } - console.log(); -}); + }); +} From 6b6d7ee30351d5e4dd7fb4d7157f6bf0011b0125 Mon Sep 17 00:00:00 2001 From: Elijah Manor Date: Thu, 4 Aug 2016 00:57:01 -0500 Subject: [PATCH 03/20] Adjust colors and account for unicode when padding --- package.json | 1 + scripts/build.js | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index e80008d845..0dbaf6a601 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "promise": "7.1.1", "recursive-readdir": "^2.0.0", "rimraf": "2.5.4", + "strip-ansi": "^3.0.1", "style-loader": "0.13.1", "url-loader": "0.5.7", "webpack": "1.13.1", diff --git a/scripts/build.js b/scripts/build.js index 11421deaa7..be1f40e4a4 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -19,6 +19,7 @@ var webpack = require('webpack'); var config = require('../config/webpack.config.prod'); var paths = require('../config/paths'); var recursive = require('recursive-readdir'); +var stripAnsi = require('strip-ansi'); function removeFileNameHash(fileName) { return fileName.replace(paths.appBuild, '') @@ -28,13 +29,17 @@ function removeFileNameHash(fileName) { } function sizeDifference(currentSize, previousSize) { - if (previousSize === undefined) { return ''; } + var FIFTY_KILOBYTES = 1024 * 50; var difference = currentSize - previousSize; - var fileSize = filesize(difference); - if (difference > 0) { + var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0; + if (difference >= FIFTY_KILOBYTES) { return chalk.red('+' + fileSize); - } else if (difference <= 0){ - return chalk.green((difference === 0 ? '+' : '') + fileSize); + } else if (difference < FIFTY_KILOBYTES && difference > 0) { + return chalk.yellow('+' + fileSize); + } else if (difference < 0) { + return chalk.green(fileSize); + } else { + return ''; } } @@ -86,16 +91,17 @@ function build(previousSizeMap) { assets.sort((a, b) => b.size - a.size); var longestSizeLabelLength = Math.max.apply(null, - assets.map(a => a.sizeLabel.length) + assets.map(a => stripAnsi(a.sizeLabel).length) ); assets.forEach(asset => { var sizeLabel = asset.sizeLabel; - if (sizeLabel.length < longestSizeLabelLength) { - var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLabel.length); + var sizeLength = stripAnsi(sizeLabel).length; + if (sizeLength < longestSizeLabelLength) { + var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength); sizeLabel += rightPadding; } console.log( - ' ' + chalk.yellow(sizeLabel) + + ' ' + sizeLabel + ' ' + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name) ); }); From f93e02e62f080e5066941c99be91286fe8f6f96d Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 4 Aug 2016 12:39:48 +0100 Subject: [PATCH 04/20] Minor style tweaks --- scripts/build.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/scripts/build.js b/scripts/build.js index be1f40e4a4..d9efa7691a 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -22,13 +22,12 @@ var recursive = require('recursive-readdir'); var stripAnsi = require('strip-ansi'); function removeFileNameHash(fileName) { - return fileName.replace(paths.appBuild, '') - .replace(/\/?(.*)(\.\w+)(\.js|\.css)/, function(match, p1, p2, p3) { - return p1 + p3; - }); + return fileName + .replace(paths.appBuild, '') + .replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3); } -function sizeDifference(currentSize, previousSize) { +function getDifferentLabel(currentSize, previousSize) { var FIFTY_KILOBYTES = 1024 * 50; var difference = currentSize - previousSize; var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0; @@ -43,15 +42,17 @@ function sizeDifference(currentSize, previousSize) { } } -recursive(paths.appBuild, function (err, fileNames) { +recursive(paths.appBuild, (err, fileNames) => { fileNames = fileNames || []; - var previousSizeMap = fileNames.filter(fileName => /\.(js|css)$/.test(fileName)) + + var previousSizeMap = fileNames + .filter(fileName => /\.(js|css)$/.test(fileName)) .reduce((memo, fileName) => { var contents = fs.readFileSync(fileName); var key = removeFileNameHash(fileName); memo[key] = gzipSize(contents); return memo; - }, {} ); + }, {}); // Remove all content but keep the directory so that // if you're in it, you don't end up in Trash @@ -62,7 +63,7 @@ recursive(paths.appBuild, function (err, fileNames) { function build(previousSizeMap) { console.log('Creating an optimized production build...'); - webpack(config).run(function(err, stats) { + webpack(config).run((err, stats) => { if (err) { console.error('Failed to create a production build. Reason:'); console.error(err.message || err); @@ -80,7 +81,7 @@ function build(previousSizeMap) { var fileContents = fs.readFileSync(paths.appBuild + '/' + asset.name); var size = gzipSize(fileContents); var previousSize = previousSizeMap[removeFileNameHash(asset.name)]; - var difference = sizeDifference(size, previousSize); + var difference = getDifferentLabel(size, previousSize); return { folder: path.join('build', path.dirname(asset.name)), name: path.basename(asset.name), From 4ac60e536fb55a8378544b656933a86f9238d14e Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 4 Aug 2016 12:41:46 +0100 Subject: [PATCH 05/20] Remove carets --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0dbaf6a601..9c0a178a30 100644 --- a/package.json +++ b/package.json @@ -66,9 +66,9 @@ "opn": "4.0.2", "postcss-loader": "0.9.1", "promise": "7.1.1", - "recursive-readdir": "^2.0.0", + "recursive-readdir": "2.0.0", "rimraf": "2.5.4", - "strip-ansi": "^3.0.1", + "strip-ansi": "3.0.1", "style-loader": "0.13.1", "url-loader": "0.5.7", "webpack": "1.13.1", From 670e382ccffd444b416a349b1dfcf3c3a3fab346 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 4 Aug 2016 13:30:03 +0100 Subject: [PATCH 06/20] Minor tweaks to console output --- scripts/build.js | 126 +++++++++++++++++++++++++++-------------------- 1 file changed, 73 insertions(+), 53 deletions(-) diff --git a/scripts/build.js b/scripts/build.js index d9efa7691a..50fd576972 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -27,7 +27,7 @@ function removeFileNameHash(fileName) { .replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3); } -function getDifferentLabel(currentSize, previousSize) { +function getDifferenceLabel(currentSize, previousSize) { var FIFTY_KILOBYTES = 1024 * 50; var difference = currentSize - previousSize; var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0; @@ -42,10 +42,10 @@ function getDifferentLabel(currentSize, previousSize) { } } +// First, read the current file sizes in build directory. +// This lets us display how much they changed later. recursive(paths.appBuild, (err, fileNames) => { - fileNames = fileNames || []; - - var previousSizeMap = fileNames + var previousSizeMap = (fileNames || []) .filter(fileName => /\.(js|css)$/.test(fileName)) .reduce((memo, fileName) => { var contents = fs.readFileSync(fileName); @@ -58,9 +58,44 @@ recursive(paths.appBuild, (err, fileNames) => { // if you're in it, you don't end up in Trash rimrafSync(paths.appBuild + '/*'); + // Start the webpack build build(previousSizeMap); }); +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) + ); + }); +} + function build(previousSizeMap) { console.log('Creating an optimized production build...'); webpack(config).run((err, stats) => { @@ -75,64 +110,49 @@ function build(previousSizeMap) { console.log('File sizes after gzip:'); console.log(); - 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 = getDifferentLabel(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) - ); - }); + printFileSizes(stats, previousSizeMap); console.log(); var openCommand = process.platform === 'win32' ? 'start' : 'open'; var homepagePath = require(paths.appPackageJson).homepage; - if (homepagePath) { - console.log('You can now publish them at ' + homepagePath + '.'); - console.log('For example, if you use GitHub Pages:'); + var publicPath = config.output.publicPath; + if (homepagePath && homepagePath.indexOf('.github.io/') !== -1) { + // "homepage": "http://user.github.io/project" + console.log('You can now deploy them to ' + chalk.green(homepagePath) + ':'); console.log(); - console.log(' git commit -am "Save local changes"'); - console.log(' git checkout -B gh-pages'); - console.log(' git add -f build'); - console.log(' git commit -am "Rebuild website"'); - console.log(' git filter-branch -f --prune-empty --subdirectory-filter build'); - console.log(' git push -f origin gh-pages'); - console.log(' git checkout -'); + 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 -')); console.log(); + 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') + '.'); + } 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') + '.'); } else { - console.log('You can now serve them with any static server.'); - console.log('For example:'); + // no homepage or "homepage": "http://mywebsite.com" + console.log('You can now deploy them or serve them with a static server:'); console.log(); - console.log(' npm install -g pushstate-server'); - console.log(' pushstate-server build'); - console.log(' ' + openCommand + ' http://localhost:9000'); + 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(); - console.log(chalk.dim('The project was built assuming it is hosted at the root.')); - console.log(chalk.dim('Set the "homepage" field in package.json to override this.')); - console.log(chalk.dim('For example, "homepage": "http://user.github.io/project".')); + 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') + '.'); + } 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(',')); + } } console.log(); }); From cd8bae0d12b2648715da2df3b8378f2421129172 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 4 Aug 2016 14:21:34 +0100 Subject: [PATCH 07/20] Further tweak the build output --- scripts/build.js | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/scripts/build.js b/scripts/build.js index 50fd576972..36affa9ed4 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -118,7 +118,11 @@ function build(previousSizeMap) { var publicPath = config.output.publicPath; if (homepagePath && homepagePath.indexOf('.github.io/') !== -1) { // "homepage": "http://user.github.io/project" - console.log('You can now deploy them to ' + chalk.green(homepagePath) + ':'); + 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:'); console.log(); 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')); @@ -128,32 +132,35 @@ function build(previousSizeMap) { console.log(' ' + chalk.blue('git') + chalk.cyan(' push -f origin gh-pages')); console.log(' ' + chalk.blue('git') + chalk.cyan(' checkout -')); console.log(); - 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') + '.'); } 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') + '.'); - } else { - // no homepage or "homepage": "http://mywebsite.com" - console.log('You can now deploy them or serve them 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('The ' + chalk.cyan('build') + ' folder is ready to be deployed.'); console.log(); + } else { + // no homepage or "homepage": "http://mywebsite.com" 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') + '.'); + console.log(); } 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(',')); + console.log(); } + 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(); } - console.log(); }); } From a829ca397feaf3cdb0c39c50e9e9be3e1f1944e4 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 4 Aug 2016 14:47:30 +0100 Subject: [PATCH 08/20] Tweak howto --- template/README.md | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/template/README.md b/template/README.md index 7cb4c5c1ed..d46883289d 100644 --- a/template/README.md +++ b/template/README.md @@ -9,7 +9,7 @@ You can find the most recent version of this guide [here](https://github.com/fac - [npm start](#npm-start) - [npm run build](#npm-run-build) - [npm run eject](#npm-run-eject) -- [How To](#how-to) +- [Recipes](#recipes) - [Displaying Lint Output in the Editor](#displaying-lint-output-in-the-editor) - [Installing a Dependency](#installing-a-dependency) - [Importing a Component](#importing-a-component) @@ -19,6 +19,7 @@ You can find the most recent version of this guide [here](https://github.com/fac - [Adding Bootstrap](#adding-bootstrap) - [Adding Flow](#adding-flow) - [Adding Custom Environment Variables](#adding-custom-environment-variables) + - [Integrating with a Node Backend](#integrating-with-a-node-backend) - [Deploying](#deploying) - [Something Missing?](#something-missing) @@ -89,7 +90,7 @@ Instead, it will copy all the configuration files and the transitive dependencie You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. -## How To +## Recipes ### Displaying Lint Output in the Editor @@ -129,7 +130,6 @@ npm install -g eslint babel-eslint eslint-plugin-react eslint-plugin-import esli We recognize that this is suboptimal, but it is currently required due to the way we hide the ESLint dependency. The ESLint team is already [working on a solution to this](https://github.com/eslint/eslint/issues/3458) so this may become unnecessary in a couple of months. - ### Installing a Dependency The generated project includes React and ReactDOM as dependencies. It also includes a set of scripts used by Create React App as a development dependency. You may install other dependencies (for example, React Router) with `npm`: @@ -317,7 +317,7 @@ Now you are ready to use the imported React Bootstrap components within your com Flow typing is currently [not supported out of the box](https://github.com/facebookincubator/create-react-app/issues/72) with the default `.flowconfig` generated by Flow. If you run it, you might get errors like this: -``` +```js node_modules/fbjs/lib/Deferred.js.flow:60 60: Promise.prototype.done.apply(this._promise, arguments); ^^^^ property `done`. Property not found in @@ -343,7 +343,7 @@ src/index.js:5 To fix this, change your `.flowconfig` to look like this: -``` +```ini [libs] ./node_modules/fbjs/flow/lib @@ -362,7 +362,7 @@ Re-run flow, and you shouldn’t get any extra issues. If you later `eject`, you’ll need to replace `react-scripts` references with the `` placeholder, for example: -``` +```ini module.name_mapper='^\(.*\)\.css$' -> '/config/flow/css' module.name_mapper='^\(.*\)\.\(jpg\|png\|gif\|eot\|svg\|ttf\|woff\|woff2\|mp4\|webm\)$' -> '/config/flow/file' ``` @@ -386,13 +386,16 @@ First, you need to have environment variables defined, which can vary between OS consume a secret defined in the environment inside a `
`: ```jsx -
Hello, Admin!
- -You are running this application in {process.env.NODE_ENV} mode. - - - - +render() { + return ( +
+ You are running this application in {process.env.NODE_ENV} mode. +
+ +
+
+ ); +} ``` The above form is looking for a variable called `REACT_APP_SECRET_CODE` from the environment. In order to consume this @@ -418,11 +421,12 @@ variable will be set for you automatically. When you load the app in the browser its value set to `abcdef`, and the bold text will show the environment provided when using `npm start`: ```html -
Hello, Admin!
-You are running this application in development mode. -
- -
+
+ You are running this application in development mode. +
+ +
+
``` Having access to the `NODE_ENV` is also useful for performing actions conditionally: @@ -433,6 +437,10 @@ if (process.env.NODE_ENV !== 'production') { } ``` +### Integrating with a Node Backend + +Check out [this tutorial](https://www.fullstackreact.com/articles/using-create-react-app-with-a-server/) for instructions on integrating an app with a Node backend running on another port, and using `fetch()` to access it. You can find the companion GitHub repository [here](https://github.com/fullstackreact/food-lookup-demo). + ### Deploying #### GitHub Pages From 156a3c6cf176e8ec18e17446d9cc93a5ce82d472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Poduszl=C3=B3?= Date: Thu, 4 Aug 2016 16:32:47 +0200 Subject: [PATCH 09/20] Added a reasonable config for autoprefixer (resolves #73) (#345) * Added a reasonable config for autoprefixer (resolves #73) * Moved autoprefixer config to webpack.config --- config/webpack.config.dev.js | 11 ++++++++++- config/webpack.config.prod.js | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index 89ada953ea..ba6c1926c7 100644 --- a/config/webpack.config.dev.js +++ b/config/webpack.config.dev.js @@ -98,7 +98,16 @@ module.exports = { useEslintrc: false }, postcss: function() { - return [autoprefixer]; + return [ + autoprefixer({ + browsers: [ + '>1%', + 'last 4 versions', + 'Firefox ESR', + 'not ie < 9', + ] + }), + ]; }, plugins: [ new HtmlWebpackPlugin({ diff --git a/config/webpack.config.prod.js b/config/webpack.config.prod.js index a84e936c26..a4e52d7657 100644 --- a/config/webpack.config.prod.js +++ b/config/webpack.config.prod.js @@ -108,7 +108,16 @@ module.exports = { useEslintrc: false }, postcss: function() { - return [autoprefixer]; + return [ + autoprefixer({ + browsers: [ + '>1%', + 'last 4 versions', + 'Firefox ESR', + 'not ie < 9', + ] + }), + ]; }, plugins: [ new HtmlWebpackPlugin({ From 379cf5c96d7148ca11a024b6dcfd5ced252d1523 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 4 Aug 2016 16:49:59 +0100 Subject: [PATCH 10/20] Add proxy option to package.json (#282) This allows users to avoid CORS in simple projects that are hosted on the same server as backend. --- package.json | 2 ++ scripts/start.js | 56 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 9c0a178a30..0f15776f37 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "babel-runtime": "6.11.6", "case-sensitive-paths-webpack-plugin": "1.1.2", "chalk": "1.1.3", + "connect-history-api-fallback": "1.2.0", "cross-spawn": "4.0.0", "css-loader": "0.23.1", "detect-port": "1.0.0", @@ -61,6 +62,7 @@ "fs-extra": "0.30.0", "gzip-size": "3.0.0", "html-webpack-plugin": "2.22.0", + "http-proxy-middleware": "0.17.0", "jest": "14.1.0", "json-loader": "0.5.4", "opn": "4.0.2", diff --git a/scripts/start.js b/scripts/start.js index dd09ab51bd..168a77d0f0 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -13,11 +13,14 @@ var path = require('path'); var chalk = require('chalk'); var webpack = require('webpack'); var WebpackDevServer = require('webpack-dev-server'); +var historyApiFallback = require('connect-history-api-fallback'); +var httpProxyMiddleware = require('http-proxy-middleware'); var execSync = require('child_process').execSync; var opn = require('opn'); var detect = require('detect-port'); var prompt = require('./utils/prompt'); var config = require('../config/webpack.config.dev'); +var paths = require('../config/paths'); // Tools like Cloud9 rely on this var DEFAULT_PORT = process.env.PORT || 3000; @@ -155,16 +158,63 @@ function openBrowser(port) { opn('http://localhost:' + port + '/'); } +function addMiddleware(devServer) { + // `proxy` lets you to specify a fallback server during development. + // Every unrecognized request will be forwarded to it. + var proxy = require(paths.appPackageJson).proxy; + devServer.use(historyApiFallback({ + // For single page apps, we generally want to fallback to /index.html. + // However we also want to respect `proxy` for API calls. + // So if `proxy` is specified, we need to decide which fallback to use. + // We use a heuristic: if request `accept`s text/html, we pick /index.html. + // Modern browsers include text/html into `accept` header when navigating. + // However API calls like `fetch()` won’t generally won’t accept text/html. + // If this heuristic doesn’t work well for you, don’t use `proxy`. + htmlAcceptHeaders: proxy ? + ['text/html'] : + ['text/html', '*/*'] + })); + if (proxy) { + if (typeof proxy !== 'string') { + console.log(chalk.red('When specified, "proxy" in package.json must be a string.')); + console.log(chalk.red('Instead, the type of "proxy" was "' + typeof proxy + '".')); + console.log(chalk.red('Either remove "proxy" from package.json, or make it a string.')); + process.exit(1); + } + + // Otherwise, if proxy is specified, we will let it handle any request. + // There are a few exceptions which we won't send to the proxy: + // - /index.html (served as HTML5 history API fallback) + // - /*.hot-update.json (WebpackDevServer uses this too for hot reloading) + // - /sockjs-node/* (WebpackDevServer uses this for hot reloading) + var mayProxy = /^(?!\/(index\.html$|.*\.hot-update\.json$|sockjs-node\/)).*$/; + devServer.use(mayProxy, + // Pass the scope regex both to Express and to the middleware for proxying + // of both HTTP and WebSockets to work without false positives. + httpProxyMiddleware(pathname => mayProxy.test(pathname), { + target: proxy, + logLevel: 'silent', + secure: false, + changeOrigin: true + }) + ); + } + // Finally, by now we have certainly resolved the URL. + // It may be /index.html, so let the dev server try serving it again. + devServer.use(devServer.middleware); +} + function runDevServer(port) { - new WebpackDevServer(compiler, { - historyApiFallback: true, + var devServer = new WebpackDevServer(compiler, { hot: true, // Note: only CSS is currently hot reloaded publicPath: config.output.publicPath, quiet: true, watchOptions: { ignored: /node_modules/ } - }).listen(port, (err, result) => { + }); + addMiddleware(devServer); + devServer.listen(port, (err, result) => { if (err) { return console.log(err); } From 8cef20dfb479308a87fb4e8a0380d37be51727d6 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 4 Aug 2016 16:55:35 +0100 Subject: [PATCH 11/20] Flatten the recipes --- template/README.md | 78 +++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/template/README.md b/template/README.md index d46883289d..d57067d723 100644 --- a/template/README.md +++ b/template/README.md @@ -9,19 +9,18 @@ You can find the most recent version of this guide [here](https://github.com/fac - [npm start](#npm-start) - [npm run build](#npm-run-build) - [npm run eject](#npm-run-eject) -- [Recipes](#recipes) - - [Displaying Lint Output in the Editor](#displaying-lint-output-in-the-editor) - - [Installing a Dependency](#installing-a-dependency) - - [Importing a Component](#importing-a-component) - - [Adding a Stylesheet](#adding-a-stylesheet) - - [Post-Processing CSS](#post-processing-css) - - [Adding Images and Fonts](#adding-images-and-fonts) - - [Adding Bootstrap](#adding-bootstrap) - - [Adding Flow](#adding-flow) - - [Adding Custom Environment Variables](#adding-custom-environment-variables) - - [Integrating with a Node Backend](#integrating-with-a-node-backend) - - [Deploying](#deploying) - - [Something Missing?](#something-missing) +- [Displaying Lint Output in the Editor](#displaying-lint-output-in-the-editor) +- [Installing a Dependency](#installing-a-dependency) +- [Importing a Component](#importing-a-component) +- [Adding a Stylesheet](#adding-a-stylesheet) +- [Post-Processing CSS](#post-processing-css) +- [Adding Images and Fonts](#adding-images-and-fonts) +- [Adding Bootstrap](#adding-bootstrap) +- [Adding Flow](#adding-flow) +- [Adding Custom Environment Variables](#adding-custom-environment-variables) +- [Integrating with a Node Backend](#integrating-with-a-node-backend) +- [Deploying](#deploying) +- [Something Missing?](#something-missing) ## Sending Feedback @@ -90,9 +89,7 @@ Instead, it will copy all the configuration files and the transitive dependencie You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. -## Recipes - -### Displaying Lint Output in the Editor +## Displaying Lint Output in the Editor >Note: this feature is available with `react-scripts@0.2.0` and higher. @@ -130,7 +127,7 @@ npm install -g eslint babel-eslint eslint-plugin-react eslint-plugin-import esli We recognize that this is suboptimal, but it is currently required due to the way we hide the ESLint dependency. The ESLint team is already [working on a solution to this](https://github.com/eslint/eslint/issues/3458) so this may become unnecessary in a couple of months. -### Installing a Dependency +## Installing a Dependency The generated project includes React and ReactDOM as dependencies. It also includes a set of scripts used by Create React App as a development dependency. You may install other dependencies (for example, React Router) with `npm`: @@ -138,14 +135,14 @@ The generated project includes React and ReactDOM as dependencies. It also inclu npm install --save ``` -### Importing a Component +## Importing a Component This project setup supports ES6 modules thanks to Babel. While you can still use `require()` and `module.exports`, we encourage you to use [`import` and `export`](http://exploringjs.com/es6/ch_modules.html) instead. For example: -#### `Button.js` +### `Button.js` ```js import React, { Component } from 'react'; @@ -159,7 +156,8 @@ class Button extends Component { export default Button; // Don’t forget to use export default! ``` -#### `DangerButton.js` +### `DangerButton.js` + ```js import React, { Component } from 'react'; @@ -186,11 +184,11 @@ Learn more about ES6 modules: * [Exploring ES6: Modules](http://exploringjs.com/es6/ch_modules.html) * [Understanding ES6: Modules](https://leanpub.com/understandinges6/read#leanpub-auto-encapsulating-code-with-modules) -### Adding a Stylesheet +## Adding a Stylesheet This project setup uses [Webpack](https://webpack.github.io/) for handling all assets. Webpack offers a custom way of “extending” the concept of `import` beyond JavaScript. To express that a JavaScript file depends on a CSS file, you need to **import the CSS from the JavaScript file**: -#### `Button.css` +### `Button.css` ```css .Button { @@ -198,7 +196,7 @@ This project setup uses [Webpack](https://webpack.github.io/) for handling all a } ``` -#### `Button.js` +### `Button.js` ```js import React, { Component } from 'react'; @@ -218,7 +216,7 @@ In development, expressing dependencies this way allows your styles to be reload If you are concerned about using Webpack-specific semantics, you can put all your CSS right into `src/index.css`. It would still be imported from `src/index.js`, but you could always remove that import if you later migrate to a different build tool. -### Post-Processing CSS +## Post-Processing CSS This project setup minifies your CSS and adds vendor prefixes to it automatically through [Autoprefixer](https://github.com/postcss/autoprefixer) so you don’t need to worry about it. @@ -251,7 +249,7 @@ becomes this: There is currently no support for preprocessors such as Less, or for sharing variables across CSS files. -### Adding Images and Fonts +## Adding Images and Fonts With Webpack, using static assets like images and fonts works similarly to CSS. @@ -287,25 +285,25 @@ Please be advised that this is also a custom feature of Webpack. **It is not required for React** but many people enjoy it (and React Native uses a similar mechanism for images). However it may not be portable to some other environments, such as Node.js and Browserify. If you prefer to reference static assets in a more traditional way outside the module system, please let us know [in this issue](https://github.com/facebookincubator/create-react-app/issues/28), and we will consider support for this. -### Adding Bootstrap +## Adding Bootstrap You don’t have to use [React Bootstrap](https://react-bootstrap.github.io) together with React but it is a popular library for integrating Bootstrap with React apps. If you need it, you can integrate it with Create React App by following these steps: -**Step 1.** Install React Bootstrap and Bootstrap from NPM. React Bootstrap does not include Bootstrap CSS so this needs to be installed as well. +Install React Bootstrap and Bootstrap from NPM. React Bootstrap does not include Bootstrap CSS so this needs to be installed as well: ``` npm install react-bootstrap --save npm install bootstrap@3 --save ``` -**Step 2.** Import Bootstrap CSS and optionally Bootstrap theme CSS in the ```index.js``` file. +Import Bootstrap CSS and optionally Bootstrap theme CSS in the ```src/index.js``` file: ```js import 'bootstrap/dist/css/bootstrap.css'; import 'bootstrap/dist/css/bootstrap-theme.css'; ``` -**Step 3.** Import required React Bootstrap components within ```App.js``` file or your custom component files. +Import required React Bootstrap components within ```src/App.js``` file or your custom component files: ```js import { Navbar, Jumbotron, Button } from 'react-bootstrap'; @@ -313,7 +311,7 @@ import { Navbar, Jumbotron, Button } from 'react-bootstrap'; Now you are ready to use the imported React Bootstrap components within your component hierarchy defined in the render method. Here is an example [`App.js`](https://gist.githubusercontent.com/gaearon/85d8c067f6af1e56277c82d19fd4da7b/raw/6158dd991b67284e9fc8d70b9d973efe87659d72/App.js) redone using React Bootstrap. -### Adding Flow +## Adding Flow Flow typing is currently [not supported out of the box](https://github.com/facebookincubator/create-react-app/issues/72) with the default `.flowconfig` generated by Flow. If you run it, you might get errors like this: @@ -369,7 +367,7 @@ module.name_mapper='^\(.*\)\.\(jpg\|png\|gif\|eot\|svg\|ttf\|woff\|woff2\|mp4\|w We will consider integrating more tightly with Flow in the future so that you don’t have to do this. -### Adding Custom Environment Variables +## Adding Custom Environment Variables > Note: this feature is available with `react-scripts@0.3.0` and higher. @@ -401,13 +399,15 @@ render() { The above form is looking for a variable called `REACT_APP_SECRET_CODE` from the environment. In order to consume this value, we need to have it defined in the environment: -#### Windows Cmd +### Windows (cmd.exe) ```cmd -set REACT_APP_SECRET_CODE=abcdef && npm start +set REACT_APP_SECRET_CODE=abcdef&&npm start ``` -#### Bash/Unix shells +(Note: the lack of whitespace is intentional.) + +### Linux, OS X (Bash) ```bash REACT_APP_SECRET_CODE=abcdef npm start @@ -437,13 +437,13 @@ if (process.env.NODE_ENV !== 'production') { } ``` -### Integrating with a Node Backend +## Integrating with a Node Backend Check out [this tutorial](https://www.fullstackreact.com/articles/using-create-react-app-with-a-server/) for instructions on integrating an app with a Node backend running on another port, and using `fetch()` to access it. You can find the companion GitHub repository [here](https://github.com/fullstackreact/food-lookup-demo). -### Deploying +## Deploying -#### GitHub Pages +### GitHub Pages >Note: this feature is available with `react-scripts@0.2.0` and higher. @@ -475,10 +475,10 @@ Note that GitHub Pages doesn't support routers that use the HTML5 `pushState` hi * You could switch from using HTML5 history API to routing with hashes. If you use React Router, you can switch to `hashHistory` for this effect, but the URL will be longer and more verbose (for example, `http://user.github.io/todomvc/#/todos/42?_k=yknaj`). [Read more](https://github.com/reactjs/react-router/blob/master/docs/guides/Histories.md#histories) about different history implementations in React Router. * Alternatively, you can use a trick to teach GitHub Pages to handle 404 by redirecting to your `index.html` page with a special redirect parameter. You would need to add a `404.html` file with the redirection code to the `build` folder before deploying your project, and you’ll need to add code handling the redirect parameter to `index.html`. You can find a detailed explanation of this technique [in this guide](https://github.com/rafrex/spa-github-pages). -#### Heroku +### Heroku Use the [Heroku Buildpack for create-react-app](https://github.com/mars/create-react-app-buildpack). -### Something Missing? +## 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/template/README.md) From f34c72e5238cc86b47f33b9ac9aa0431a045d322 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 4 Aug 2016 17:13:09 +0100 Subject: [PATCH 12/20] Add updating instructions Fixes #119 --- template/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/template/README.md b/template/README.md index d57067d723..0ec9c1e3da 100644 --- a/template/README.md +++ b/template/README.md @@ -3,6 +3,7 @@ You can find the most recent version of this guide [here](https://github.com/fac ## Table of Contents +- [Updating to New Releases](#updating-to-new-releases) - [Sending Feedback](#sending-feedback) - [Folder Structure](#folder-structure) - [Available Scripts](#available-scripts) @@ -22,6 +23,23 @@ You can find the most recent version of this guide [here](https://github.com/fac - [Deploying](#deploying) - [Something Missing?](#something-missing) +## Updating to New Releases + +Create React App is divided into two packages: + +* `create-react-app` is a global command-line utility that you use to create new projects. +* `react-scripts` is a development dependency in the generated projects (including this one). + +You almost never need to update `create-react-app` itself: it’s delegates all the setup to `react-scripts`. + +When you run `create-react-app`, it always creates the project with the latest version of `react-scripts` so you’ll get all the new features and improvements in newly created apps automatically. + +To update an existing project to a new version of `react-scripts`, [open the changelog](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md), find the version you’re currently on (check `package.json` in this folder if you’re not sure), and apply the migration instructions for the newer versions. + +In most cases bumping the `react-scripts` version in `package.json` and running `npm install` in this folder should be enough, but it’s good to consult the [changelog](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md) for potential breaking changes. + +We commit to keeping the breaking changes minimal so you can upgrade `react-scripts` painlessly. + ## Sending Feedback We are always open to [your feedback](https://github.com/facebookincubator/create-react-app/issues). From 37fcce7ed06dad2caea4d17da92966bb5a0757ce Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 4 Aug 2016 17:27:35 +0100 Subject: [PATCH 13/20] Tweak deployment instructions, add Now --- template/README.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/template/README.md b/template/README.md index 0ec9c1e3da..fe0b06ca11 100644 --- a/template/README.md +++ b/template/README.md @@ -20,7 +20,10 @@ You can find the most recent version of this guide [here](https://github.com/fac - [Adding Flow](#adding-flow) - [Adding Custom Environment Variables](#adding-custom-environment-variables) - [Integrating with a Node Backend](#integrating-with-a-node-backend) -- [Deploying](#deploying) +- [Deployment](#deployment) + - [Now](#now) + - [Heroku](#heroku) + - [GitHub Pages](#github-pages) - [Something Missing?](#something-missing) ## Updating to New Releases @@ -459,7 +462,15 @@ if (process.env.NODE_ENV !== 'production') { Check out [this tutorial](https://www.fullstackreact.com/articles/using-create-react-app-with-a-server/) for instructions on integrating an app with a Node backend running on another port, and using `fetch()` to access it. You can find the companion GitHub repository [here](https://github.com/fullstackreact/food-lookup-demo). -## Deploying +## Deployment + +### Now + +See [this example](https://github.com/xkawi/create-react-app-now) for a zero-configuration single-command deployment with [now](https://zeit.co/now). + +### Heroku + +Use the [Heroku Buildpack for create-react-app](https://github.com/mars/create-react-app-buildpack). ### GitHub Pages @@ -479,11 +490,12 @@ It could look like this: Now, whenever you run `npm run build`, you will see a cheat sheet with a sequence of commands to deploy to GitHub pages: ```sh +git commit -am "Save local changes" git checkout -B gh-pages git add -f build git commit -am "Rebuild website" -git push origin :gh-pages -git subtree push --prefix build origin gh-pages +git filter-branch -f --prune-empty --subdirectory-filter build +git push -f origin gh-pages git checkout - ``` @@ -493,10 +505,6 @@ Note that GitHub Pages doesn't support routers that use the HTML5 `pushState` hi * You could switch from using HTML5 history API to routing with hashes. If you use React Router, you can switch to `hashHistory` for this effect, but the URL will be longer and more verbose (for example, `http://user.github.io/todomvc/#/todos/42?_k=yknaj`). [Read more](https://github.com/reactjs/react-router/blob/master/docs/guides/Histories.md#histories) about different history implementations in React Router. * Alternatively, you can use a trick to teach GitHub Pages to handle 404 by redirecting to your `index.html` page with a special redirect parameter. You would need to add a `404.html` file with the redirection code to the `build` folder before deploying your project, and you’ll need to add code handling the redirect parameter to `index.html`. You can find a detailed explanation of this technique [in this guide](https://github.com/rafrex/spa-github-pages). -### Heroku - -Use the [Heroku Buildpack for create-react-app](https://github.com/mars/create-react-app-buildpack). - ## 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/template/README.md) From 731908dc31417e6faf3082a4801fef5cb17eab83 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 4 Aug 2016 17:56:05 +0100 Subject: [PATCH 14/20] Document the proxy option --- template/README.md | 54 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/template/README.md b/template/README.md index fe0b06ca11..441953eef6 100644 --- a/template/README.md +++ b/template/README.md @@ -20,6 +20,7 @@ You can find the most recent version of this guide [here](https://github.com/fac - [Adding Flow](#adding-flow) - [Adding Custom Environment Variables](#adding-custom-environment-variables) - [Integrating with a Node Backend](#integrating-with-a-node-backend) +- [Proxying API Requests in Development](#proxying-api-requests-in-development) - [Deployment](#deployment) - [Now](#now) - [Heroku](#heroku) @@ -462,8 +463,54 @@ if (process.env.NODE_ENV !== 'production') { Check out [this tutorial](https://www.fullstackreact.com/articles/using-create-react-app-with-a-server/) for instructions on integrating an app with a Node backend running on another port, and using `fetch()` to access it. You can find the companion GitHub repository [here](https://github.com/fullstackreact/food-lookup-demo). +## Proxying API Requests in Development + +>Note: this feature is available with `react-scripts@0.3.0` and higher. + +People often serve the front-end React app from the same host and port as their backend implementation. +For example, a production setup might look like this after the app is deployed: + +``` +/ - static server returns index.html with React app +/todos - static server returns index.html with React app +/api/todos - server handles any /api/* requests using the backend implementation +``` + +Such setup is **not** required. However, if you **do** have a setup like this, it is convenient to write requests like `fetch('/api/todos')` without worrying about redirecting them to another host or port during development. + +To tell the development server to proxy any unknown requests to your API server in development, add a `proxy` field to your `package.json`, for example: + +```js + "proxy": "http://localhost:4000", +``` + +This way, when you `fetch('/api/todos')` in development, the development server will recognize that it’s not a static asset, and will proxy your request to `http://localhost:4000/api/todos` as a fallback. + +Conveniently, this avoids [CORS issues](http://stackoverflow.com/questions/21854516/understanding-ajax-cors-and-security-considerations) and error messages like this in development: + +``` +Fetch API cannot load http://localhost:400/api/todos. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. +``` + +Keep in mind that `proxy` only has effect in development (with `npm start`), and it is up to you to ensure that URLs like `/api/todos` point to the right thing in production. You don’t have to use the `/api` prefix. Any unrecognized request will be redirected to the specified `proxy`. + +Currently the `proxy` option only handles HTTP requests, and it won’t proxy WebSocket connections. +If the `proxy` option is **not** flexible enough for you, alternatively you can: + +* Enable CORS on your server ([here’s how to do it for Express](http://enable-cors.org/server_expressjs.html)). +* Use [environment variables](#adding-custom-environment-variables) to inject the right server host and port into your app. + ## Deployment +By default, Create React App produces a build assuming your app is hosted at the server root. +To override this, specify the `homepage` in your `package.json`, for example: + +```js + "homepage": "http://mywebsite.com/relativepath", +``` + +This will let Create React App correctly infer the root path to use in the generated HTML file. + ### Now See [this example](https://github.com/xkawi/create-react-app-now) for a zero-configuration single-command deployment with [now](https://zeit.co/now). @@ -476,15 +523,10 @@ Use the [Heroku Buildpack for create-react-app](https://github.com/mars/create-r >Note: this feature is available with `react-scripts@0.2.0` and higher. -First, open your `package.json` and add a `homepage` field. -It could look like this: +Open your `package.json` and add a `homepage` field: ```js -{ - "name": "my-app", "homepage": "http://myusername.github.io/my-app", - // ... -} ``` Now, whenever you run `npm run build`, you will see a cheat sheet with a sequence of commands to deploy to GitHub pages: From cd815aafcdb612ba873ba3736cab26756379c16c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 4 Aug 2016 19:08:20 +0100 Subject: [PATCH 15/20] Check for JS files first --- config/webpack.config.dev.js | 2 +- config/webpack.config.prod.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index ba6c1926c7..cba0738c2a 100644 --- a/config/webpack.config.dev.js +++ b/config/webpack.config.dev.js @@ -32,7 +32,7 @@ module.exports = { publicPath: '/' }, resolve: { - extensions: ['', '.js', '.json'], + extensions: ['.js', '.json', ''], alias: { // This `alias` section can be safely removed after ejection. // We do this because `babel-runtime` may be inside `react-scripts`, diff --git a/config/webpack.config.prod.js b/config/webpack.config.prod.js index a4e52d7657..71a381fa4d 100644 --- a/config/webpack.config.prod.js +++ b/config/webpack.config.prod.js @@ -37,7 +37,7 @@ module.exports = { publicPath: publicPath }, resolve: { - extensions: ['', '.js', '.json'], + extensions: ['.js', '.json', ''], alias: { // This `alias` section can be safely removed after ejection. // We do this because `babel-runtime` may be inside `react-scripts`, From c13f7f40a20ccf2ef8d256976e381b7815b731ce Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 4 Aug 2016 19:21:07 +0100 Subject: [PATCH 16/20] Style tweaks --- scripts/eject.js | 2 +- scripts/start.js | 1 + scripts/test.js | 2 +- scripts/utils/{create-jest-config.js => createJestConfig.js} | 0 4 files changed, 3 insertions(+), 2 deletions(-) rename scripts/utils/{create-jest-config.js => createJestConfig.js} (100%) diff --git a/scripts/eject.js b/scripts/eject.js index 83a33bd1aa..82202d6eed 100644 --- a/scripts/eject.js +++ b/scripts/eject.js @@ -7,7 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -var createJestConfig = require('./utils/create-jest-config'); +var createJestConfig = require('./utils/createJestConfig'); var fs = require('fs'); var path = require('path'); var prompt = require('./utils/prompt'); diff --git a/scripts/start.js b/scripts/start.js index 168a77d0f0..dfd2018d3c 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -187,6 +187,7 @@ function addMiddleware(devServer) { // - /index.html (served as HTML5 history API fallback) // - /*.hot-update.json (WebpackDevServer uses this too for hot reloading) // - /sockjs-node/* (WebpackDevServer uses this for hot reloading) + // Tip: use https://www.debuggex.com/ to visualize the regex var mayProxy = /^(?!\/(index\.html$|.*\.hot-update\.json$|sockjs-node\/)).*$/; devServer.use(mayProxy, // Pass the scope regex both to Express and to the middleware for proxying diff --git a/scripts/test.js b/scripts/test.js index 9602637afe..f11cda5a42 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -9,7 +9,7 @@ process.env.NODE_ENV = 'test'; -const createJestConfig = require('./utils/create-jest-config'); +const createJestConfig = require('./utils/createJestConfig'); const jest = require('jest'); const path = require('path'); const paths = require('../config/paths'); diff --git a/scripts/utils/create-jest-config.js b/scripts/utils/createJestConfig.js similarity index 100% rename from scripts/utils/create-jest-config.js rename to scripts/utils/createJestConfig.js From 9035a558f35870dbfac90b9ad553eb4e5c40b2b3 Mon Sep 17 00:00:00 2001 From: Jeremy Gayed Date: Thu, 4 Aug 2016 15:05:58 -0400 Subject: [PATCH 17/20] Add '.jsx' to resolve.extensions[] (only to be blocked by CheckFilenameWebpackPlugin) --- config/webpack.config.dev.js | 2 +- config/webpack.config.prod.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index 830ae1a8c2..845f7d2e9a 100644 --- a/config/webpack.config.dev.js +++ b/config/webpack.config.dev.js @@ -33,7 +33,7 @@ module.exports = { publicPath: '/' }, resolve: { - extensions: ['', '.js', '.json'], + extensions: ['', '.js', '.json', '.jsx'], alias: { // This `alias` section can be safely removed after ejection. // We do this because `babel-runtime` may be inside `react-scripts`, diff --git a/config/webpack.config.prod.js b/config/webpack.config.prod.js index a84e936c26..4d7e7a95ae 100644 --- a/config/webpack.config.prod.js +++ b/config/webpack.config.prod.js @@ -37,7 +37,7 @@ module.exports = { publicPath: publicPath }, resolve: { - extensions: ['', '.js', '.json'], + extensions: ['', '.js', '.json', '.jsx'], alias: { // This `alias` section can be safely removed after ejection. // We do this because `babel-runtime` may be inside `react-scripts`, From 0a35edeba788f83a3fe8e8cadadc80d9efd3476e Mon Sep 17 00:00:00 2001 From: Jeremy Gayed Date: Thu, 4 Aug 2016 15:13:01 -0400 Subject: [PATCH 18/20] Don't do it for prod --- config/webpack.config.prod.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/webpack.config.prod.js b/config/webpack.config.prod.js index 4d7e7a95ae..a84e936c26 100644 --- a/config/webpack.config.prod.js +++ b/config/webpack.config.prod.js @@ -37,7 +37,7 @@ module.exports = { publicPath: publicPath }, resolve: { - extensions: ['', '.js', '.json', '.jsx'], + extensions: ['', '.js', '.json'], alias: { // This `alias` section can be safely removed after ejection. // We do this because `babel-runtime` may be inside `react-scripts`, From 77e5e3ee77eea419987b86cecb9c56cc7f7195a6 Mon Sep 17 00:00:00 2001 From: Jeremy Gayed Date: Wed, 3 Aug 2016 23:43:58 -0400 Subject: [PATCH 19/20] Add check-filename-webpack-plugin to warn on JSX file extension --- config/webpack.config.dev.js | 7 +++++++ package.json | 1 + template/README.md | 2 ++ 3 files changed, 10 insertions(+) diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index cba0738c2a..7eb0c453b6 100644 --- a/config/webpack.config.dev.js +++ b/config/webpack.config.dev.js @@ -12,6 +12,7 @@ var autoprefixer = require('autoprefixer'); var webpack = require('webpack'); var HtmlWebpackPlugin = require('html-webpack-plugin'); var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); +var CheckFilenamePlugin = require('check-filename-webpack-plugin'); var WatchMissingNodeModulesPlugin = require('../scripts/utils/WatchMissingNodeModulesPlugin'); var paths = require('./paths'); var env = require('./env'); @@ -119,6 +120,12 @@ module.exports = { // Note: only CSS is currently hot reloaded new webpack.HotModuleReplacementPlugin(), new CaseSensitivePathsPlugin(), + new CheckFilenamePlugin({ + regex: /\.jsx$/, + error: function(filename) { + return 'Module load aborted: .jsx extensions are not allowed, use .js extensions only. See create-react-app/issues/290 for more info.\n\tFor: ' + filename; + } + }), new WatchMissingNodeModulesPlugin(paths.appNodeModules) ] }; diff --git a/package.json b/package.json index 0f15776f37..ccccf9a745 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "babel-runtime": "6.11.6", "case-sensitive-paths-webpack-plugin": "1.1.2", "chalk": "1.1.3", + "check-filename-webpack-plugin": "^1.0.0", "connect-history-api-fallback": "1.2.0", "cross-spawn": "4.0.0", "css-loader": "0.23.1", diff --git a/template/README.md b/template/README.md index 441953eef6..d4f129e138 100644 --- a/template/README.md +++ b/template/README.md @@ -81,6 +81,8 @@ You need to **put any JS and CSS files inside `src`**, or Webpack won’t see th You can, however, create more top-level directories. They will not be included in the production build so you can use them for things like documentation. +> NOTE: Only use `.js` file extensions for all JavaScript files inside `src`, including any React components. `.jsx` is explicitly not allowed, and an error will be thrown during compilation. See [create-react-app/issues/290](https://github.com/facebookincubator/create-react-app/issues/290) for more info. + ## Available Scripts In the project directory, you can run: From 1348773473edc8b8b94a4f2fdc2e41744d5e04a6 Mon Sep 17 00:00:00 2001 From: Jeremy Gayed Date: Thu, 4 Aug 2016 15:05:58 -0400 Subject: [PATCH 20/20] Add '.jsx' to resolve.extensions[] (only to be blocked by CheckFilenameWebpackPlugin) --- config/webpack.config.dev.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index 7eb0c453b6..0954c83901 100644 --- a/config/webpack.config.dev.js +++ b/config/webpack.config.dev.js @@ -33,7 +33,7 @@ module.exports = { publicPath: '/' }, resolve: { - extensions: ['.js', '.json', ''], + extensions: ['.jsx', '.js', '.json', ''], alias: { // This `alias` section can be safely removed after ejection. // We do this because `babel-runtime` may be inside `react-scripts`,