/** * 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. * * This file is taken from create-react-app. * We disabled tty support, removed unessential parts to us and * added support for `warningsFilter` and Multi Webpack Config. * * TODO: (cvle) The `forkTsCheckerWebpackPlugin` hooks are not working atm. */ "use strict"; /* eslint-disable no-console */ const address = require("address"); const url = require("url"); const chalk = require("chalk"); const clearConsole = require("react-dev-utils/clearConsole"); const formatWebpackMessages = require("react-dev-utils/formatWebpackMessages"); const typescriptFormatter = require("react-dev-utils/typescriptFormatter"); const forkTsCheckerWebpackPlugin = require("react-dev-utils/ForkTsCheckerWebpackPlugin"); // (cvle): Changed to false as we are sharing the tty with other processes. // const isInteractive = process.stdout.isTTY && false; const isInteractive = false; 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; let lanUrlForConfig; let 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({ appName, config, devSocket, urls, useYarn, useTypeScript, webpack, }) { // "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); } 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; const tsMessagesPromises = []; if (useTypeScript) { compiler.compilers.forEach(singleCompiler => { let tsMessagesPromise; let tsMessagesResolver; singleCompiler.hooks.beforeCompile.tap("beforeCompile", () => { tsMessagesPromise = new Promise(resolve => { tsMessagesResolver = msgs => resolve(msgs); }); tsMessagesPromises.push(tsMessagesPromise); }); const tsCheckerHooks = forkTsCheckerWebpackPlugin.getCompilerHooks( singleCompiler ); // now access hooks just like before // TODO: (cvle) this doesn't work currently. tsCheckerHooks.waiting.tap("yourListenerName", () => { console.log("waiting for typecheck results"); }); // TODO: (cvle) this doesn't work currently. tsCheckerHooks.receive.tap( "afterTypeScriptCheck", (diagnostics, lints) => { console.log("RECEIVED"); const allMsgs = [...diagnostics, ...lints]; const format = message => `${message.file}\n${typescriptFormatter(message, true)}`; tsMessagesResolver({ errors: allMsgs.filter(msg => msg.severity === "error").map(format), warnings: allMsgs .filter(msg => msg.severity === "warning") .map(format), }); } ); }); } // "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", async 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 statOptions = { all: false, warnings: true, errors: true, ...config.stats, }; if (Array.isArray(config)) { statOptions.children = config.map(c => c.stats || {}); } const statsData = stats.toJson(statOptions); if (useTypeScript && statsData.errors.length === 0) { const delayedMsg = setTimeout(() => { console.log( chalk.yellow( "Files successfully emitted, waiting for typecheck results..." ) ); }, 100); // Get back all tscheck results. const results = await Promise.all(tsMessagesPromises); clearTimeout(delayedMsg); results.forEach(msgs => { statsData.errors.push(...msgs.errors); statsData.warnings.push(...msgs.warnings); // Push errors and warnings into compilation result // to show them after page refresh triggered by user. stats.compilation.errors.push(...msgs.errors); stats.compilation.warnings.push(...msgs.warnings); if (msgs.errors.length > 0) { devSocket.errors(msgs.errors); } else if (msgs.warnings.length > 0) { devSocket.warnings(msgs.warnings); } }); if (isInteractive) { clearConsole(); } } const messages = formatWebpackMessages(statsData); 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; } module.exports = { createCompiler, prepareUrls, };