Skip to content

Commit 32c796d

Browse files
lmontopodevongovett
authored andcommitted
Add overlay for build errors when using hmr (#1074)
1 parent ba93b87 commit 32c796d

3 files changed

Lines changed: 89 additions & 16 deletions

File tree

src/builtins/hmr-runtime.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
var OVERLAY_ID = '__parcel__error__overlay__';
2+
13
var global = (1, eval)('this');
24
var OldModule = module.bundle.Module;
35

@@ -49,14 +51,51 @@ if ((!parent || !parent.isParcelRequire) && typeof WebSocket !== 'undefined') {
4951

5052
if (data.type === 'error-resolved') {
5153
console.log('[parcel] ✨ Error resolved');
54+
55+
removeErrorOverlay();
5256
}
5357

5458
if (data.type === 'error') {
5559
console.error('[parcel] 🚨 ' + data.error.message + '\n' + data.error.stack);
60+
61+
removeErrorOverlay();
62+
63+
var overlay = createErrorOverlay(data);
64+
document.body.appendChild(overlay);
5665
}
5766
};
5867
}
5968

69+
function removeErrorOverlay() {
70+
var overlay = document.getElementById(OVERLAY_ID);
71+
if (overlay) {
72+
overlay.remove();
73+
}
74+
}
75+
76+
function createErrorOverlay(data) {
77+
var overlay = document.createElement('div');
78+
overlay.id = OVERLAY_ID;
79+
80+
// html encode message and stack trace
81+
var message = document.createElement('div');
82+
var stackTrace = document.createElement('pre');
83+
message.innerText = data.error.message;
84+
stackTrace.innerText = data.error.stack;
85+
86+
overlay.innerHTML = (
87+
'<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;">' +
88+
'<span style="background: red; padding: 2px 4px; border-radius: 2px;">ERROR</span>' +
89+
'<span style="top: 2px; margin-left: 5px; position: relative;">🚨</span>' +
90+
'<div style="font-size: 18px; font-weight: bold; margin-top: 20px;">' + message.innerHTML + '</div>' +
91+
'<pre>' + stackTrace.innerHTML + '</pre>' +
92+
'</div>'
93+
);
94+
95+
return overlay;
96+
97+
}
98+
6099
function getParents(bundle, id) {
61100
var modules = bundle.modules;
62101
if (!modules) {

test/hmr.js

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const promisify = require('../src/utils/promisify');
77
const ncp = promisify(require('ncp'));
88
const WebSocket = require('ws');
99
const json5 = require('json5');
10+
const sinon = require('sinon');
1011

1112
describe('hmr', function() {
1213
let b, ws;
@@ -299,20 +300,26 @@ describe('hmr', function() {
299300
assert.deepEqual(outputs, [3, 10]);
300301
});
301302

302-
it('should log emitted errors', async function() {
303+
it('should log emitted errors and show an error overlay', async function() {
303304
await ncp(__dirname + '/integration/commonjs', __dirname + '/input');
304305

305306
b = bundler(__dirname + '/input/index.js', {watch: true, hmr: true});
306307
let bundle = await b.bundle();
307308

308309
let logs = [];
309-
run(bundle, {
310-
console: {
311-
error(msg) {
312-
logs.push(msg);
310+
let ctx = run(
311+
bundle,
312+
{
313+
console: {
314+
error(msg) {
315+
logs.push(msg);
316+
}
313317
}
314-
}
315-
});
318+
},
319+
{require: false}
320+
);
321+
322+
let spy = sinon.spy(ctx.document.body, 'appendChild');
316323

317324
fs.writeFileSync(
318325
__dirname + '/input/local.js',
@@ -323,6 +330,7 @@ describe('hmr', function() {
323330

324331
assert.equal(logs.length, 1);
325332
assert(logs[0].trim().startsWith('[parcel] 🚨'));
333+
assert(spy.calledOnce);
326334
});
327335

328336
it('should log when errors resolve', async function() {
@@ -332,22 +340,32 @@ describe('hmr', function() {
332340
let bundle = await b.bundle();
333341

334342
let logs = [];
335-
run(bundle, {
336-
console: {
337-
error(msg) {
338-
logs.push(msg);
339-
},
340-
log(msg) {
341-
logs.push(msg);
343+
let ctx = run(
344+
bundle,
345+
{
346+
console: {
347+
error(msg) {
348+
logs.push(msg);
349+
},
350+
log(msg) {
351+
logs.push(msg);
352+
}
342353
}
343-
}
344-
});
354+
},
355+
{require: false}
356+
);
357+
358+
let appendSpy = sinon.spy(ctx.document.body, 'appendChild');
359+
let removeSpy = sinon.spy(ctx.document.getElementById('tmp'), 'remove');
345360

346361
fs.writeFileSync(
347362
__dirname + '/input/local.js',
348363
'require("fs"; exports.a = 5; exports.b = 5;'
349364
);
350365
await nextEvent(b, 'buildEnd');
366+
await sleep(50);
367+
368+
assert(appendSpy.called);
351369

352370
fs.writeFileSync(
353371
__dirname + '/input/local.js',
@@ -356,6 +374,8 @@ describe('hmr', function() {
356374
await nextEvent(b, 'buildEnd');
357375
await sleep(50);
358376

377+
assert(removeSpy.called);
378+
359379
assert.equal(logs.length, 2);
360380
assert(logs[0].trim().startsWith('[parcel] 🚨'));
361381
assert(logs[1].trim().startsWith('[parcel] ✨'));

test/utils.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ function bundle(file, opts) {
4646

4747
function prepareBrowserContext(bundle, globals) {
4848
// for testing dynamic imports
49+
const fakeElement = {
50+
remove() {}
51+
};
52+
4953
const fakeDocument = {
5054
createElement(tag) {
5155
return {tag};
@@ -68,6 +72,16 @@ function prepareBrowserContext(bundle, globals) {
6872
}
6973
}
7074
];
75+
},
76+
77+
getElementById() {
78+
return fakeElement;
79+
},
80+
81+
body: {
82+
appendChild() {
83+
return null;
84+
}
7185
}
7286
};
7387

0 commit comments

Comments
 (0)