diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index 89ada953ea..0954c83901 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'); @@ -32,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`, @@ -98,7 +99,16 @@ module.exports = { useEslintrc: false }, postcss: function() { - return [autoprefixer]; + return [ + autoprefixer({ + browsers: [ + '>1%', + 'last 4 versions', + 'Firefox ESR', + 'not ie < 9', + ] + }), + ]; }, plugins: [ new HtmlWebpackPlugin({ @@ -110,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/config/webpack.config.prod.js b/config/webpack.config.prod.js index a84e936c26..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`, @@ -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({ diff --git a/package.json b/package.json index 15eaec5237..ccccf9a745 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,8 @@ "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", "detect-port": "1.0.0", @@ -61,12 +63,15 @@ "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", "postcss-loader": "0.9.1", "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 e57e040bd6..36affa9ed4 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -18,79 +18,149 @@ 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'); +var stripAnsi = require('strip-ansi'); -// 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)/, (match, p1, p2, p3) => 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 getDifferenceLabel(currentSize, previousSize) { + var FIFTY_KILOBYTES = 1024 * 50; + var difference = currentSize - previousSize; + var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0; + if (difference >= FIFTY_KILOBYTES) { + return chalk.red('+' + fileSize); + } else if (difference < FIFTY_KILOBYTES && difference > 0) { + return chalk.yellow('+' + fileSize); + } else if (difference < 0) { + return chalk.green(fileSize); + } else { + return ''; } +} + +// First, read the current file sizes in build directory. +// This lets us display how much they changed later. +recursive(paths.appBuild, (err, 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(chalk.green('Compiled successfully.')); - console.log(); + // Remove all content but keep the directory so that + // if you're in it, you don't end up in Trash + rimrafSync(paths.appBuild + '/*'); + + // Start the webpack build + build(previousSizeMap); +}); - console.log('File sizes after gzip:'); - console.log(); +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) + 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.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.green(sizeLabel) + + ' ' + 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(); - 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 -'); +function build(previousSizeMap) { + console.log('Creating an optimized production build...'); + webpack(config).run((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('Compiled successfully.')); console.log(); - } else { - console.log('You can now serve them with any static server.'); - console.log('For example:'); + + console.log('File sizes after gzip:'); console.log(); - console.log(' npm install -g pushstate-server'); - console.log(' pushstate-server build'); - console.log(' ' + openCommand + ' http://localhost:9000'); + printFileSizes(stats, previousSizeMap); 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(); -}); + + var openCommand = process.platform === 'win32' ? 'start' : 'open'; + var homepagePath = require(paths.appPackageJson).homepage; + var publicPath = config.output.publicPath; + if (homepagePath && homepagePath.indexOf('.github.io/') !== -1) { + // "homepage": "http://user.github.io/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('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')); + 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(); + } 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 { + // 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(); + } + }); +} 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 dd09ab51bd..dfd2018d3c 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,64 @@ 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) + // 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 + // 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); } 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 diff --git a/template/README.md b/template/README.md index 7cb4c5c1ed..d4f129e138 100644 --- a/template/README.md +++ b/template/README.md @@ -3,24 +3,46 @@ 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) - [npm start](#npm-start) - [npm run build](#npm-run-build) - [npm run eject](#npm-run-eject) -- [How To](#how-to) - - [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) - - [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) +- [Proxying API Requests in Development](#proxying-api-requests-in-development) +- [Deployment](#deployment) + - [Now](#now) + - [Heroku](#heroku) + - [GitHub Pages](#github-pages) +- [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 @@ -59,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: @@ -89,9 +113,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 - -### 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. @@ -129,8 +151,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 +159,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 +180,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 +208,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 +220,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 +240,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 +273,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 +309,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,11 +335,11 @@ 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: -``` +```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 +365,7 @@ src/index.js:5 To fix this, change your `.flowconfig` to look like this: -``` +```ini [libs] ./node_modules/fbjs/flow/lib @@ -362,14 +384,14 @@ 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' ``` 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. @@ -386,25 +408,30 @@ 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 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 @@ -418,11 +445,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,31 +461,85 @@ if (process.env.NODE_ENV !== 'production') { } ``` -### Deploying +## 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). + +## 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. -#### GitHub Pages +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). + +### Heroku + +Use the [Heroku Buildpack for create-react-app](https://github.com/mars/create-react-app-buildpack). + +### GitHub Pages >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: ```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 - ``` @@ -467,10 +549,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? +## 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)