diff --git a/packages/react-dev-utils/README.md b/packages/react-dev-utils/README.md index d65c43ec667..a0662bf24bd 100644 --- a/packages/react-dev-utils/README.md +++ b/packages/react-dev-utils/README.md @@ -265,7 +265,25 @@ const publicPath = config.output.publicPath; printHostingInstructions(appPackage, publicUrl, publicPath, 'build', true); ``` -#### `webpackHotDevClient.js` +#### `WebpackDevServerUtils` + +##### `choosePort(host: string, defaultPort: number): Promise` + +Returns a Promise resolving to either `defaultPort` or next available port if the user confirms it is okay to do. If the port is taken and the user has refused to use another port, or if the terminal is not interactive and can’t present user with the choice, resolves to `null`. + +##### `createCompiler(webpack: Function, config: Object, appName: string, urls: Object, useYarn: boolean): WebpackCompiler` + +Creates a Webpack compiler instance for WebpackDevServer with built-in helpful messages. Takes the `require('webpack')` entry point as the first argument. To provide the `urls` argument, use `prepareUrls()` described below. + +##### `prepareProxy(proxySetting: string): Object` + +Creates a WebpackDevServer `proxy` configuration object from the `proxy` setting in `package.json`. + +##### `prepareUrls(protocol: string, host: string, port: number): Object` + +Returns an object with local and remote URLs for the development server. Pass this object to `createCompiler()` described above. + +#### `webpackHotDevClient` This is an alternative client for [WebpackDevServer](https://github.com/webpack/webpack-dev-server) that shows a syntax error overlay. diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js new file mode 100644 index 00000000000..592d8678a08 --- /dev/null +++ b/packages/react-dev-utils/WebpackDevServerUtils.js @@ -0,0 +1,395 @@ +/** + * 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. + */ +'use strict'; + +const address = require('address'); +const url = require('url'); +const chalk = require('chalk'); +const detect = require('@timer/detect-port'); +const inquirer = require('inquirer'); +const clearConsole = require('./clearConsole'); +const formatWebpackMessages = require('./formatWebpackMessages'); +const getProcessForPort = require('./getProcessForPort'); + +const isInteractive = process.stdout.isTTY; +let handleCompile; + +// You can safely remove this after ejecting. +// We only use this block for testing of Create React App itself: +const isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1); +if (isSmokeTest) { + handleCompile = (err, stats) => { + if (err || stats.hasErrors() || stats.hasWarnings()) { + process.exit(1); + } else { + process.exit(0); + } + }; +} + +function prepareUrls(protocol, host, port) { + const formatUrl = hostname => url.format({ + protocol, + hostname, + port, + pathname: '/', + }); + const prettyPrintUrl = hostname => url.format({ + protocol, + hostname, + port: chalk.bold(port), + pathname: '/', + }); + + const isUnspecifiedHost = host === '0.0.0.0' || host === '::'; + let prettyHost, lanUrlForConfig, lanUrlForTerminal; + if (isUnspecifiedHost) { + prettyHost = 'localhost'; + try { + lanUrlForConfig = address.ip(); + if (lanUrlForConfig) { + lanUrlForTerminal = prettyPrintUrl(lanUrlForConfig); + } + } catch (_e) { + // ignored + } + } else { + prettyHost = host; + } + const localUrlForTerminal = prettyPrintUrl(prettyHost); + const localUrlForBrowser = formatUrl(prettyHost); + return { + lanUrlForConfig, + lanUrlForTerminal, + localUrlForTerminal, + localUrlForBrowser, + }; +} + +function printInstructions(appName, urls, useYarn) { + console.log(); + console.log(`You can now view ${chalk.bold(appName)} in the browser.`); + console.log(); + + if (urls.lanUrlForTerminal) { + console.log( + ` ${chalk.bold('Local:')} ${urls.localUrlForTerminal}` + ); + console.log( + ` ${chalk.bold('On Your Network:')} ${urls.lanUrlForTerminal}` + ); + } else { + console.log(` ${urls.localUrlForTerminal}`); + } + + console.log(); + console.log('Note that the development build is not optimized.'); + console.log( + `To create a production build, use ` + + `${chalk.cyan(`${useYarn ? 'yarn' : 'npm'} run build`)}.` + ); + console.log(); +} + +function createCompiler(webpack, config, appName, urls, useYarn) { + // "Compiler" is a low-level interface to Webpack. + // It lets us listen to some events and provide our own custom messages. + let compiler; + try { + compiler = webpack(config, handleCompile); + } catch (err) { + console.log(chalk.red('Failed to compile.')); + console.log(); + console.log(err.message || err); + console.log(); + process.exit(1); + } + + // "invalid" event fires when you have changed a file, and Webpack is + // recompiling a bundle. WebpackDevServer takes care to pause serving the + // bundle, so if you refresh, it'll wait instead of serving the old one. + // "invalid" is short for "bundle invalidated", it doesn't imply any errors. + compiler.plugin('invalid', () => { + if (isInteractive) { + clearConsole(); + } + console.log('Compiling...'); + }); + + let isFirstCompile = true; + + // "done" event fires when Webpack has finished recompiling the bundle. + // Whether or not you have warnings or errors, you will get this event. + compiler.plugin('done', stats => { + if (isInteractive) { + clearConsole(); + } + + // We have switched off the default Webpack output in WebpackDevServer + // options so we are going to "massage" the warnings and errors and present + // them in a readable focused way. + const messages = formatWebpackMessages(stats.toJson({}, true)); + const isSuccessful = !messages.errors.length && !messages.warnings.length; + if (isSuccessful) { + console.log(chalk.green('Compiled successfully!')); + } + if (isSuccessful && (isInteractive || isFirstCompile)) { + printInstructions(appName, urls, useYarn); + } + isFirstCompile = false; + + // If errors exist, only show errors. + if (messages.errors.length) { + console.log(chalk.red('Failed to compile.\n')); + console.log(messages.errors.join('\n\n')); + return; + } + + // Show warnings if no errors were found. + if (messages.warnings.length) { + console.log(chalk.yellow('Compiled with warnings.\n')); + console.log(messages.warnings.join('\n\n')); + + // Teach some ESLint tricks. + console.log( + '\nSearch for the ' + + chalk.underline(chalk.yellow('rule keywords')) + + ' to learn more about each warning.' + ); + console.log( + 'To ignore, add ' + + chalk.cyan('// eslint-disable-next-line') + + ' to the line before.\n' + ); + } + }); + return compiler; +} + +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 + + ').' + ); + }; +} + +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), + }); + }); +} + +function choosePort(host, defaultPort) { + return detect(defaultPort, host).then( + port => new Promise(resolve => { + if (port === defaultPort) { + return resolve(port); + } + if (isInteractive) { + clearConsole(); + const existingProcess = getProcessForPort(defaultPort); + const question = { + type: 'confirm', + name: 'shouldChangePort', + message: chalk.yellow( + `Something is already running on port ${defaultPort}.` + + `${existingProcess ? ` Probably:\n ${existingProcess}` : ''}` + ) + '\n\nWould you like to run the app on another port instead?', + default: true, + }; + inquirer.prompt(question).then(answer => { + if (answer.shouldChangePort) { + resolve(port); + } else { + resolve(null); + } + }); + } else { + console.log( + chalk.red(`Something is already running on port ${defaultPort}.`) + ); + resolve(null); + } + }), + err => { + throw new Error( + chalk.red(`Could not find an open port at ${chalk.bold(host)}.`) + + '\n' + + ('Network error message: ' + err.message || err) + + '\n' + ); + } + ); +} + +module.exports = { + choosePort, + createCompiler, + prepareProxy, + prepareUrls, +}; diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index c63502cd8b0..f5e26edaf6d 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -24,12 +24,13 @@ "ModuleScopePlugin.js", "openBrowser.js", "openChrome.applescript", - "prepareProxy.js", "printHostingInstructions.js", "WatchMissingNodeModulesPlugin.js", + "WebpackDevServerUtils.js", "webpackHotDevClient.js" ], "dependencies": { + "@timer/detect-port": "1.1.3", "address": "1.0.1", "anser": "1.3.0", "babel-code-frame": "6.22.0", @@ -39,6 +40,7 @@ "filesize": "3.3.0", "gzip-size": "3.0.0", "html-entities": "1.2.1", + "inquirer": "3.0.6", "opn": "5.0.0", "recursive-readdir": "2.2.1", "shell-quote": "1.6.1", diff --git a/packages/react-dev-utils/prepareProxy.js b/packages/react-dev-utils/prepareProxy.js deleted file mode 100644 index 2e12e05bd29..00000000000 --- a/packages/react-dev-utils/prepareProxy.js +++ /dev/null @@ -1,187 +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 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/package.json b/packages/react-scripts/package.json index cdc70febc60..79dd9a39291 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -21,8 +21,6 @@ "react-scripts": "./bin/react-scripts.js" }, "dependencies": { - "@timer/detect-port": "1.1.3", - "address": "1.0.1", "autoprefixer": "7.1.0", "babel-core": "6.24.1", "babel-eslint": "7.2.3", diff --git a/packages/react-scripts/scripts/build.js b/packages/react-scripts/scripts/build.js index 8a4149370d2..c669b55b9e1 100644 --- a/packages/react-scripts/scripts/build.js +++ b/packages/react-scripts/scripts/build.js @@ -56,12 +56,25 @@ measureFileSizesBeforeBuild(paths.appBuild) return build(previousFileSizes); }) .then( - ({ stats, previousFileSizes }) => { - console.log(chalk.green('Compiled successfully.')); - console.log(); + ({ stats, previousFileSizes, warnings }) => { + if (warnings.length) { + console.log(chalk.yellow('Compiled with warnings.\n')); + console.log(warnings.join('\n\n')); + console.log( + '\nSearch for the ' + + chalk.underline(chalk.yellow('rule keywords')) + + ' to learn more about each warning.' + ); + console.log( + 'To ignore, add ' + + chalk.cyan('// eslint-disable-next-line') + + ' to the line before.\n' + ); + } else { + console.log(chalk.green('Compiled successfully.\n')); + } - console.log('File sizes after gzip:'); - console.log(); + console.log('File sizes after gzip:\n'); printFileSizesAfterBuild(stats, previousFileSizes); console.log(); @@ -78,10 +91,8 @@ measureFileSizesBeforeBuild(paths.appBuild) ); }, err => { - console.log(chalk.red('Failed to compile.')); - console.log(); - console.log(err.message || err); - console.log(); + console.log(chalk.red('Failed to compile.\n')); + console.log((err.message || err) + '\n'); process.exit(1); } ); @@ -101,17 +112,19 @@ function build(previousFileSizes) { return reject(new Error(messages.errors.join('\n\n'))); } if (process.env.CI && messages.warnings.length) { - console.log(); console.log( chalk.yellow( - 'Treating warnings as errors because process.env.CI = true.\n' + - 'Most CI servers set it automatically.' + '\nTreating warnings as errors because process.env.CI = true.\n' + + 'Most CI servers set it automatically.\n' ) ); - console.log(); return reject(new Error(messages.warnings.join('\n\n'))); } - return resolve({ stats, previousFileSizes }); + return resolve({ + stats, + previousFileSizes, + warnings: messages.warnings, + }); }); }); } diff --git a/packages/react-scripts/scripts/eject.js b/packages/react-scripts/scripts/eject.js index c7861e16c35..28dd41b8aaf 100644 --- a/packages/react-scripts/scripts/eject.js +++ b/packages/react-scripts/scripts/eject.js @@ -57,7 +57,7 @@ inquirer } } - const folders = ['config', 'config/jest', 'scripts', 'scripts/utils']; + const folders = ['config', 'config/jest', 'scripts']; // Make shallow array of files paths const files = folders.reduce( diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index 858e39cdc49..c9a88d9f5d6 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -22,25 +22,24 @@ process.env.NODE_ENV = 'development'; // Ensure environment variables are read. require('../config/env'); -const address = require('address'); const fs = require('fs'); const chalk = require('chalk'); -const detect = require('@timer/detect-port'); +const webpack = require('webpack'); const WebpackDevServer = require('webpack-dev-server'); const clearConsole = require('react-dev-utils/clearConsole'); const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); -const getProcessForPort = require('react-dev-utils/getProcessForPort'); +const { + choosePort, + createCompiler, + prepareProxy, + prepareUrls, +} = require('react-dev-utils/WebpackDevServerUtils'); const openBrowser = require('react-dev-utils/openBrowser'); -const inquirer = require('inquirer'); const paths = require('../config/paths'); const config = require('../config/webpack.config.dev'); -const devServerConfig = require('../config/webpackDevServer.config'); -const createWebpackCompiler = require('./utils/createWebpackCompiler'); -const prepareProxy = require('react-dev-utils/prepareProxy'); -const url = require('url'); +const createDevServerConfig = require('../config/webpackDevServer.config'); const useYarn = fs.existsSync(paths.yarnLockFile); -const cli = useYarn ? 'yarn' : 'npm'; const isInteractive = process.stdout.isTTY; // Warn and crash if required files are missing @@ -52,130 +51,43 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; const HOST = process.env.HOST || '0.0.0.0'; -function run(port) { - const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; - - const formatUrl = hostname => url.format({ - protocol, - hostname, - port, - pathname: '/', - }); - const prettyPrintUrl = hostname => url.format({ - protocol, - hostname, - port: chalk.bold(port), - pathname: '/', - }); - - const isUnspecifiedAddress = HOST === '0.0.0.0' || HOST === '::'; - let prettyHost, lanAddress, prettyLanUrl; - if (isUnspecifiedAddress) { - prettyHost = 'localhost'; - try { - lanAddress = address.ip(); - if (lanAddress) { - prettyLanUrl = prettyPrintUrl(lanAddress); - } - } catch (_e) { - // ignored - } - } else { - prettyHost = HOST; - } - const prettyLocalUrl = prettyPrintUrl(prettyHost); - - // Create a webpack compiler that is configured with custom messages. - const compiler = createWebpackCompiler( - config, - function onReady(showInstructions) { - if (!showInstructions) { - return; - } - console.log(); - console.log( - `You can now view ${chalk.bold(require(paths.appPackageJson).name)} in the browser.` - ); - console.log(); - - if (prettyLanUrl) { - console.log(` ${chalk.bold('Local:')} ${prettyLocalUrl}`); - console.log(` ${chalk.bold('On Your Network:')} ${prettyLanUrl}`); - } else { - console.log(` ${prettyLocalUrl}`); - } - - console.log(); - console.log('Note that the development build is not optimized.'); - console.log( - `To create a production build, use ${chalk.cyan(`${cli} run build`)}.` - ); - console.log(); - } - ); - - // 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(prepareProxy(proxy), lanAddress) - ); - - // 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(formatUrl(prettyHost)); - }); -} - // We attempt to use the default port but if it is busy, we offer the user to // run on a different port. `detect()` Promise resolves to the next free port. -detect(DEFAULT_PORT, HOST).then( - port => { - if (port === DEFAULT_PORT) { - run(port); +choosePort(HOST, DEFAULT_PORT) + .then(port => { + if (port == null) { + // We have not found a port. return; } - - if (isInteractive) { - clearConsole(); - const existingProcess = getProcessForPort(DEFAULT_PORT); - const question = { - type: 'confirm', - name: 'shouldChangePort', - message: chalk.yellow( - `Something is already running on port ${DEFAULT_PORT}.` + - `${existingProcess ? ` Probably:\n ${existingProcess}` : ''}` - ) + '\n\nWould you like to run the app on another port instead?', - default: true, - }; - - inquirer.prompt(question).then(answer => { - if (answer.shouldChangePort) { - run(port); - } - }); - } else { - console.log( - chalk.red(`Something is already running on port ${DEFAULT_PORT}.`) - ); - } - }, - err => { - console.log( - chalk.red(`Could not find an open port at ${chalk.bold(HOST)}.`) + const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; + const appName = require(paths.appPackageJson).name; + const urls = prepareUrls(protocol, HOST, port); + // Create a webpack compiler that is configured with custom messages. + const compiler = createCompiler(webpack, config, appName, urls, useYarn); + // Load proxy config + const proxySetting = require(paths.appPackageJson).proxy; + const proxyConfig = prepareProxy(proxySetting); + // Serve webpack assets generated by the compiler over a web sever. + const serverConfig = createDevServerConfig( + proxyConfig, + urls.lanUrlForConfig ); - console.log('Network error message: ' + err.message || err); - console.log(); - } -); + const devServer = new WebpackDevServer(compiler, serverConfig); + // Launch WebpackDevServer. + devServer.listen(port, HOST, err => { + if (err) { + return console.log(err); + } + if (isInteractive) { + clearConsole(); + } + console.log(chalk.cyan('Starting the development server...\n')); + openBrowser(urls.localUrlForBrowser); + }); + }) + .catch(err => { + if (err && err.message) { + console.log(err.message); + } + process.exit(1); + }); diff --git a/packages/react-scripts/scripts/utils/createWebpackCompiler.js b/packages/react-scripts/scripts/utils/createWebpackCompiler.js deleted file mode 100644 index a3adeee618c..00000000000 --- a/packages/react-scripts/scripts/utils/createWebpackCompiler.js +++ /dev/null @@ -1,120 +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 webpack = require('webpack'); -const clearConsole = require('react-dev-utils/clearConsole'); -const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); - -const isInteractive = process.stdout.isTTY; -let handleCompile; - -// You can safely remove this after ejecting. -// We only use this block for testing of Create React App itself: -const isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1); -if (isSmokeTest) { - handleCompile = (err, stats) => { - if (err || stats.hasErrors() || stats.hasWarnings()) { - process.exit(1); - } else { - process.exit(0); - } - }; -} - -module.exports = function createWebpackCompiler(config, onReadyCallback) { - // "Compiler" is a low-level interface to Webpack. - // It lets us listen to some events and provide our own custom messages. - let compiler; - try { - compiler = webpack(config, handleCompile); - } catch (err) { - console.log(chalk.red('Failed to compile.')); - console.log(); - console.log(err.message || err); - console.log(); - process.exit(1); - } - - // "invalid" event fires when you have changed a file, and Webpack is - // recompiling a bundle. WebpackDevServer takes care to pause serving the - // bundle, so if you refresh, it'll wait instead of serving the old one. - // "invalid" is short for "bundle invalidated", it doesn't imply any errors. - compiler.plugin('invalid', () => { - if (isInteractive) { - clearConsole(); - } - console.log('Compiling...'); - }); - - let isFirstCompile = true; - - // "done" event fires when Webpack has finished recompiling the bundle. - // Whether or not you have warnings or errors, you will get this event. - compiler.plugin('done', stats => { - if (isInteractive) { - clearConsole(); - } - - // We have switched off the default Webpack output in WebpackDevServer - // options so we are going to "massage" the warnings and errors and present - // them in a readable focused way. - const messages = formatWebpackMessages(stats.toJson({}, true)); - const isSuccessful = !messages.errors.length && !messages.warnings.length; - const showInstructions = isSuccessful && (isInteractive || isFirstCompile); - - if (isSuccessful) { - console.log(chalk.green('Compiled successfully!')); - } - - if (typeof onReadyCallback === 'function') { - onReadyCallback(showInstructions); - } - isFirstCompile = false; - - // If errors exist, only show errors. - if (messages.errors.length) { - console.log(chalk.red('Failed to compile.')); - console.log(); - messages.errors.forEach(message => { - console.log(message); - console.log(); - }); - return; - } - - // Show warnings if no errors were found. - if (messages.warnings.length) { - console.log(chalk.yellow('Compiled with warnings.')); - console.log(); - messages.warnings.forEach(message => { - console.log(message); - console.log(); - }); - - // Teach some ESLint tricks. - console.log( - 'Search for the ' + - chalk.underline(chalk.yellow('rule keywords')) + - ' to learn more about each warning.' - ); - console.log( - 'To ignore, add ' + - chalk.cyan('// eslint-disable-next-line') + - ' to the line before.' - ); - console.log(); - } - }); - - return compiler; -};