Skip to content

Commit ecd1f05

Browse files
tharakawjgaearon
authored andcommitted
Convert react-error-overlay to React (#2515)
* Convert react-error-overlay to React * Update compile-time error overlay to use react-error-overlay components * Refactor react-error-overlay components to container and presentational components. * Make the compile-time error overlay a part of react-error-overlay package. * Use react-error-overlay as dependency in react-dev-utils to show compile-time errors. * Run Prettier * Move the function name fix into StackFrame itself * Fix clicking on source code snippet to open the code in editor * Use exact objects + minor style tweak * Don't linkify frames that don't exist on the disk * Fix lint * Consolidate iframe rendering logic * Remove circular dependency between react-dev-utils and react-error-overlay * Fix lint * Fix decoupling of react-dev-utils and react-error-overlay by moving middleware * Deduplicate identical errors
1 parent 3b91748 commit ecd1f05

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1273
-1473
lines changed

packages/react-error-overlay/middleware.js packages/react-dev-utils/errorOverlayMiddleware.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
*/
99
'use strict';
1010

11-
const launchEditor = require('react-dev-utils/launchEditor');
11+
const launchEditor = require('./launchEditor');
12+
const launchEditorEndpoint = require('./launchEditorEndpoint');
1213

1314
module.exports = function createLaunchEditorMiddleware() {
1415
return function launchEditorMiddleware(req, res, next) {
15-
// Keep this in sync with react-error-overlay
16-
if (req.url.startsWith('/__open-stack-frame-in-editor')) {
16+
if (req.url.startsWith(launchEditorEndpoint)) {
1717
launchEditor(req.query.fileName, req.query.lineNumber);
1818
res.end();
1919
} else {

packages/react-error-overlay/src/utils/dom/consumeEvent.js packages/react-dev-utils/launchEditorEndpoint.js

+3-9
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,7 @@
66
* LICENSE file in the root directory of this source tree. An additional grant
77
* of patent rights can be found in the PATENTS file in the same directory.
88
*/
9+
'use strict';
910

10-
/* @flow */
11-
function consumeEvent(e: Event) {
12-
e.preventDefault();
13-
if (typeof e.target.blur === 'function') {
14-
e.target.blur();
15-
}
16-
}
17-
18-
export { consumeEvent };
11+
// TODO: we might want to make this injectable to support DEV-time non-root URLs.
12+
module.exports = '/__open-stack-frame-in-editor';

packages/react-dev-utils/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,20 @@
1111
"node": ">=6"
1212
},
1313
"files": [
14-
"ansiHTML.js",
1514
"checkRequiredFiles.js",
1615
"clearConsole.js",
1716
"crashOverlay.js",
1817
"crossSpawn.js",
1918
"eslintFormatter.js",
19+
"errorOverlayMiddleware.js",
2020
"FileSizeReporter.js",
2121
"printBuildError.js",
2222
"formatWebpackMessages.js",
2323
"getProcessForPort.js",
2424
"inquirer.js",
2525
"InterpolateHtmlPlugin.js",
2626
"launchEditor.js",
27+
"launchEditorEndpoint.js",
2728
"ModuleScopePlugin.js",
2829
"noopServiceWorkerMiddleware.js",
2930
"openBrowser.js",
@@ -35,7 +36,6 @@
3536
],
3637
"dependencies": {
3738
"address": "1.0.2",
38-
"anser": "1.4.1",
3939
"babel-code-frame": "6.22.0",
4040
"chalk": "1.1.3",
4141
"cross-spawn": "5.1.0",
@@ -44,10 +44,10 @@
4444
"filesize": "3.5.10",
4545
"global-modules": "1.0.0",
4646
"gzip-size": "3.0.0",
47-
"html-entities": "1.2.1",
4847
"inquirer": "3.2.1",
4948
"is-root": "1.0.0",
5049
"opn": "5.1.0",
50+
"react-error-overlay": "^1.0.9",
5151
"recursive-readdir": "2.2.1",
5252
"shell-quote": "1.6.1",
5353
"sockjs-client": "1.1.4",

packages/react-dev-utils/webpackHotDevClient.js

+21-137
Original file line numberDiff line numberDiff line change
@@ -21,143 +21,27 @@
2121
var SockJS = require('sockjs-client');
2222
var stripAnsi = require('strip-ansi');
2323
var url = require('url');
24+
var launchEditorEndpoint = require('./launchEditorEndpoint');
2425
var formatWebpackMessages = require('./formatWebpackMessages');
25-
var ansiHTML = require('./ansiHTML');
26-
27-
function createOverlayIframe(onIframeLoad) {
28-
var iframe = document.createElement('iframe');
29-
iframe.id = 'react-dev-utils-webpack-hot-dev-client-overlay';
30-
iframe.src = 'about:blank';
31-
iframe.style.position = 'fixed';
32-
iframe.style.left = 0;
33-
iframe.style.top = 0;
34-
iframe.style.right = 0;
35-
iframe.style.bottom = 0;
36-
iframe.style.width = '100vw';
37-
iframe.style.height = '100vh';
38-
iframe.style.border = 'none';
39-
iframe.style.zIndex = 2147483647;
40-
iframe.onload = onIframeLoad;
41-
return iframe;
42-
}
43-
44-
function addOverlayDivTo(iframe) {
45-
// TODO: unify these styles with react-error-overlay
46-
iframe.contentDocument.body.style.margin = 0;
47-
iframe.contentDocument.body.style.maxWidth = '100vw';
48-
49-
var outerDiv = iframe.contentDocument.createElement('div');
50-
outerDiv.id = 'react-dev-utils-webpack-hot-dev-client-overlay-div';
51-
outerDiv.style.width = '100%';
52-
outerDiv.style.height = '100%';
53-
outerDiv.style.boxSizing = 'border-box';
54-
outerDiv.style.textAlign = 'center';
55-
outerDiv.style.backgroundColor = 'rgb(255, 255, 255)';
56-
57-
var div = iframe.contentDocument.createElement('div');
58-
div.style.position = 'relative';
59-
div.style.display = 'inline-flex';
60-
div.style.flexDirection = 'column';
61-
div.style.height = '100%';
62-
div.style.width = '1024px';
63-
div.style.maxWidth = '100%';
64-
div.style.overflowX = 'hidden';
65-
div.style.overflowY = 'auto';
66-
div.style.padding = '0.5rem';
67-
div.style.boxSizing = 'border-box';
68-
div.style.textAlign = 'left';
69-
div.style.fontFamily = 'Consolas, Menlo, monospace';
70-
div.style.fontSize = '11px';
71-
div.style.whiteSpace = 'pre-wrap';
72-
div.style.wordBreak = 'break-word';
73-
div.style.lineHeight = '1.5';
74-
div.style.color = 'rgb(41, 50, 56)';
75-
76-
outerDiv.appendChild(div);
77-
iframe.contentDocument.body.appendChild(outerDiv);
78-
return div;
79-
}
80-
81-
function overlayHeaderStyle() {
82-
return (
83-
'font-size: 2em;' +
84-
'font-family: sans-serif;' +
85-
'color: rgb(206, 17, 38);' +
86-
'white-space: pre-wrap;' +
87-
'margin: 0 2rem 0.75rem 0px;' +
88-
'flex: 0 0 auto;' +
89-
'max-height: 35%;' +
90-
'overflow: auto;'
91-
);
92-
}
93-
94-
var overlayIframe = null;
95-
var overlayDiv = null;
96-
var lastOnOverlayDivReady = null;
97-
98-
function ensureOverlayDivExists(onOverlayDivReady) {
99-
if (overlayDiv) {
100-
// Everything is ready, call the callback right away.
101-
onOverlayDivReady(overlayDiv);
102-
return;
103-
}
104-
105-
// Creating an iframe may be asynchronous so we'll schedule the callback.
106-
// In case of multiple calls, last callback wins.
107-
lastOnOverlayDivReady = onOverlayDivReady;
108-
109-
if (overlayIframe) {
110-
// We're already creating it.
111-
return;
112-
}
113-
114-
// Create iframe and, when it is ready, a div inside it.
115-
overlayIframe = createOverlayIframe(function onIframeLoad() {
116-
overlayDiv = addOverlayDivTo(overlayIframe);
117-
// Now we can talk!
118-
lastOnOverlayDivReady(overlayDiv);
119-
});
120-
121-
// Zalgo alert: onIframeLoad() will be called either synchronously
122-
// or asynchronously depending on the browser.
123-
// We delay adding it so `overlayIframe` is set when `onIframeLoad` fires.
124-
document.body.appendChild(overlayIframe);
125-
}
26+
var ErrorOverlay = require('react-error-overlay');
27+
28+
ErrorOverlay.startReportingRuntimeErrors({
29+
launchEditorEndpoint: launchEditorEndpoint,
30+
onError: function() {
31+
// TODO: why do we need this?
32+
if (module.hot && typeof module.hot.decline === 'function') {
33+
module.hot.decline();
34+
}
35+
},
36+
});
12637

127-
function showErrorOverlay(message) {
128-
ensureOverlayDivExists(function onOverlayDivReady(overlayDiv) {
129-
// TODO: unify this with our runtime overlay
130-
overlayDiv.innerHTML =
131-
'<div style="' +
132-
overlayHeaderStyle() +
133-
'">Failed to compile</div>' +
134-
'<pre style="' +
135-
'display: block; padding: 0.5em; margin-top: 0; ' +
136-
'margin-bottom: 0.5em; overflow-x: auto; white-space: pre-wrap; ' +
137-
'border-radius: 0.25rem; background-color: rgba(206, 17, 38, 0.05)">' +
138-
'<code style="font-family: Consolas, Menlo, monospace;">' +
139-
ansiHTML(message) +
140-
'</code></pre>' +
141-
'<div style="' +
142-
'font-family: sans-serif; color: rgb(135, 142, 145); margin-top: 0.5rem; ' +
143-
'flex: 0 0 auto">' +
144-
'This error occurred during the build time and cannot be dismissed.</div>';
38+
if (module.hot && typeof module.hot.dispose === 'function') {
39+
module.hot.dispose(function() {
40+
// TODO: why do we need this?
41+
ErrorOverlay.stopReportingRuntimeErrors();
14542
});
14643
}
14744

148-
function destroyErrorOverlay() {
149-
if (!overlayDiv) {
150-
// It is not there in the first place.
151-
return;
152-
}
153-
154-
// Clean up and reset internal state.
155-
document.body.removeChild(overlayIframe);
156-
overlayDiv = null;
157-
overlayIframe = null;
158-
lastOnOverlayDivReady = null;
159-
}
160-
16145
// Connect to WebpackDevServer via a socket.
16246
var connection = new SockJS(
16347
url.format({
@@ -205,9 +89,9 @@ function handleSuccess() {
20589
// Attempt to apply hot updates or reload.
20690
if (isHotUpdate) {
20791
tryApplyUpdates(function onHotUpdateSuccess() {
208-
// Only destroy it when we're sure it's a hot update.
92+
// Only dismiss it when we're sure it's a hot update.
20993
// Otherwise it would flicker right before the reload.
210-
destroyErrorOverlay();
94+
ErrorOverlay.dismissBuildError();
21195
});
21296
}
21397
}
@@ -247,9 +131,9 @@ function handleWarnings(warnings) {
247131
// Only print warnings if we aren't refreshing the page.
248132
// Otherwise they'll disappear right away anyway.
249133
printWarnings();
250-
// Only destroy it when we're sure it's a hot update.
134+
// Only dismiss it when we're sure it's a hot update.
251135
// Otherwise it would flicker right before the reload.
252-
destroyErrorOverlay();
136+
ErrorOverlay.dismissBuildError();
253137
});
254138
} else {
255139
// Print initial warnings immediately.
@@ -271,7 +155,7 @@ function handleErrors(errors) {
271155
});
272156

273157
// Only show the first error.
274-
showErrorOverlay(formatted.errors[0]);
158+
ErrorOverlay.reportBuildError(formatted.errors[0]);
275159

276160
// Also log them to the console.
277161
if (typeof console !== 'undefined' && typeof console.error === 'function') {

packages/react-error-overlay/.flowconfig

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[ignore]
2+
.*/node_modules/eslint-plugin-jsx-a11y/.*
23

34
[include]
45
src/**/*.js

packages/react-error-overlay/fixtures/bundle.json

+14-14
Original file line numberDiff line numberDiff line change
@@ -240,11 +240,11 @@
240240
]
241241
},
242242
{
243-
"functionName": "Object.batchedUpdates",
243+
"functionName": "batchedUpdates",
244244
"fileName": "http://localhost:3000/static/js/bundle.js",
245245
"lineNumber": 33900,
246246
"columnNumber": 26,
247-
"_originalFunctionName": "Object.batchedUpdates",
247+
"_originalFunctionName": "batchedUpdates",
248248
"_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactDefaultBatchingStrategy.js",
249249
"_originalLineNumber": 62,
250250
"_originalColumnNumber": 0,
@@ -264,11 +264,11 @@
264264
]
265265
},
266266
{
267-
"functionName": "Object.batchedUpdates",
267+
"functionName": "batchedUpdates",
268268
"fileName": "http://localhost:3000/static/js/bundle.js",
269269
"lineNumber": 2181,
270270
"columnNumber": 27,
271-
"_originalFunctionName": "Object.batchedUpdates",
271+
"_originalFunctionName": "batchedUpdates",
272272
"_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactUpdates.js",
273273
"_originalLineNumber": 97,
274274
"_originalColumnNumber": 0,
@@ -288,11 +288,11 @@
288288
]
289289
},
290290
{
291-
"functionName": "Object._renderNewRootComponent",
291+
"functionName": "_renderNewRootComponent",
292292
"fileName": "http://localhost:3000/static/js/bundle.js",
293293
"lineNumber": 14398,
294294
"columnNumber": 18,
295-
"_originalFunctionName": "Object._renderNewRootComponent",
295+
"_originalFunctionName": "_renderNewRootComponent",
296296
"_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactMount.js",
297297
"_originalLineNumber": 320,
298298
"_originalColumnNumber": 0,
@@ -312,11 +312,11 @@
312312
]
313313
},
314314
{
315-
"functionName": "Object._renderSubtreeIntoContainer",
315+
"functionName": "_renderSubtreeIntoContainer",
316316
"fileName": "http://localhost:3000/static/js/bundle.js",
317317
"lineNumber": 14479,
318318
"columnNumber": 32,
319-
"_originalFunctionName": "Object._renderSubtreeIntoContainer",
319+
"_originalFunctionName": "_renderSubtreeIntoContainer",
320320
"_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactMount.js",
321321
"_originalLineNumber": 401,
322322
"_originalColumnNumber": 0,
@@ -336,11 +336,11 @@
336336
]
337337
},
338338
{
339-
"functionName": "Object.render",
339+
"functionName": "render",
340340
"fileName": "http://localhost:3000/static/js/bundle.js",
341341
"lineNumber": 14500,
342342
"columnNumber": 23,
343-
"_originalFunctionName": "Object.render",
343+
"_originalFunctionName": "render",
344344
"_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactMount.js",
345345
"_originalLineNumber": 422,
346346
"_originalColumnNumber": 0,
@@ -360,11 +360,11 @@
360360
]
361361
},
362362
{
363-
"functionName": "Object.friendlySyntaxErrorLabel",
363+
"functionName": null,
364364
"fileName": "http://localhost:3000/static/js/bundle.js",
365365
"lineNumber": 17287,
366366
"columnNumber": 20,
367-
"_originalFunctionName": "Object.friendlySyntaxErrorLabel",
367+
"_originalFunctionName": null,
368368
"_originalFileName": "webpack:///packages/react-scripts/template/src/index.js",
369369
"_originalLineNumber": 6,
370370
"_originalColumnNumber": 0,
@@ -432,11 +432,11 @@
432432
]
433433
},
434434
{
435-
"functionName": "Object.<anonymous>",
435+
"functionName": null,
436436
"fileName": "http://localhost:3000/static/js/bundle.js",
437437
"lineNumber": 41219,
438438
"columnNumber": 18,
439-
"_originalFunctionName": "Object.<anonymous>",
439+
"_originalFunctionName": null,
440440
"_originalFileName": null,
441441
"_originalLineNumber": null,
442442
"_originalColumnNumber": null,

packages/react-error-overlay/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
"anser": "1.4.1",
3535
"babel-code-frame": "6.22.0",
3636
"babel-runtime": "6.23.0",
37-
"react-dev-utils": "^3.1.0",
37+
"html-entities": "1.2.1",
38+
"react": "^15.5.4",
39+
"react-dom": "^15.5.4",
3840
"settle-promise": "1.0.0",
3941
"source-map": "0.5.6"
4042
},

packages/react-error-overlay/src/__tests__/stack-frame.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ test('proper empty shape', () => {
1313
const empty = new StackFrame();
1414
expect(empty).toMatchSnapshot();
1515

16-
expect(empty.getFunctionName()).toBe(null);
16+
expect(empty.getFunctionName()).toBe('(anonymous function)');
1717
expect(empty.getSource()).toBe('');
18-
expect(empty.toString()).toBe('');
18+
expect(empty.toString()).toBe('(anonymous function)');
1919
});
2020

2121
test('proper full shape', () => {

0 commit comments

Comments
 (0)