diff --git a/config/jest/babelTransform.js b/config/jest/babelTransform.js index 145bd86cc9a..064fbf10290 100644 --- a/config/jest/babelTransform.js +++ b/config/jest/babelTransform.js @@ -1,3 +1,4 @@ +// @remove-file-on-eject /** * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. * diff --git a/config/webpackDevServer.config.js b/config/webpackDevServer.config.js new file mode 100644 index 00000000000..4d65567bd7d --- /dev/null +++ b/config/webpackDevServer.config.js @@ -0,0 +1,51 @@ +var config = require('./webpack.config.dev'); +var paths = require('./paths'); + +var protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; +var host = process.env.HOST || 'localhost'; + +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, +}; diff --git a/scripts/eject.js b/scripts/eject.js index 701907fa436..0358d4256f4 100644 --- a/scripts/eject.js +++ b/scripts/eject.js @@ -1,3 +1,4 @@ +// @remove-file-on-eject /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -7,13 +8,14 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -var createJestConfig = require('../utils/createJestConfig'); var fs = require('fs-extra'); var path = require('path'); -var paths = require('../config/paths'); -var prompt = require('react-dev-utils/prompt'); var spawnSync = require('cross-spawn').sync; var chalk = require('chalk'); +var prompt = require('react-dev-utils/prompt'); +var paths = require('../config/paths'); +var createJestConfig = require('./utils/createJestConfig'); + var green = chalk.green; var cyan = chalk.cyan; @@ -45,44 +47,48 @@ prompt( var folders = [ 'config', - path.join('config', 'jest'), - 'scripts' + 'config/jest', + 'scripts', + 'scripts/utils', ]; - var files = [ - path.join('config', 'env.js'), - path.join('config', 'paths.js'), - path.join('config', 'polyfills.js'), - path.join('config', 'webpack.config.dev.js'), - path.join('config', 'webpack.config.prod.js'), - path.join('config', 'jest', 'cssTransform.js'), - path.join('config', 'jest', 'fileTransform.js'), - path.join('scripts', 'build.js'), - path.join('scripts', 'start.js'), - path.join('scripts', 'test.js') - ]; + // Make shallow array of files paths + var files = folders.reduce(function (files, folder) { + return files.concat( + fs.readdirSync(path.join(ownPath, folder)) + // set full path + .map(file => path.join(ownPath, folder, file)) + // omit dirs from file list + .filter(file => fs.lstatSync(file).isFile()) + ); + }, []); // Ensure that the app folder is clean and we won't override any files folders.forEach(verifyAbsent); files.forEach(verifyAbsent); - // Copy the files over + console.log(); + console.log(cyan('Copying files into ' + appPath)); + folders.forEach(function(folder) { fs.mkdirSync(path.join(appPath, folder)) }); - console.log(); - console.log(cyan('Copying files into ' + appPath)); files.forEach(function(file) { - console.log(' Adding ' + cyan(file) + ' to the project'); - var content = fs - .readFileSync(path.join(ownPath, file), 'utf8') + var content = fs.readFileSync(file, 'utf8'); + + // Skip flagged files + if (content.match(/\/\/ @remove-file-on-eject/)) { + return; + } + content = content // Remove dead code from .js files on eject .replace(/\/\/ @remove-on-eject-begin([\s\S]*?)\/\/ @remove-on-eject-end/mg, '') // Remove dead code from .applescript files on eject .replace(/-- @remove-on-eject-begin([\s\S]*?)-- @remove-on-eject-end/mg, '') .trim() + '\n'; - fs.writeFileSync(path.join(appPath, file), content); + console.log(' Adding ' + cyan(file.replace(ownPath, '')) + ' to the project'); + fs.writeFileSync(file.replace(ownPath, appPath), content); }); console.log(); diff --git a/scripts/init.js b/scripts/init.js index aa62265ccc5..33c0777ef41 100644 --- a/scripts/init.js +++ b/scripts/init.js @@ -1,3 +1,4 @@ +// @remove-file-on-eject /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. diff --git a/scripts/start.js b/scripts/start.js index 5d2fd8a8523..9f6f645af10 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -17,21 +17,20 @@ process.env.NODE_ENV = 'development'; // https://github.com/motdotla/dotenv require('dotenv').config({silent: true}); +var fs = require('fs'); var chalk = require('chalk'); -var webpack = require('webpack'); -var WebpackDevServer = require('webpack-dev-server'); -var historyApiFallback = require('connect-history-api-fallback'); -var httpProxyMiddleware = require('http-proxy-middleware'); var detect = require('detect-port'); +var WebpackDevServer = require('webpack-dev-server'); var clearConsole = require('react-dev-utils/clearConsole'); var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); -var formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); var getProcessForPort = require('react-dev-utils/getProcessForPort'); var openBrowser = require('react-dev-utils/openBrowser'); var prompt = require('react-dev-utils/prompt'); -var fs = require('fs'); -var config = require('../config/webpack.config.dev'); var paths = require('../config/paths'); +var config = require('../config/webpack.config.dev'); +var devServerConfig = require('../config/webpackDevServer.config'); +var createWebpackCompiler = require('./utils/createWebpackCompiler'); +var addWebpackMiddleware = require('./utils/addWebpackMiddleware'); var useYarn = fs.existsSync(paths.yarnLockFile); var cli = useYarn ? 'yarn' : 'npm'; @@ -44,247 +43,31 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { // Tools like Cloud9 rely on this. var DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; -var compiler; -var handleCompile; - -// You can safely remove this after ejecting. -// We only use this block for testing of Create React App itself: -var isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1); -if (isSmokeTest) { - handleCompile = function (err, stats) { - if (err || stats.hasErrors() || stats.hasWarnings()) { - process.exit(1); - } else { - process.exit(0); - } - }; -} - -function setupCompiler(host, port, protocol) { - // "Compiler" is a low-level interface to Webpack. - // It lets us listen to some events and provide our own custom messages. - 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', function() { - if (isInteractive) { - clearConsole(); - } - console.log('Compiling...'); - }); - - var 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', function(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. - var messages = formatWebpackMessages(stats.toJson({}, true)); - var isSuccessful = !messages.errors.length && !messages.warnings.length; - var showInstructions = isSuccessful && (isInteractive || isFirstCompile); - - if (isSuccessful) { - console.log(chalk.green('Compiled successfully!')); - } - - if (showInstructions) { - console.log(); - console.log('The app is running at:'); - console.log(); - console.log(' ' + chalk.cyan(protocol + '://' + host + ':' + port + '/')); - 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(); - isFirstCompile = false; - } +function run(port) { + var protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; + var host = process.env.HOST || 'localhost'; - // 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(); - }); + // Create a webpack compiler that is configured with custom messages. + var compiler = createWebpackCompiler(config, function onReady(showInstructions) { + if (!showInstructions) { 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('You may use special comments to disable some warnings.'); - console.log('Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.'); - console.log('Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.'); - } - }); -} - -// 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 function(err, req, res){ - var 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 addMiddleware(devServer) { - // `proxy` lets you to specify a fallback server during development. - // Every unrecognized request will be forwarded to it. - var proxy = require(paths.appPackageJson).proxy; - 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', '*/*'] - })); - if (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); - } - - // 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 - var 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. - var hpm = httpProxyMiddleware(pathname => mayProxy.test(pathname), { - target: proxy, - logLevel: 'silent', - onProxyReq: function(proxyReq, req, res) { - // 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); - } - - // 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); -} - -function runDevServer(host, port, protocol) { - var devServer = new WebpackDevServer(compiler, { - // 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, + console.log('The app is running at:'); + console.log(); + console.log(' ' + chalk.cyan(protocol + '://' + host + ':' + port + '/')); + 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(); }); + // Serve webpack assets generated by the compiler over a web sever. + var devServer = new WebpackDevServer(compiler, devServerConfig); + // Our custom middleware proxies requests to /index.html or a remote API. - addMiddleware(devServer); + addWebpackMiddleware(devServer); // Launch WebpackDevServer. devServer.listen(port, (err, result) => { @@ -302,13 +85,6 @@ function runDevServer(host, port, protocol) { }); } -function run(port) { - var protocol = process.env.HTTPS === 'true' ? "https" : "http"; - var host = process.env.HOST || 'localhost'; - setupCompiler(host, port, protocol); - runDevServer(host, port, protocol); -} - // 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).then(port => { diff --git a/scripts/test.js b/scripts/test.js index 9de5181d739..dfd2c75c728 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -28,7 +28,7 @@ if (!process.env.CI && argv.indexOf('--coverage') < 0) { // @remove-on-eject-begin // This is not necessary after eject because we embed config into package.json. -const createJestConfig = require('../utils/createJestConfig'); +const createJestConfig = require('./utils/createJestConfig'); const path = require('path'); const paths = require('../config/paths'); argv.push('--config', JSON.stringify(createJestConfig( diff --git a/scripts/utils/addWebpackMiddleware.js b/scripts/utils/addWebpackMiddleware.js new file mode 100644 index 00000000000..16bf40c3709 --- /dev/null +++ b/scripts/utils/addWebpackMiddleware.js @@ -0,0 +1,97 @@ +var chalk = require('chalk'); +var historyApiFallback = require('connect-history-api-fallback'); +var httpProxyMiddleware = require('http-proxy-middleware'); +var 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 function(err, req, res){ + var 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 addWebpackMiddleware(devServer) { + // `proxy` lets you to specify a fallback server during development. + // Every unrecognized request will be forwarded to it. + var proxy = require(paths.appPackageJson).proxy; + 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', '*/*'] + })); + if (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); + } + + // 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 + var 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. + var hpm = httpProxyMiddleware(pathname => mayProxy.test(pathname), { + target: proxy, + logLevel: 'silent', + onProxyReq: function(proxyReq, req, res) { + // 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); + } + + // 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); +}; diff --git a/utils/createJestConfig.js b/scripts/utils/createJestConfig.js similarity index 94% rename from utils/createJestConfig.js rename to scripts/utils/createJestConfig.js index f1c67c018f1..c99345b858a 100644 --- a/utils/createJestConfig.js +++ b/scripts/utils/createJestConfig.js @@ -1,3 +1,4 @@ +// @remove-file-on-eject /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -7,10 +8,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -// Note: this file does not exist after ejecting. - const fs = require('fs'); -const paths = require('../config/paths'); +const paths = require('../../config/paths'); module.exports = (resolve, rootDir, isEjecting) => { // Use this instead of `paths.testsSetup` to avoid putting diff --git a/scripts/utils/createWebpackCompiler.js b/scripts/utils/createWebpackCompiler.js new file mode 100644 index 00000000000..932bfd54034 --- /dev/null +++ b/scripts/utils/createWebpackCompiler.js @@ -0,0 +1,98 @@ +var chalk = require('chalk'); +var webpack = require('webpack'); +var clearConsole = require('react-dev-utils/clearConsole'); +var formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); + +var isInteractive = process.stdout.isTTY; +var handleCompile; + +// You can safely remove this after ejecting. +// We only use this block for testing of Create React App itself: +var isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1); +if (isSmokeTest) { + handleCompile = function (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. + try { + var 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', function() { + if (isInteractive) { + clearConsole(); + } + console.log('Compiling...'); + }); + + var 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', function(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. + var messages = formatWebpackMessages(stats.toJson({}, true)); + var isSuccessful = !messages.errors.length && !messages.warnings.length; + var 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('You may use special comments to disable some warnings.'); + console.log('Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.'); + console.log('Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.'); + } + }); + + return compiler; +};