Skip to content

Commit

Permalink
[T-177] Update HMR and Dev Server to support full diagnostics (#3729)
Browse files Browse the repository at this point in the history
* update hmr server error handling

* update dev server

* Update ansi-html.js

* update ansi-html util back
  • Loading branch information
DeMoorJasper committed Nov 15, 2019
1 parent 4a449dc commit 4a3f534
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 84 deletions.
3 changes: 2 additions & 1 deletion packages/core/utils/package.json
Expand Up @@ -33,7 +33,8 @@
"nullthrows": "^1.1.1",
"resolve": "^1.12.0",
"serialize-to-js": "^1.1.1",
"terser": "^3.7.3"
"terser": "^3.7.3",
"ansi-html": "^0.0.7"
},
"devDependencies": {
"@babel/plugin-transform-flow-strip-types": "^7.2.0",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/utils/src/ansi-html.js
@@ -0,0 +1,6 @@
// @flow
import ansiHTML from 'ansi-html';

export function ansiHtml(ansi: string): string {
return ansiHTML(ansi);
}
2 changes: 2 additions & 0 deletions packages/core/utils/src/index.js
@@ -1,6 +1,7 @@
// @flow strict-local
export type * from './generateBundleReport';
export type * from './prettyError';
export type * from './prettyDiagnostic';

export {default as countLines} from './countLines';
export {default as DefaultMap} from './DefaultMap';
Expand Down Expand Up @@ -36,3 +37,4 @@ export * from './serializer';
export * from './stream';
export * from './resolve';
export * from './relativeBundlePath';
export * from './ansi-html';
2 changes: 1 addition & 1 deletion packages/core/utils/src/prettyDiagnostic.js
Expand Up @@ -5,7 +5,7 @@ import formatCodeFrame from '@parcel/codeframe';
import mdAnsi from '@parcel/markdown-ansi';
import path from 'path';

type AnsiDiagnosticResult = {|
export type AnsiDiagnosticResult = {|
message: string,
stack: string,
codeframe: string,
Expand Down
1 change: 0 additions & 1 deletion packages/reporters/dev-server/package.json
Expand Up @@ -19,7 +19,6 @@
"dependencies": {
"@parcel/plugin": "^2.0.0-alpha.2.1",
"@parcel/utils": "^2.0.0-alpha.2.1",
"ansi-html": "^0.0.7",
"connect": "^3.7.0",
"ejs": "^2.6.1",
"http-proxy-middleware": "^0.19.1",
Expand Down
44 changes: 29 additions & 15 deletions packages/reporters/dev-server/src/Server.js
Expand Up @@ -11,7 +11,13 @@ import path from 'path';
import http from 'http';
import https from 'https';
import url from 'url';
import {loadConfig, generateCertificate, getCertificate} from '@parcel/utils';
import {
loadConfig,
generateCertificate,
getCertificate,
prettyDiagnostic,
ansiHtml
} from '@parcel/utils';
import serverErrors from './serverErrors';
import fs from 'fs';
import ejs from 'ejs';
Expand Down Expand Up @@ -42,15 +48,18 @@ const TEMPLATE_500 = fs.readFileSync(
path.join(__dirname, 'templates/500.html'),
'utf8'
);

type NextFunction = (req: Request, res: Response, next?: (any) => any) => any;

export default class Server extends EventEmitter {
pending: boolean;
options: DevServerOptions;
rootPath: ?string;
bundleGraph: BundleGraph | null;
error: Diagnostic | null;
errors: Array<{|
message: string,
stack: string,
hints: Array<string>
|}> | null;
server: HTTPServer | HTTPSServer;

constructor(options: DevServerOptions) {
Expand All @@ -64,25 +73,35 @@ export default class Server extends EventEmitter {
}
this.pending = true;
this.bundleGraph = null;
this.error = null;
this.errors = null;
}

buildSuccess(bundleGraph: BundleGraph) {
this.bundleGraph = bundleGraph;
this.error = null;
this.errors = null;
this.pending = false;

this.emit('bundled');
}

buildError(diagnostics: Array<Diagnostic>) {
this.error = diagnostics[0];
this.errors = diagnostics.map(d => {
let ansiDiagnostic = prettyDiagnostic(d);

return {
message: ansiHtml(ansiDiagnostic.message),
stack: ansiDiagnostic.codeframe
? ansiHtml(ansiDiagnostic.codeframe)
: ansiHtml(ansiDiagnostic.stack),
hints: ansiDiagnostic.hints.map(hint => ansiHtml(hint))
};
});
}

respond(req: Request, res: Response) {
let {pathname} = url.parse(req.originalUrl || req.url);

if (this.error) {
if (this.errors) {
return this.send500(req, res);
} else if (
!pathname ||
Expand Down Expand Up @@ -238,17 +257,12 @@ export default class Server extends EventEmitter {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.writeHead(500);

if (this.error) {
res.end(
if (this.errors) {
return res.end(
ejs.render(TEMPLATE_500, {
error: {
message: this.error.message,
stack: this.error.stack
}
errors: this.errors
})
);
} else {
res.end();
}
}

Expand Down
51 changes: 30 additions & 21 deletions packages/reporters/dev-server/src/templates/500.html
Expand Up @@ -8,45 +8,54 @@
<style>
body {
margin: 0;
font-family: sans-serif;
}

.error-title-style {
display: flex;
.title-heading {
font-size: 2rem;
background-color: #fe0140;
color: #ffffff;
margin: 0 0 20px 0;
padding: 10px;
}

.error-title-emoji {
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
width: 75px;
.error-message {
font-size: 1.2rem;
color: #000000;
margin: 10px 5px;
}

.error-title-heading {
background-color: #FE0140;
color: #FFFFFF;
width: 100%;
font-size: 2rem;
padding: 10px;
margin: 0;
font-family: sans-serif;
.error-hints-container {
margin: 5px 0 20px 0;
}

.error-hint {
color: #282c33;
font-size: 1rem;
padding: 0 0 0 5px;
}

.error-stack-trace {
padding: 20px 0;
background-color: #282c33;
color: #c5ccdb;
font-family: monospace; white-space: pre;
font-family: monospace;
white-space: pre;
}
</style>
</head>
<body>
<div class="error-title-style">
<div class="error-title-emoji">🚨</div>
<h1 class="error-title-heading"><%- error.message %></h1>
</div>
<h1 class="title-heading">🚨 Parcel encountered errors</h1>
<% errors.forEach(function(error){ %>
<h2 class="error-message"><%- error.message %></h2>
<div class="error-stack-trace">
<%- error.stack %>
</div>
<div class="error-hints-container">
<% error.hints.forEach(function(hint){ %>
<div class="error-hint"><%- hint %></div>
<% }); %>
</div>
<% }); %>
</body>
</html>
1 change: 0 additions & 1 deletion packages/reporters/hmr-server/package.json
Expand Up @@ -20,7 +20,6 @@
"@parcel/plugin": "^2.0.0-alpha.2.1",
"@parcel/reporter-cli": "^2.0.0-alpha.2.1",
"@parcel/utils": "^2.0.0-alpha.2.1",
"ansi-html": "^0.0.7",
"ws": "^6.2.0"
},
"devDependencies": {
Expand Down
52 changes: 31 additions & 21 deletions packages/reporters/hmr-server/src/HMRServer.js
Expand Up @@ -2,13 +2,19 @@

import type {BuildSuccessEvent} from '@parcel/types';
import type {Diagnostic} from '@parcel/diagnostic';
import type {AnsiDiagnosticResult} from '@parcel/utils';
import type {Server, ServerError, HMRServerOptions} from './types.js.flow';

import http from 'http';
import https from 'https';
import WebSocket from 'ws';
import {getCertificate, generateCertificate} from '@parcel/utils';
import {md5FromObject} from '@parcel/utils';
import {
getCertificate,
generateCertificate,
md5FromObject,
prettyDiagnostic,
ansiHtml
} from '@parcel/utils';

type HMRAsset = {|
id: string,
Expand All @@ -18,17 +24,18 @@ type HMRAsset = {|
deps: Object
|};

type HMRError = {|
message: string,
stack?: string
|};

type HMRMessage = {|
type: string,
ansiError?: HMRError,
htmlError?: HMRError,
assets?: Array<HMRAsset>
|};
type HMRMessage =
| {|
type: 'update',
assets: Array<HMRAsset>
|}
| {|
type: 'error',
diagnostics: {|
ansi: Array<AnsiDiagnosticResult>,
html: Array<AnsiDiagnosticResult>
|}
|};

export default class HMRServer {
server: Server;
Expand Down Expand Up @@ -90,19 +97,22 @@ export default class HMRServer {
}

emitError(diagnostics: Array<Diagnostic>) {
let err = diagnostics[0];
let renderedDiagnostics = diagnostics.map(d => prettyDiagnostic(d));

// store the most recent error so we can notify new connections
// and so we can broadcast when the error is resolved
this.unresolvedError = {
type: 'error',
ansiError: {
message: err.message,
stack: err.stack
},
htmlError: {
message: err.message,
stack: err.stack
diagnostics: {
ansi: renderedDiagnostics,
html: renderedDiagnostics.map(d => {
return {
message: ansiHtml(d.message),
stack: ansiHtml(d.stack),
codeframe: ansiHtml(d.codeframe),
hints: d.hints.map(hint => ansiHtml(hint))
};
})
}
};

Expand Down
65 changes: 42 additions & 23 deletions packages/runtimes/hmr/src/loaders/hmr-runtime.js
Expand Up @@ -73,13 +73,25 @@ if ((!parent || !parent.isParcelRequire) && typeof WebSocket !== 'undefined') {
}

if (data.type === 'error') {
console.error(
'[parcel] 🚨 ' + data.ansiError.message + '\n' + data.ansiError.stack
);
// Log parcel errors to console
for (let ansiDiagnostic of data.diagnostics.ansi) {
let stack = ansiDiagnostic.codeframe
? ansiDiagnostic.codeframe
: ansiDiagnostic.stack;

console.error(
'🚨 [parcel]: ' +
ansiDiagnostic.message +
'\n' +
stack +
'\n\n' +
ansiDiagnostic.hints.join('\n')
);
}

// Render the fancy html overlay
removeErrorOverlay();

var overlay = createErrorOverlay(data);
var overlay = createErrorOverlay(data.diagnostics.html);
document.body.appendChild(overlay);
}
};
Expand All @@ -99,27 +111,34 @@ function removeErrorOverlay() {
}
}

function createErrorOverlay(data) {
function createErrorOverlay(diagnostics) {
var overlay = document.createElement('div');
overlay.id = OVERLAY_ID;

// html encode message and stack trace
var message = document.createElement('div');
var stackTrace = document.createElement('pre');
message.innerHTML = data.htmlError.message;
stackTrace.innerHTML = data.htmlError.stack;

overlay.innerHTML =
'<div style="background: black; font-size: 16px; color: white; position: fixed; height: 100%; width: 100%; top: 0px; left: 0px; padding: 30px; opacity: 0.85; font-family: Menlo, Consolas, monospace; z-index: 9999;">' +
'<span style="background: red; padding: 2px 4px; border-radius: 2px;">ERROR</span>' +
'<span style="top: 2px; margin-left: 5px; position: relative;">🚨</span>' +
'<div style="font-size: 18px; font-weight: bold; margin-top: 20px;">' +
message.innerHTML +
'</div>' +
'<pre>' +
stackTrace.innerHTML +
'</pre>' +
'</div>';
let errorHTML =
'<div style="background: black; opacity: 0.85; font-size: 16px; color: white; position: fixed; height: 100%; width: 100%; top: 0px; left: 0px; padding: 30px; font-family: Menlo, Consolas, monospace; z-index: 9999;">';

for (let diagnostic of diagnostics) {
let stack = diagnostic.codeframe ? diagnostic.codeframe : diagnostic.stack;

errorHTML += `
<div>
<div style="font-size: 18px; font-weight: bold; margin-top: 20px;">
🚨 ${diagnostic.message}
</div>
<pre>
${stack}
</pre>
<div>
${diagnostic.hints.map(hint => '<div>' + hint + '</div>').join('')}
</div>
</div>
`;
}

errorHTML += '</div>';

overlay.innerHTML = errorHTML;

return overlay;
}
Expand Down

0 comments on commit 4a3f534

Please sign in to comment.