|
- /**
- * Copyright (c) 2015-present, Facebook, Inc.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
- 'use strict';
-
- const address = require('address');
- const fs = require('fs');
- const path = require('path');
- const url = require('url');
- const chalk = require('chalk');
- const detect = require('detect-port-alt');
- const isRoot = require('is-root');
- 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 {
- // This can only return an IPv4 address
- lanUrlForConfig = address.ip();
- if (lanUrlForConfig) {
- // Check if the address is a private ip
- // https://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
- if (
- /^10[.]|^172[.](1[6-9]|2[0-9]|3[0-1])[.]|^192[.]168[.]/.test(
- lanUrlForConfig
- )
- ) {
- // Address is private, format it for later use
- lanUrlForTerminal = prettyPrintUrl(lanUrlForConfig);
- } else {
- // Address is not private, so we will discard it
- lanUrlForConfig = undefined;
- }
- }
- } 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.hooks.invalid.tap('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.hooks.done.tap('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.
- // We only construct the warnings and errors for speed:
- // https://github.com/facebook/create-react-app/issues/4492#issuecomment-421959548
- const messages = formatWebpackMessages(
- stats.toJson({ all: false, warnings: true, errors: 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) {
- // Only keep the first error. Others are often indicative
- // of the same problem, but confuse the reader with noise.
- if (messages.errors.length > 1) {
- messages.errors.length = 1;
- }
- 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('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;
- }
- // Unfortunately, many languages (unlike node) do not yet support IPv6.
- // This means even though localhost resolves to ::1, the application
- // must fall back to IPv4 (on 127.0.0.1).
- // We can re-enable this in a few years.
- /*try {
- o.hostname = address.ipv6() ? '::1' : '127.0.0.1';
- } catch (_ignored) {
- o.hostname = '127.0.0.1';
- }*/
-
- try {
- // Check if we're on a network; if we are, chances are we can resolve
- // localhost. Otherwise, we can just be safe and assume localhost is
- // IPv4 for maximum compatibility.
- if (!address.ip()) {
- o.hostname = '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, appPublicFolder) {
- // `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 !== '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 except for files in the public folder.
- function mayProxy(pathname) {
- const maybePublicPath = path.resolve(appPublicFolder, pathname.slice(1));
- return !fs.existsSync(maybePublicPath);
- }
-
- 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: We want to proxy all the requests that are not meant
- // for static assets and as all the requests for static assets will be using
- // `GET` method, we can proxy all non-`GET` requests.
- // For `GET` requests, 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 (
- req.method !== 'GET' ||
- (mayProxy(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,
- },
- ];
- }
-
- function choosePort(host, defaultPort) {
- return detect(defaultPort, host).then(
- port =>
- new Promise(resolve => {
- if (port === defaultPort) {
- return resolve(port);
- }
- const message =
- process.platform !== 'win32' && defaultPort < 1024 && !isRoot()
- ? `Admin permissions are required to run a server on a port below 1024.`
- : `Something is already running on port ${defaultPort}.`;
- if (isInteractive) {
- clearConsole();
- const existingProcess = getProcessForPort(defaultPort);
- const question = {
- type: 'confirm',
- name: 'shouldChangePort',
- message:
- chalk.yellow(
- message +
- `${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(message));
- 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,
- };
|