diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json
index 64a368c1bba..a5d4c75fe78 100644
--- a/packages/react-dev-utils/package.json
+++ b/packages/react-dev-utils/package.json
@@ -23,10 +23,12 @@
"launchEditor.js",
"openBrowser.js",
"openChrome.applescript",
+ "prepareProxy.js",
"WatchMissingNodeModulesPlugin.js",
"webpackHotDevClient.js"
],
"dependencies": {
+ "address": "1.0.1",
"anser": "1.1.0",
"babel-code-frame": "6.20.0",
"chalk": "1.1.3",
diff --git a/packages/react-dev-utils/prepareProxy.js b/packages/react-dev-utils/prepareProxy.js
new file mode 100644
index 00000000000..ea0e9f163b0
--- /dev/null
+++ b/packages/react-dev-utils/prepareProxy.js
@@ -0,0 +1,185 @@
+// @remove-on-eject-begin
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+// @remove-on-eject-end
+'use strict';
+
+const address = require('address');
+const chalk = require('chalk');
+const url = require('url');
+
+function resolveLoopback(proxy) {
+ const o = url.parse(proxy);
+ o.host = undefined;
+ if (o.hostname !== 'localhost') {
+ return proxy;
+ }
+ try {
+ o.hostname = address.ipv6() ? '::1' : '127.0.0.1';
+ } catch (_ignored) {
+ o.hostname = '127.0.0.1';
+ }
+ return url.format(o);
+}
+
+// We need to provide a custom onError function for httpProxyMiddleware.
+// It allows us to log custom error messages on the console.
+function onProxyError(proxy) {
+ return (err, req, res) => {
+ const host = req.headers && req.headers.host;
+ console.log(
+ chalk.red('Proxy error:') +
+ ' Could not proxy request ' +
+ chalk.cyan(req.url) +
+ ' from ' +
+ chalk.cyan(host) +
+ ' to ' +
+ chalk.cyan(proxy) +
+ '.'
+ );
+ console.log(
+ 'See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (' +
+ chalk.cyan(err.code) +
+ ').'
+ );
+ console.log();
+
+ // And immediately send the proper error response to the client.
+ // Otherwise, the request will eventually timeout with ERR_EMPTY_RESPONSE on the client side.
+ if (res.writeHead && !res.headersSent) {
+ res.writeHead(500);
+ }
+ res.end(
+ 'Proxy error: Could not proxy request ' +
+ req.url +
+ ' from ' +
+ host +
+ ' to ' +
+ proxy +
+ ' (' +
+ err.code +
+ ').'
+ );
+ };
+}
+
+module.exports = function prepareProxy(proxy) {
+ // `proxy` lets you specify alternate servers for specific requests.
+ // It can either be a string or an object conforming to the Webpack dev server proxy configuration
+ // https://webpack.github.io/docs/webpack-dev-server.html
+ if (!proxy) return undefined;
+ if (typeof proxy !== 'object' && typeof proxy !== 'string') {
+ console.log(
+ chalk.red(
+ 'When specified, "proxy" in package.json must be a string or an object.'
+ )
+ );
+ 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 an object.'
+ )
+ );
+ 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://jex.im/regulex/ to visualize the regex
+ const mayProxy = /^(?!\/(index\.html$|.*\.hot-update\.json$|sockjs-node\/)).*$/;
+
+ // Support proxy as a string for those who are using the simple proxy option
+ if (typeof proxy === 'string') {
+ if (!/^http(s)?:\/\//.test(proxy)) {
+ console.log(
+ chalk.red(
+ 'When "proxy" is specified in package.json it must start with either http:// or https://'
+ )
+ );
+ process.exit(1);
+ }
+
+ let target;
+ if (process.platform === 'win32') {
+ target = resolveLoopback(proxy);
+ } else {
+ target = proxy;
+ }
+ return [
+ {
+ target,
+ logLevel: 'silent',
+ // 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 as a string, 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 accept text/html.
+ // If this heuristic doesn’t work well for you, use a custom `proxy` object.
+ context: function(pathname, req) {
+ return mayProxy.test(pathname) &&
+ req.headers.accept &&
+ req.headers.accept.indexOf('text/html') === -1;
+ },
+ onProxyReq: proxyReq => {
+ // Browers may send Origin headers even with same-origin
+ // requests. To prevent CORS issues, we have to change
+ // the Origin to match the target URL.
+ if (proxyReq.getHeader('origin')) {
+ proxyReq.setHeader('origin', target);
+ }
+ },
+ onError: onProxyError(target),
+ secure: false,
+ changeOrigin: true,
+ ws: true,
+ xfwd: true,
+ },
+ ];
+ }
+
+ // Otherwise, proxy is an object so create an array of proxies to pass to webpackDevServer
+ return Object.keys(proxy).map(function(context) {
+ if (!proxy[context].hasOwnProperty('target')) {
+ console.log(
+ chalk.red(
+ 'When `proxy` in package.json is as an object, each `context` object must have a ' +
+ '`target` property specified as a url string'
+ )
+ );
+ process.exit(1);
+ }
+ let target;
+ if (process.platform === 'win32') {
+ target = resolveLoopback(proxy[context].target);
+ } else {
+ target = proxy[context].target;
+ }
+ return Object.assign({}, proxy[context], {
+ context: function(pathname) {
+ return mayProxy.test(pathname) && pathname.match(context);
+ },
+ onProxyReq: proxyReq => {
+ // Browers may send Origin headers even with same-origin
+ // requests. To prevent CORS issues, we have to change
+ // the Origin to match the target URL.
+ if (proxyReq.getHeader('origin')) {
+ proxyReq.setHeader('origin', target);
+ }
+ },
+ target,
+ onError: onProxyError(target),
+ });
+ });
+};
diff --git a/packages/react-scripts/config/webpackDevServer.config.js b/packages/react-scripts/config/webpackDevServer.config.js
index 37d9d1a750e..d8c1b885c25 100644
--- a/packages/react-scripts/config/webpackDevServer.config.js
+++ b/packages/react-scripts/config/webpackDevServer.config.js
@@ -10,54 +10,74 @@
// @remove-on-eject-end
'use strict';
+const launchEditor = require('react-dev-utils/launchEditor');
const config = require('./webpack.config.dev');
const paths = require('./paths');
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
-const host = process.env.HOST || 'localhost';
+const host = process.env.HOST || '0.0.0.0';
-module.exports = {
- // Enable gzip compression of generated files.
- compress: true,
- // Silence WebpackDevServer's own logs since they're generally not useful.
- // It will still show compile warnings and errors with this setting.
- clientLogLevel: 'none',
- // By default WebpackDevServer serves physical files from current directory
- // in addition to all the virtual build products that it serves from memory.
- // This is confusing because those files won’t automatically be available in
- // production build folder unless we copy them. However, copying the whole
- // project directory is dangerous because we may expose sensitive files.
- // Instead, we establish a convention that only files in `public` directory
- // get served. Our build script will copy `public` into the `build` folder.
- // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
- //
- // In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
- // Note that we only recommend to use `public` folder as an escape hatch
- // for files like `favicon.ico`, `manifest.json`, and libraries that are
- // for some reason broken when imported through Webpack. If you just want to
- // use an image, put it in `src` and `import` it from JavaScript instead.
- contentBase: paths.appPublic,
- // By default files from `contentBase` will not trigger a page reload.
- watchContentBase: true,
- // Enable hot reloading server. It will provide /sockjs-node/ endpoint
- // for the WebpackDevServer client so it can learn when the files were
- // updated. The WebpackDevServer client is included as an entry point
- // in the Webpack development configuration. Note that only changes
- // to CSS are currently hot reloaded. JS changes will refresh the browser.
- hot: true,
- // It is important to tell WebpackDevServer to use the same "root" path
- // as we specified in the config. In development, we always serve from /.
- publicPath: config.output.publicPath,
- // WebpackDevServer is noisy by default so we emit custom message instead
- // by listening to the compiler events with `compiler.plugin` calls above.
- quiet: true,
- // Reportedly, this avoids CPU overload on some systems.
- // https://github.com/facebookincubator/create-react-app/issues/293
- watchOptions: {
- ignored: /node_modules/,
- },
- // Enable HTTPS if the HTTPS environment variable is set to 'true'
- https: protocol === 'https',
- host: host,
- overlay: false,
+module.exports = function(proxy) {
+ return {
+ // Enable gzip compression of generated files.
+ compress: true,
+ // Silence WebpackDevServer's own logs since they're generally not useful.
+ // It will still show compile warnings and errors with this setting.
+ clientLogLevel: 'none',
+ // By default WebpackDevServer serves physical files from current directory
+ // in addition to all the virtual build products that it serves from memory.
+ // This is confusing because those files won’t automatically be available in
+ // production build folder unless we copy them. However, copying the whole
+ // project directory is dangerous because we may expose sensitive files.
+ // Instead, we establish a convention that only files in `public` directory
+ // get served. Our build script will copy `public` into the `build` folder.
+ // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
+ //
+ // In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
+ // Note that we only recommend to use `public` folder as an escape hatch
+ // for files like `favicon.ico`, `manifest.json`, and libraries that are
+ // for some reason broken when imported through Webpack. If you just want to
+ // use an image, put it in `src` and `import` it from JavaScript instead.
+ contentBase: paths.appPublic,
+ // By default files from `contentBase` will not trigger a page reload.
+ watchContentBase: true,
+ // Enable hot reloading server. It will provide /sockjs-node/ endpoint
+ // for the WebpackDevServer client so it can learn when the files were
+ // updated. The WebpackDevServer client is included as an entry point
+ // in the Webpack development configuration. Note that only changes
+ // to CSS are currently hot reloaded. JS changes will refresh the browser.
+ hot: true,
+ // It is important to tell WebpackDevServer to use the same "root" path
+ // as we specified in the config. In development, we always serve from /.
+ publicPath: config.output.publicPath,
+ // WebpackDevServer is noisy by default so we emit custom message instead
+ // by listening to the compiler events with `compiler.plugin` calls above.
+ quiet: true,
+ // Reportedly, this avoids CPU overload on some systems.
+ // https://github.com/facebookincubator/create-react-app/issues/293
+ watchOptions: {
+ ignored: /node_modules/,
+ },
+ // Enable HTTPS if the HTTPS environment variable is set to 'true'
+ https: protocol === 'https',
+ host: host,
+ overlay: false,
+ historyApiFallback: {
+ // Paths with dots should still use the history fallback.
+ // See https://github.com/facebookincubator/create-react-app/issues/387.
+ disableDotRule: true,
+ },
+ proxy,
+ setup(app) {
+ // This lets us open files from the crash overlay.
+ app.use(function launchEditorMiddleware(req, res, next) {
+ if (req.url.startsWith('/__open-stack-frame-in-editor')) {
+ launchEditor(req.query.fileName, req.query.lineNumber);
+ res.end();
+ } else {
+ next();
+ }
+ });
+ },
+ };
};
diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js
index 4448a0407e0..69bdc4b7450 100644
--- a/packages/react-scripts/scripts/start.js
+++ b/packages/react-scripts/scripts/start.js
@@ -35,7 +35,7 @@ const paths = require('../config/paths');
const config = require('../config/webpack.config.dev');
const devServerConfig = require('../config/webpackDevServer.config');
const createWebpackCompiler = require('./utils/createWebpackCompiler');
-const addWebpackMiddleware = require('./utils/addWebpackMiddleware');
+const prepareProxy = require('react-dev-utils/prepareProxy');
const useYarn = fs.existsSync(paths.yarnLockFile);
const cli = useYarn ? 'yarn' : 'npm';
@@ -73,34 +73,28 @@ function run(port) {
}
);
+ // Load proxy config
+ const proxy = require(paths.appPackageJson).proxy;
// Serve webpack assets generated by the compiler over a web sever.
- const devServer = new WebpackDevServer(compiler, devServerConfig);
-
- // Our custom middleware proxies requests to /index.html or a remote API.
- addWebpackMiddleware(devServer)
- .then(() => {
- // Launch WebpackDevServer.
- devServer.listen(port, HOST, err => {
- if (err) {
- return console.log(err);
- }
-
- if (isInteractive) {
- clearConsole();
- }
- console.log(chalk.cyan('Starting the development server...'));
- console.log();
-
- openBrowser(`${protocol}://${HOST}:${port}/`);
- });
- })
- .catch(e => {
- console.log(
- chalk.red('Failed to setup middleware, please report this error:')
- );
- console.log(e);
- process.exit(1);
- });
+ const devServer = new WebpackDevServer(
+ compiler,
+ devServerConfig(prepareProxy(proxy))
+ );
+
+ // Launch WebpackDevServer.
+ devServer.listen(port, HOST, err => {
+ if (err) {
+ return console.log(err);
+ }
+
+ if (isInteractive) {
+ clearConsole();
+ }
+ console.log(chalk.cyan('Starting the development server...'));
+ console.log();
+
+ openBrowser(`${protocol}://${HOST}:${port}/`);
+ });
}
// We attempt to use the default port but if it is busy, we offer the user to
diff --git a/packages/react-scripts/scripts/utils/addWebpackMiddleware.js b/packages/react-scripts/scripts/utils/addWebpackMiddleware.js
deleted file mode 100644
index aec088b808b..00000000000
--- a/packages/react-scripts/scripts/utils/addWebpackMiddleware.js
+++ /dev/null
@@ -1,188 +0,0 @@
-// @remove-on-eject-begin
-/**
- * Copyright (c) 2015-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
-// @remove-on-eject-end
-'use strict';
-
-const chalk = require('chalk');
-const dns = require('dns');
-const historyApiFallback = require('connect-history-api-fallback');
-const httpProxyMiddleware = require('http-proxy-middleware');
-const launchEditor = require('react-dev-utils/launchEditor');
-const url = require('url');
-const paths = require('../../config/paths');
-
-// We need to provide a custom onError function for httpProxyMiddleware.
-// It allows us to log custom error messages on the console.
-function onProxyError(proxy) {
- return (err, req, res) => {
- const host = req.headers && req.headers.host;
- console.log(
- chalk.red('Proxy error:') +
- ' Could not proxy request ' +
- chalk.cyan(req.url) +
- ' from ' +
- chalk.cyan(host) +
- ' to ' +
- chalk.cyan(proxy) +
- '.'
- );
- console.log(
- 'See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (' +
- chalk.cyan(err.code) +
- ').'
- );
- console.log();
-
- // And immediately send the proper error response to the client.
- // Otherwise, the request will eventually timeout with ERR_EMPTY_RESPONSE on the client side.
- if (res.writeHead && !res.headersSent) {
- res.writeHead(500);
- }
- res.end(
- 'Proxy error: Could not proxy request ' +
- req.url +
- ' from ' +
- host +
- ' to ' +
- proxy +
- ' (' +
- err.code +
- ').'
- );
- };
-}
-
-function resolveProxy(proxy) {
- const p = url.parse(proxy);
- const hostname = p.hostname;
- if (hostname !== 'localhost') {
- return Promise.resolve(proxy);
- }
- p.host = undefined; // Remove the host; we don't care about it
- return new Promise(resolve => {
- dns.lookup(hostname, { hints: 0, all: false }, (err, address) => {
- if (err) {
- console.log(
- chalk.red(
- '"proxy" in package.json is set to localhost and cannot be resolved.'
- )
- );
- console.log(
- chalk.red('Try setting "proxy" to 127.0.0.1 instead of localhost.')
- );
- process.exit(1);
- }
- p.hostname = address;
- resolve(url.format(p));
- });
- });
-}
-
-function registerProxy(devServer, _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);
- // Test that proxy url specified starts with http:// or https://
- } else if (!/^http(s)?:\/\//.test(_proxy)) {
- console.log(
- chalk.red(
- 'When "proxy" is specified in package.json it must start with either http:// or https://'
- )
- );
- process.exit(1);
- }
-
- return (process.platform === 'win32'
- ? resolveProxy(_proxy)
- : Promise.resolve(_proxy)).then(proxy => {
- // 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://jex.im/regulex/ to visualize the regex
- const mayProxy = /^(?!\/(index\.html$|.*\.hot-update\.json$|sockjs-node\/)).*$/;
-
- // Pass the scope regex both to Express and to the middleware for proxying
- // of both HTTP and WebSockets to work without false positives.
- const hpm = httpProxyMiddleware(pathname => mayProxy.test(pathname), {
- target: proxy,
- logLevel: 'silent',
- onProxyReq: proxyReq => {
- // Browers may send Origin headers even with same-origin
- // requests. To prevent CORS issues, we have to change
- // the Origin to match the target URL.
- if (proxyReq.getHeader('origin')) {
- proxyReq.setHeader('origin', proxy);
- }
- },
- onError: onProxyError(proxy),
- secure: false,
- changeOrigin: true,
- ws: true,
- xfwd: true,
- });
- devServer.use(mayProxy, hpm);
-
- // Listen for the websocket 'upgrade' event and upgrade the connection.
- // If this is not done, httpProxyMiddleware will not try to upgrade until
- // an initial plain HTTP request is made.
- devServer.listeningApp.on('upgrade', hpm.upgrade);
- });
-}
-
-// This is used by the crash overlay.
-function launchEditorMiddleware() {
- return function(req, res, next) {
- if (req.url.startsWith('/__open-stack-frame-in-editor')) {
- launchEditor(req.query.fileName, req.query.lineNumber);
- res.end();
- } else {
- next();
- }
- };
-}
-
-module.exports = function addWebpackMiddleware(devServer) {
- // `proxy` lets you to specify a fallback server during development.
- // Every unrecognized request will be forwarded to it.
- const proxy = require(paths.appPackageJson).proxy;
- devServer.use(launchEditorMiddleware());
- devServer.use(
- historyApiFallback({
- // Paths with dots should still use the history fallback.
- // See https://github.com/facebookincubator/create-react-app/issues/387.
- disableDotRule: true,
- // 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 accept text/html.
- // If this heuristic doesn’t work well for you, don’t use `proxy`.
- htmlAcceptHeaders: proxy ? ['text/html'] : ['text/html', '*/*'],
- })
- );
- return (proxy
- ? registerProxy(devServer, proxy)
- : Promise.resolve()).then(() => {
- // 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);
- });
-};