mirror of
https://github.com/zhigang1992/create-react-app.git
synced 2026-03-29 22:38:26 +08:00
283 lines
10 KiB
JavaScript
283 lines
10 KiB
JavaScript
// @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
|
||
|
||
process.env.NODE_ENV = 'development';
|
||
|
||
var path = require('path');
|
||
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 execSync = require('child_process').execSync;
|
||
var opn = require('opn');
|
||
var detect = require('detect-port');
|
||
var prompt = require('./utils/prompt');
|
||
var config = require('../config/webpack.config.dev');
|
||
var paths = require('../config/paths');
|
||
|
||
// Tools like Cloud9 rely on this.
|
||
var DEFAULT_PORT = process.env.PORT || 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);
|
||
}
|
||
};
|
||
}
|
||
|
||
// Some custom utilities to prettify Webpack output.
|
||
// This is a little hacky.
|
||
// It would be easier if webpack provided a rich error object.
|
||
var friendlySyntaxErrorLabel = 'Syntax error:';
|
||
function isLikelyASyntaxError(message) {
|
||
return message.indexOf(friendlySyntaxErrorLabel) !== -1;
|
||
}
|
||
function formatMessage(message) {
|
||
return message
|
||
// Make some common errors shorter:
|
||
.replace(
|
||
// Babel syntax error
|
||
'Module build failed: SyntaxError:',
|
||
friendlySyntaxErrorLabel
|
||
)
|
||
.replace(
|
||
// Webpack file not found error
|
||
/Module not found: Error: Cannot resolve 'file' or 'directory'/,
|
||
'Module not found:'
|
||
)
|
||
// Internal stacks are generally useless so we strip them
|
||
.replace(/^\s*at\s.*:\d+:\d+[\s\)]*\n/gm, '') // at ... ...:x:y
|
||
// Webpack loader names obscure CSS filenames
|
||
.replace('./~/css-loader!./~/postcss-loader!', '');
|
||
}
|
||
|
||
function clearConsole() {
|
||
// This seems to work best on Windows and other systems.
|
||
// The intention is to clear the output so you can focus on most recent build.
|
||
process.stdout.write('\x1bc');
|
||
}
|
||
|
||
function setupCompiler(port) {
|
||
// "Compiler" is a low-level interface to Webpack.
|
||
// It lets us listen to some events and provide our own custom messages.
|
||
compiler = webpack(config, handleCompile);
|
||
|
||
// "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() {
|
||
clearConsole();
|
||
console.log('Compiling...');
|
||
});
|
||
|
||
// "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) {
|
||
clearConsole();
|
||
var hasErrors = stats.hasErrors();
|
||
var hasWarnings = stats.hasWarnings();
|
||
if (!hasErrors && !hasWarnings) {
|
||
console.log(chalk.green('Compiled successfully!'));
|
||
console.log();
|
||
console.log('The app is running at:');
|
||
console.log();
|
||
console.log(' ' + chalk.cyan('http://localhost:' + port + '/'));
|
||
console.log();
|
||
console.log('Note that the development build is not optimized.');
|
||
console.log('To create a production build, use ' + chalk.cyan('npm run build') + '.');
|
||
console.log();
|
||
return;
|
||
}
|
||
|
||
// 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 use stats.toJson({}, true) to make output more compact and readable:
|
||
// https://github.com/facebookincubator/create-react-app/issues/401#issuecomment-238291901
|
||
var json = stats.toJson({}, true);
|
||
var formattedErrors = json.errors.map(message =>
|
||
'Error in ' + formatMessage(message)
|
||
);
|
||
var formattedWarnings = json.warnings.map(message =>
|
||
'Warning in ' + formatMessage(message)
|
||
);
|
||
if (hasErrors) {
|
||
console.log(chalk.red('Failed to compile.'));
|
||
console.log();
|
||
if (formattedErrors.some(isLikelyASyntaxError)) {
|
||
// If there are any syntax errors, show just them.
|
||
// This prevents a confusing ESLint parsing error
|
||
// preceding a much more useful Babel syntax error.
|
||
formattedErrors = formattedErrors.filter(isLikelyASyntaxError);
|
||
}
|
||
formattedErrors.forEach(message => {
|
||
console.log(message);
|
||
console.log();
|
||
});
|
||
// If errors exist, ignore warnings.
|
||
return;
|
||
}
|
||
if (hasWarnings) {
|
||
console.log(chalk.yellow('Compiled with warnings.'));
|
||
console.log();
|
||
formattedWarnings.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.');
|
||
}
|
||
});
|
||
}
|
||
|
||
function openBrowser(port) {
|
||
if (process.platform === 'darwin') {
|
||
try {
|
||
// Try our best to reuse existing tab
|
||
// on OS X Google Chrome with AppleScript
|
||
execSync('ps cax | grep "Google Chrome"');
|
||
execSync(
|
||
'osascript chrome.applescript http://localhost:' + port + '/',
|
||
{cwd: path.join(__dirname, 'utils'), stdio: 'ignore'}
|
||
);
|
||
return;
|
||
} catch (err) {
|
||
// Ignore errors.
|
||
}
|
||
}
|
||
// Fallback to opn
|
||
// (It will always open new tab)
|
||
opn('http://localhost:' + port + '/');
|
||
}
|
||
|
||
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({
|
||
// Allow paths with dots in them to be loaded, reference issue #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 won’t 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://www.debuggex.com/ to visualize the regex
|
||
var mayProxy = /^(?!\/(index\.html$|.*\.hot-update\.json$|sockjs-node\/)).*$/;
|
||
devServer.use(mayProxy,
|
||
// Pass the scope regex both to Express and to the middleware for proxying
|
||
// of both HTTP and WebSockets to work without false positives.
|
||
httpProxyMiddleware(pathname => mayProxy.test(pathname), {
|
||
target: proxy,
|
||
logLevel: 'silent',
|
||
secure: false,
|
||
changeOrigin: true
|
||
})
|
||
);
|
||
}
|
||
// 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(port) {
|
||
var devServer = new WebpackDevServer(compiler, {
|
||
// 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/
|
||
}
|
||
});
|
||
|
||
// Our custom middleware proxies requests to /index.html or a remote API.
|
||
addMiddleware(devServer);
|
||
|
||
// Launch WebpackDevServer.
|
||
devServer.listen(port, (err, result) => {
|
||
if (err) {
|
||
return console.log(err);
|
||
}
|
||
|
||
clearConsole();
|
||
console.log(chalk.cyan('Starting the development server...'));
|
||
console.log();
|
||
openBrowser(port);
|
||
});
|
||
}
|
||
|
||
function run(port) {
|
||
setupCompiler(port);
|
||
runDevServer(port);
|
||
}
|
||
|
||
// 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 => {
|
||
if (port === DEFAULT_PORT) {
|
||
run(port);
|
||
return;
|
||
}
|
||
|
||
clearConsole();
|
||
var question =
|
||
chalk.yellow('Something is already running on port ' + DEFAULT_PORT + '.') +
|
||
'\n\nWould you like to run the app on another port instead?';
|
||
|
||
prompt(question, true).then(shouldChangePort => {
|
||
if (shouldChangePort) {
|
||
run(port);
|
||
}
|
||
});
|
||
});
|