diff --git a/config/webpackDevServer.config.js b/config/webpackDevServer.config.js index 37d9d1a750e..d8c1b885c25 100644 --- a/config/webpackDevServer.config.js +++ b/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/scripts/start.js b/scripts/start.js index 4448a0407e0..69bdc4b7450 100644 --- a/scripts/start.js +++ b/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/scripts/utils/addWebpackMiddleware.js b/scripts/utils/addWebpackMiddleware.js deleted file mode 100644 index aec088b808b..00000000000 --- a/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); - }); -};