Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prettify errors and warnings for Webpack 2 #2113

Merged
merged 2 commits into from
May 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 77 additions & 41 deletions packages/react-dev-utils/formatWebpackMessages.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@ function isLikelyASyntaxError(message) {
}

// Cleans up webpack error messages.
function formatMessage(message) {
function formatMessage(message, isError) {
var lines = message.split('\n');

if (lines.length > 2 && lines[1] === '') {
// Remove extra newline.
lines.splice(1, 1);
}

// Remove webpack-specific loader notation from filename.
// Before:
// ./~/css-loader!./~/postcss-loader!./src/App.css
Expand All @@ -35,6 +40,15 @@ function formatMessage(message) {
lines[0] = lines[0].substr(lines[0].lastIndexOf('!') + 1);
}

lines = lines.filter(function(line) {
// Webpack adds a list of entry points to warning messages:
// @ ./src/index.js
// @ multi react-scripts/~/react-dev-utils/webpackHotDevClient.js ...
// It is misleading (and unrelated to the warnings) so we clean it up.
// It is only useful for syntax errors but we have beautiful frames for them.
return line.indexOf(' @ ') !== 0;
});

// line #0 is filename
// line #1 is the main error message
if (!lines[0] || !lines[1]) {
Expand All @@ -50,54 +64,76 @@ function formatMessage(message) {
.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
);
}

// Clean up export errors.
// TODO: we should really send a PR to Webpack for this.
var exportError = /\s*(.+?)\s*(")?export '(.+?)' was not found in '(.+?)'/;
if (lines[1].match(exportError)) {
lines[1] = lines[1].replace(
exportError,
"$1 '$4' does not contain an export named '$3'."
);
}

// TODO: Ideally we should write a custom ESLint formatter instead.

// If the second line already includes a filename, and it's a warning,
// this is likely coming from ESLint. Skip it because Webpack also prints it.
// Let's omit that in this case.
var BEGIN_ESLINT_FILENAME = String.fromCharCode(27) + '[4m';
// Also filter out ESLint summaries for each file
var BEGIN_ESLINT_WARNING_SUMMARY = String.fromCharCode(27) +
'[33m' +
String.fromCharCode(27) +
'[1m' +
String.fromCharCode(10006);
var BEGIN_ESLINT_ERROR_SUMMARY = String.fromCharCode(27) +
'[31m' +
String.fromCharCode(27) +
'[1m' +
String.fromCharCode(10006);
// ESLint puts separators like this between groups. We don't need them:
var ESLINT_EMPTY_SEPARATOR = String.fromCharCode(27) +
'[22m' +
String.fromCharCode(27) +
'[39m';
// Go!
lines = lines.filter(function(line) {
if (line === ESLINT_EMPTY_SEPARATOR) {
return false;
}
if (
line.indexOf(BEGIN_ESLINT_FILENAME) === 0 ||
line.indexOf(BEGIN_ESLINT_WARNING_SUMMARY) === 0 ||
line.indexOf(BEGIN_ESLINT_ERROR_SUMMARY) === 0
) {
return false;
}
return true;
});

// Prepend filename with an explanation.
lines[0] =
// Underline
String.fromCharCode(27) +
'[4m' +
// Filename
lines[0] +
// End underline
String.fromCharCode(27) +
'[24m' +
(isError ? ' contains errors.' : ' contains warnings.');

// Reassemble the message.
message = lines.join('\n');
// Internal stacks are generally useless so we strip them... with the
Expand All @@ -109,15 +145,15 @@ function formatMessage(message) {
''
); // at ... ...:x:y

return message;
return message.trim();
}

function formatWebpackMessages(json) {
var formattedErrors = json.errors.map(function(message) {
return 'Error in ' + formatMessage(message);
return formatMessage(message, true);
});
var formattedWarnings = json.warnings.map(function(message) {
return 'Warning in ' + formatMessage(message);
return formatMessage(message, false);
});
var result = {
errors: formattedErrors,
Expand Down
9 changes: 7 additions & 2 deletions packages/react-dev-utils/webpackHotDevClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,13 @@ function handleWarnings(warnings) {

function printWarnings() {
// Print warnings to the console.
for (var i = 0; i < warnings.length; i++) {
console.warn(stripAnsi(warnings[i]));
var formatted = formatWebpackMessages({
warnings: warnings,
errors: [],
});

for (var i = 0; i < formatted.warnings.length; i++) {
console.warn(stripAnsi(formatted.warnings[i]));
}
}

Expand Down