mirror of
https://github.com/zhigang1992/create-react-app.git
synced 2026-04-22 20:39:05 +08:00
Add syntax error overlay in development (#744)
* Add syntax error overlay in development * Support HMR being disabled * Tweak CSS
This commit is contained in:
@@ -178,3 +178,25 @@ prompt(
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### `webpackHotDevClient.js`
|
||||
|
||||
This is an alternative client for [WebpackDevServer](https://github.com/webpack/webpack-dev-server) that shows a syntax error overlay.
|
||||
|
||||
It currently supports only Webpack 1.x.
|
||||
|
||||
```js
|
||||
// Webpack development config
|
||||
module.exports = {
|
||||
// ...
|
||||
entry: [
|
||||
// You can replace the line below with these two lines if you prefer the
|
||||
// stock client:
|
||||
// require.resolve('webpack-dev-server/client') + '?/',
|
||||
// require.resolve('webpack/hot/dev-server'),
|
||||
'react-dev-utils/webpackHotDevClient',
|
||||
'src/index'
|
||||
],
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
133
packages/react-dev-utils/formatWebpackMessages.js
vendored
133
packages/react-dev-utils/formatWebpackMessages.js
vendored
@@ -7,50 +7,115 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
// WARNING: this code is untranspiled and is used in browser too.
|
||||
// Please make sure any changes are in ES5 or contribute a Babel compile step.
|
||||
|
||||
// Some custom utilities to prettify Webpack output.
|
||||
// This is a little hacky.
|
||||
// It would be easier if webpack provided a rich error object.
|
||||
// This is quite hacky and hopefully won't be needed when Webpack fixes this.
|
||||
// https://github.com/webpack/webpack/issues/2878
|
||||
|
||||
var friendlySyntaxErrorLabel = 'Syntax error:';
|
||||
|
||||
function isLikelyASyntaxError(message) {
|
||||
return message.indexOf(friendlySyntaxErrorLabel) !== -1;
|
||||
}
|
||||
|
||||
// Cleans up webpack error messages.
|
||||
function formatMessage(message) {
|
||||
return message
|
||||
// Make some common errors shorter:
|
||||
.replace(
|
||||
// Babel syntax error
|
||||
var lines = message.split('\n');
|
||||
|
||||
// line #0 is filename
|
||||
// line #1 is the main error message
|
||||
if (!lines[0] || !lines[1]) {
|
||||
return message;
|
||||
}
|
||||
|
||||
// Remove webpack-specific loader notation from filename.
|
||||
// Before:
|
||||
// ./~/css-loader!./~/postcss-loader!./src/App.css
|
||||
// After:
|
||||
// ./src/App.css
|
||||
if (lines[0].lastIndexOf('!') !== -1) {
|
||||
lines[0] = lines[0].substr(lines[0].lastIndexOf('!') + 1);
|
||||
}
|
||||
|
||||
// Cleans up verbose "module not found" messages for files and packages.
|
||||
if (lines[1].indexOf('Module not found: ') === 0) {
|
||||
lines = [
|
||||
lines[0],
|
||||
// Clean up message because "Module not found: " is descriptive enough.
|
||||
lines[1].replace(
|
||||
'Cannot resolve \'file\' or \'directory\' ', ''
|
||||
).replace(
|
||||
'Cannot resolve module ', ''
|
||||
).replace(
|
||||
'Error: ', ''
|
||||
),
|
||||
// Skip all irrelevant lines.
|
||||
// (For some reason they only appear on the client in browser.)
|
||||
'',
|
||||
lines[lines.length - 1] // error location is the last line
|
||||
]
|
||||
}
|
||||
|
||||
// Cleans up syntax error messages.
|
||||
if (lines[1].indexOf('Module build failed: ') === 0) {
|
||||
// For some reason, on the client messages appear duplicated:
|
||||
// https://github.com/webpack/webpack/issues/3008
|
||||
// This won't happen in Node but since we share this helpers,
|
||||
// we will dedupe them right here. We will ignore all lines
|
||||
// after the original error message text is repeated the second time.
|
||||
var errorText = lines[1].substr('Module build failed: '.length);
|
||||
var cleanedLines = [];
|
||||
var hasReachedDuplicateMessage = false;
|
||||
// Gather lines until we reach the beginning of duplicate message.
|
||||
lines.forEach(function(line, index) {
|
||||
if (
|
||||
// First time it occurs is fine.
|
||||
index !== 1 &&
|
||||
// line.endsWith(errorText)
|
||||
line.length >= errorText.length &&
|
||||
line.indexOf(errorText) === line.length - errorText.length
|
||||
) {
|
||||
// We see the same error message for the second time!
|
||||
// Filter out repeated error message and everything after it.
|
||||
hasReachedDuplicateMessage = true;
|
||||
}
|
||||
if (
|
||||
!hasReachedDuplicateMessage ||
|
||||
// Print last line anyway because it contains the source location
|
||||
index === lines.length - 1
|
||||
) {
|
||||
// This line is OK to appear in the output.
|
||||
cleanedLines.push(line);
|
||||
}
|
||||
});
|
||||
// We are clean now!
|
||||
lines = cleanedLines;
|
||||
// Finally, brush up the error message a little.
|
||||
lines[1] = lines[1].replace(
|
||||
'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!', '');
|
||||
);
|
||||
}
|
||||
|
||||
// Reassemble the message.
|
||||
message = lines.join('\n');
|
||||
// Internal stacks are generally useless so we strip them
|
||||
message = message.replace(
|
||||
/^\s*at\s.*:\d+:\d+[\s\)]*\n/gm, ''
|
||||
); // at ... ...:x:y
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
function formatWebpackMessages(stats) {
|
||||
var hasErrors = stats.hasErrors();
|
||||
var hasWarnings = stats.hasWarnings();
|
||||
if (!hasErrors && !hasWarnings) {
|
||||
return {
|
||||
errors: [],
|
||||
warnings: []
|
||||
};
|
||||
}
|
||||
// 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)
|
||||
);
|
||||
function formatWebpackMessages(json) {
|
||||
var formattedErrors = json.errors.map(function(message) {
|
||||
return 'Error in ' + formatMessage(message)
|
||||
});
|
||||
var formattedWarnings = json.warnings.map(function(message) {
|
||||
return 'Warning in ' + formatMessage(message)
|
||||
});
|
||||
var result = {
|
||||
errors: formattedErrors,
|
||||
warnings: formattedWarnings
|
||||
|
||||
@@ -18,12 +18,17 @@
|
||||
"openChrome.applescript",
|
||||
"openBrowser.js",
|
||||
"prompt.js",
|
||||
"WatchMissingNodeModulesPlugin.js"
|
||||
"WatchMissingNodeModulesPlugin.js",
|
||||
"webpackHotDevClient.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"ansi-html": "0.0.5",
|
||||
"chalk": "1.1.3",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"opn": "4.0.2"
|
||||
"html-entities": "1.2.0",
|
||||
"opn": "4.0.2",
|
||||
"sockjs-client": "1.0.3",
|
||||
"strip-ansi": "3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webpack": "^1.13.2"
|
||||
|
||||
230
packages/react-dev-utils/webpackHotDevClient.js
vendored
Normal file
230
packages/react-dev-utils/webpackHotDevClient.js
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// This alternative WebpackDevServer combines the functionality of:
|
||||
// https://github.com/webpack/webpack-dev-server/blob/webpack-1/client/index.js
|
||||
// https://github.com/webpack/webpack/blob/webpack-1/hot/dev-server.js
|
||||
|
||||
// It only supports their simplest configuration (hot updates on same server).
|
||||
// It makes some opinionated choices on top, like adding a syntax error overlay
|
||||
// that looks similar to our console output. The error overlay is inspired by:
|
||||
// https://github.com/glenjamin/webpack-hot-middleware
|
||||
|
||||
var ansiHTML = require('ansi-html');
|
||||
var SockJS = require('sockjs-client');
|
||||
var stripAnsi = require('strip-ansi');
|
||||
var url = require('url');
|
||||
var formatWebpackMessages = require('./formatWebpackMessages');
|
||||
var Entities = require('html-entities').AllHtmlEntities;
|
||||
var entities = new Entities();
|
||||
|
||||
// Color scheme inspired by https://github.com/glenjamin/webpack-hot-middleware
|
||||
var colors = {
|
||||
reset: ['transparent', 'transparent'],
|
||||
black: '181818',
|
||||
red: 'E36049',
|
||||
green: 'B3CB74',
|
||||
yellow: 'FFD080',
|
||||
blue: '7CAFC2',
|
||||
magenta: '7FACCA',
|
||||
cyan: 'C3C2EF',
|
||||
lightgrey: 'EBE7E3',
|
||||
darkgrey: '6D7891'
|
||||
};
|
||||
ansiHTML.setColors(colors);
|
||||
|
||||
function showErrorOverlay(message) {
|
||||
// Use an iframe so that document styles don't mess up the overlay.
|
||||
var iframeID = 'react-dev-utils-webpack-hot-dev-client-overlay';
|
||||
var iframe =
|
||||
document.getElementById(iframeID) ||
|
||||
document.createElement('iframe');
|
||||
iframe.id = iframeID;
|
||||
iframe.style.position = 'fixed';
|
||||
iframe.style.left = 0;
|
||||
iframe.style.top = 0;
|
||||
iframe.style.right = 0;
|
||||
iframe.style.bottom = 0;
|
||||
iframe.style.width = '100vw';
|
||||
iframe.style.height = '100vh';
|
||||
iframe.style.border = 'none';
|
||||
iframe.style.zIndex = 9999999999;
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
// Inside, make a div.
|
||||
var overlayID = 'react-dev-utils-webpack-hot-dev-client-overlay-div';
|
||||
var overlay =
|
||||
iframe.contentDocument.getElementById(overlayID) ||
|
||||
iframe.contentDocument.createElement('div');
|
||||
overlay.id = overlayID;
|
||||
overlay.style.position = 'fixed';
|
||||
overlay.style.left = 0;
|
||||
overlay.style.top = 0;
|
||||
overlay.style.right = 0;
|
||||
overlay.style.bottom = 0;
|
||||
overlay.style.width = '100vw';
|
||||
overlay.style.height = '100vh';
|
||||
overlay.style.backgroundColor = 'black';
|
||||
overlay.style.color = '#E8E8E8';
|
||||
overlay.style.fontFamily = 'Menlo, Consolas, monospace';
|
||||
overlay.style.fontSize = 'large';
|
||||
overlay.style.padding = '2rem';
|
||||
overlay.style.lineHeight = '1.2';
|
||||
overlay.style.whiteSpace = 'pre-wrap';
|
||||
overlay.style.overflow = 'auto';
|
||||
|
||||
// Make it look similar to our terminal.
|
||||
overlay.innerHTML =
|
||||
'<span style="color: #' +
|
||||
colors.red +
|
||||
'">Failed to compile.</span><br><br>' +
|
||||
ansiHTML(entities.encode(message));
|
||||
|
||||
// Render!
|
||||
iframe.contentDocument.body.appendChild(overlay);
|
||||
}
|
||||
|
||||
// Connect to WebpackDevServer via a socket.
|
||||
var connection = new SockJS(url.format({
|
||||
protocol: window.location.protocol,
|
||||
hostname: window.location.hostname,
|
||||
port: window.location.port,
|
||||
// Hardcoded in WebpackDevServer
|
||||
pathname: '/sockjs-node'
|
||||
}));
|
||||
// Note: unlike WebpackDevServer's built-in client,
|
||||
// we don't handle disconnect. If connection fails,
|
||||
// just leave it instead of spamming the console.
|
||||
|
||||
// Remember some state related to hot module replacement.
|
||||
var isFirstCompilation = true;
|
||||
var mostRecentCompilationHash = null;
|
||||
|
||||
// Successful compilation.
|
||||
function handleSuccess() {
|
||||
var isHotUpdate = !isFirstCompilation;
|
||||
isFirstCompilation = false;
|
||||
|
||||
// Attempt to apply hot updates or reload.
|
||||
if (isHotUpdate) {
|
||||
tryApplyUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
// Compilation with warnings (e.g. ESLint).
|
||||
function handleWarnings(warnings) {
|
||||
var isHotUpdate = !isFirstCompilation;
|
||||
isFirstCompilation = false;
|
||||
|
||||
function printWarnings() {
|
||||
// Print warnings to the console.
|
||||
for (var i = 0; i < warnings.length; i++) {
|
||||
console.warn(stripAnsi(warnings[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to apply hot updates or reload.
|
||||
if (isHotUpdate) {
|
||||
tryApplyUpdates(function onSuccessfulHotUpdate() {
|
||||
// Only print warnings if we aren't refreshing the page.
|
||||
// Otherwise they'll disappear right away anyway.
|
||||
printWarnings();
|
||||
});
|
||||
} else {
|
||||
// Print initial warnings immediately.
|
||||
printWarnings();
|
||||
}
|
||||
}
|
||||
|
||||
// Compilation with errors (e.g. syntax error or missing modules).
|
||||
function handleErrors(errors) {
|
||||
isFirstCompilation = false;
|
||||
|
||||
// "Massage" webpack messages.
|
||||
var formatted = formatWebpackMessages({
|
||||
errors: errors,
|
||||
warnings: []
|
||||
});
|
||||
|
||||
// Only show the first error.
|
||||
showErrorOverlay(formatted.errors[0]);
|
||||
// Do not attempt to reload now.
|
||||
// We will reload on next success instead.
|
||||
}
|
||||
|
||||
// There is a newer version of the code available.
|
||||
function handleAvailableHash(hash) {
|
||||
// Update last known compilation hash.
|
||||
mostRecentCompilationHash = hash;
|
||||
}
|
||||
|
||||
// Handle messages from the server.
|
||||
connection.onmessage = function(e) {
|
||||
var message = JSON.parse(e.data);
|
||||
switch (message.type) {
|
||||
case 'hash':
|
||||
handleAvailableHash(message.data);
|
||||
break;
|
||||
case 'ok':
|
||||
handleSuccess();
|
||||
break;
|
||||
case 'warnings':
|
||||
handleWarnings(message.data);
|
||||
break;
|
||||
case 'errors':
|
||||
handleErrors(message.data);
|
||||
break;
|
||||
default:
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
// Is there a newer version of this code available?
|
||||
function isUpdateAvailable() {
|
||||
/* globals __webpack_hash__ */
|
||||
// __webpack_hash__ is the hash of the current compilation.
|
||||
// It's a global variable injected by Webpack.
|
||||
return mostRecentCompilationHash !== __webpack_hash__;
|
||||
}
|
||||
|
||||
// Webpack disallows updates in other states.
|
||||
function canApplyUpdates() {
|
||||
return module.hot.status() === 'idle';
|
||||
}
|
||||
|
||||
// Attempt to update code on the fly, fall back to a hard reload.
|
||||
function tryApplyUpdates(onHotUpdateSuccess) {
|
||||
if (!module.hot) {
|
||||
// HotModuleReplacementPlugin is not in Webpack configuration.
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isUpdateAvailable() || !canApplyUpdates()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// https://webpack.github.io/docs/hot-module-replacement.html#check
|
||||
module.hot.check(/* autoApply */true, function(err, updatedModules) {
|
||||
if (err || !updatedModules) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof onHotUpdateSuccess === 'function') {
|
||||
// Maybe we want to do something.
|
||||
onHotUpdateSuccess();
|
||||
}
|
||||
|
||||
if (isUpdateAvailable()) {
|
||||
// While we were updating, there was a new update! Do it again.
|
||||
tryApplyUpdates();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -43,22 +43,18 @@ module.exports = {
|
||||
// This means they will be the "root" imports that are included in JS bundle.
|
||||
// The first two entry points enable "hot" CSS and auto-refreshes for JS.
|
||||
entry: [
|
||||
// Include WebpackDevServer client. It connects to WebpackDevServer via
|
||||
// sockets and waits for recompile notifications. When WebpackDevServer
|
||||
// recompiles, it sends a message to the client by socket. If only CSS
|
||||
// was changed, the app reload just the CSS. Otherwise, it will refresh.
|
||||
// The "?/" bit at the end tells the client to look for the socket at
|
||||
// the root path, i.e. /sockjs-node/. Otherwise visiting a client-side
|
||||
// route like /todos/42 would make it wrongly request /todos/42/sockjs-node.
|
||||
// The socket server is a part of WebpackDevServer which we are using.
|
||||
// The /sockjs-node/ path I'm referring to is hardcoded in WebpackDevServer.
|
||||
require.resolve('webpack-dev-server/client') + '?/',
|
||||
// Include Webpack hot module replacement runtime. Webpack is pretty
|
||||
// low-level so we need to put all the pieces together. The runtime listens
|
||||
// to the events received by the client above, and applies updates (such as
|
||||
// new CSS) to the running application.
|
||||
require.resolve('webpack/hot/dev-server'),
|
||||
// We ship a few polyfills by default.
|
||||
// Include an alternative client for WebpackDevServer. A client's job is to
|
||||
// connect to WebpackDevServer by a socket and get notified about changes.
|
||||
// When you save a file, the client will either apply hot updates (in case
|
||||
// of CSS changes), or refresh the page (in case of JS changes). When you
|
||||
// make a syntax error, this client will display a syntax error overlay.
|
||||
// Note: instead of the default WebpackDevServer client, we use a custom one
|
||||
// to bring better experience for Create React App users. You can replace
|
||||
// the line below with these two lines if you prefer the stock client:
|
||||
// require.resolve('webpack-dev-server/client') + '?/',
|
||||
// require.resolve('webpack/hot/dev-server'),
|
||||
require.resolve('react-dev-utils/webpackHotDevClient'),
|
||||
// We ship a few polyfills by default:
|
||||
require.resolve('./polyfills'),
|
||||
// Finally, this is your app's code:
|
||||
paths.appIndexJs
|
||||
|
||||
2
packages/react-scripts/scripts/start.js
vendored
2
packages/react-scripts/scripts/start.js
vendored
@@ -76,7 +76,7 @@ function setupCompiler(host, port, protocol) {
|
||||
// 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);
|
||||
var messages = formatWebpackMessages(stats.toJson({}, true));
|
||||
if (!messages.errors.length && !messages.warnings.length) {
|
||||
console.log(chalk.green('Compiled successfully!'));
|
||||
console.log();
|
||||
|
||||
Reference in New Issue
Block a user