Skip to content

Commit de8e9b6

Browse files
committed
Reactor code to reuse open-in-editor functionality in both build and runtime error overlays
1 parent 51236e9 commit de8e9b6

File tree

8 files changed

+67
-58
lines changed

8 files changed

+67
-58
lines changed

packages/react-dev-utils/webpackHotDevClient.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ var launchEditorEndpoint = require('./launchEditorEndpoint');
2323
var formatWebpackMessages = require('./formatWebpackMessages');
2424
var ErrorOverlay = require('react-error-overlay');
2525

26+
ErrorOverlay.listenToOpenInEditor(function OpenInEditor({
27+
fileName,
28+
lineNumber,
29+
}) {
30+
fetch(
31+
`${launchEditorEndpoint}?fileName=` +
32+
window.encodeURIComponent(fileName) +
33+
'&lineNumber=' +
34+
window.encodeURIComponent(lineNumber || 1)
35+
).then(() => {}, () => {});
36+
});
37+
2638
// We need to keep track of if there has been a runtime error.
2739
// Essentially, we cannot guarantee application state was not corrupted by the
2840
// runtime error. To prevent confusing behavior, we forcibly reload the entire
@@ -31,7 +43,6 @@ var ErrorOverlay = require('react-error-overlay');
3143
// See https://github.com/facebookincubator/create-react-app/issues/3096
3244
var hadRuntimeError = false;
3345
ErrorOverlay.startReportingRuntimeErrors({
34-
launchEditorEndpoint: launchEditorEndpoint,
3546
onError: function() {
3647
hadRuntimeError = true;
3748
},

packages/react-error-overlay/src/containers/CompileErrorContainer.js

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,18 @@ const codeAnchorStyle = {
2121

2222
type Props = {|
2323
error: string,
24+
openInEditor: (errorLoc: ErrorLocation) => void,
2425
|};
2526

2627
class CompileErrorContainer extends PureComponent<Props, void> {
27-
openInEditor(errorLoc: ErrorLocation): void {
28-
const { filePath, lineNumber } = errorLoc;
29-
fetch(
30-
`/__open-stack-frame-in-editor?fileName=` +
31-
window.encodeURIComponent(filePath) +
32-
'&lineNumber=' +
33-
window.encodeURIComponent(lineNumber || 1)
34-
).then(() => {}, () => {});
35-
}
36-
3728
render() {
38-
const { error } = this.props;
29+
const { error, openInEditor } = this.props;
3930
const errLoc = parseCompileError(error);
4031
return (
4132
<ErrorOverlay>
4233
<Header headerText="Failed to compile" />
4334
<a
44-
onClick={errLoc ? () => this.openInEditor(errLoc) : null}
35+
onClick={errLoc ? () => openInEditor(errLoc) : null}
4536
style={errLoc ? codeAnchorStyle : null}
4637
>
4738
<CodeBlock main={true} codeHTML={generateAnsiHTML(error)} />

packages/react-error-overlay/src/containers/RuntimeError.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Header from '../components/Header';
1111
import StackTrace from './StackTrace';
1212

1313
import type { StackFrame } from '../utils/stack-frame';
14+
import type { ErrorLocation } from '../utils/parseCompileError';
1415

1516
const wrapperStyle = {
1617
display: 'flex',
@@ -26,10 +27,10 @@ export type ErrorRecord = {|
2627

2728
type Props = {|
2829
errorRecord: ErrorRecord,
29-
launchEditorEndpoint: ?string,
30+
openInEditor: (errorLoc: ErrorLocation) => void,
3031
|};
3132

32-
function RuntimeError({ errorRecord, launchEditorEndpoint }: Props) {
33+
function RuntimeError({ errorRecord, openInEditor }: Props) {
3334
const { error, unhandledRejection, contextSize, stackFrames } = errorRecord;
3435
const errorName = unhandledRejection
3536
? 'Unhandled Rejection (' + error.name + ')'
@@ -58,7 +59,7 @@ function RuntimeError({ errorRecord, launchEditorEndpoint }: Props) {
5859
stackFrames={stackFrames}
5960
errorName={errorName}
6061
contextSize={contextSize}
61-
launchEditorEndpoint={launchEditorEndpoint}
62+
openInEditor={openInEditor}
6263
/>
6364
</div>
6465
);

packages/react-error-overlay/src/containers/RuntimeErrorContainer.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ import RuntimeError from './RuntimeError';
1414
import Footer from '../components/Footer';
1515

1616
import type { ErrorRecord } from './RuntimeError';
17+
import type { ErrorLocation } from '../utils/parseCompileError';
1718

1819
type Props = {|
1920
errorRecords: ErrorRecord[],
2021
close: () => void,
21-
launchEditorEndpoint: ?string,
22+
openInEditor: (errorLoc: ErrorLocation) => void,
2223
|};
2324

2425
type State = {|
@@ -74,7 +75,7 @@ class RuntimeErrorContainer extends PureComponent<Props, State> {
7475
)}
7576
<RuntimeError
7677
errorRecord={errorRecords[this.state.currentIndex]}
77-
launchEditorEndpoint={this.props.launchEditorEndpoint}
78+
openInEditor={this.props.openInEditor}
7879
/>
7980
<Footer
8081
line1="This screen is visible only in development. It will not appear if the app crashes in production."

packages/react-error-overlay/src/containers/StackFrame.js

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { getPrettyURL } from '../utils/getPrettyURL';
1212
import { darkGray } from '../styles';
1313

1414
import type { StackFrame as StackFrameType } from '../utils/stack-frame';
15+
import type { ErrorLocation } from '../utils/parseCompileError';
1516

1617
const linkStyle = {
1718
fontSize: '0.9em',
@@ -45,10 +46,10 @@ const toggleStyle = {
4546

4647
type Props = {|
4748
frame: StackFrameType,
48-
launchEditorEndpoint: ?string,
4949
contextSize: number,
5050
critical: boolean,
5151
showCode: boolean,
52+
openInEditor: (errorLoc: ErrorLocation) => void,
5253
|};
5354

5455
type State = {|
@@ -66,42 +67,30 @@ class StackFrame extends Component<Props, State> {
6667
}));
6768
};
6869

69-
getEndpointUrl(): string | null {
70-
if (!this.props.launchEditorEndpoint) {
71-
return null;
72-
}
73-
const { _originalFileName: sourceFileName } = this.props.frame;
70+
getErrorLocation(): ErrorLocation | null {
71+
const {
72+
_originalFileName: fileName,
73+
_originalLineNumber: lineNumber,
74+
} = this.props.frame;
7475
// Unknown file
75-
if (!sourceFileName) {
76+
if (!fileName) {
7677
return null;
7778
}
7879
// e.g. "/path-to-my-app/webpack/bootstrap eaddeb46b67d75e4dfc1"
79-
const isInternalWebpackBootstrapCode =
80-
sourceFileName.trim().indexOf(' ') !== -1;
80+
const isInternalWebpackBootstrapCode = fileName.trim().indexOf(' ') !== -1;
8181
if (isInternalWebpackBootstrapCode) {
8282
return null;
8383
}
8484
// Code is in a real file
85-
return this.props.launchEditorEndpoint || null;
85+
return { fileName, lineNumber: lineNumber || 1 };
8686
}
8787

8888
openInEditor = () => {
89-
const endpointUrl = this.getEndpointUrl();
90-
if (endpointUrl === null) {
89+
const errorLoc = this.getErrorLocation();
90+
if (!errorLoc) {
9191
return;
9292
}
93-
94-
const {
95-
_originalFileName: sourceFileName,
96-
_originalLineNumber: sourceLineNumber,
97-
} = this.props.frame;
98-
// Keep this in sync with react-error-overlay/middleware.js
99-
fetch(
100-
`${endpointUrl}?fileName=` +
101-
window.encodeURIComponent(sourceFileName) +
102-
'&lineNumber=' +
103-
window.encodeURIComponent(sourceLineNumber || 1)
104-
).then(() => {}, () => {});
93+
this.props.openInEditor(errorLoc);
10594
};
10695

10796
onKeyDown = (e: SyntheticKeyboardEvent<>) => {
@@ -166,7 +155,7 @@ class StackFrame extends Component<Props, State> {
166155
}
167156
}
168157

169-
const canOpenInEditor = this.getEndpointUrl() !== null;
158+
const canOpenInEditor = this.getErrorLocation() !== null;
170159
return (
171160
<div>
172161
<div>{functionName}</div>

packages/react-error-overlay/src/containers/StackTrace.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { isInternalFile } from '../utils/isInternalFile';
1313
import { isBultinErrorName } from '../utils/isBultinErrorName';
1414

1515
import type { StackFrame as StackFrameType } from '../utils/stack-frame';
16+
import type { ErrorLocation } from '../utils/parseCompileError';
1617

1718
const traceStyle = {
1819
fontSize: '1em',
@@ -25,17 +26,12 @@ type Props = {|
2526
stackFrames: StackFrameType[],
2627
errorName: string,
2728
contextSize: number,
28-
launchEditorEndpoint: ?string,
29+
openInEditor: (errorLoc: ErrorLocation) => void,
2930
|};
3031

3132
class StackTrace extends Component<Props> {
3233
renderFrames() {
33-
const {
34-
stackFrames,
35-
errorName,
36-
contextSize,
37-
launchEditorEndpoint,
38-
} = this.props;
34+
const { stackFrames, errorName, contextSize, openInEditor } = this.props;
3935
const renderedFrames = [];
4036
let hasReachedAppCode = false,
4137
currentBundle = [],
@@ -59,7 +55,7 @@ class StackTrace extends Component<Props> {
5955
contextSize={contextSize}
6056
critical={index === 0}
6157
showCode={!shouldCollapse}
62-
launchEditorEndpoint={launchEditorEndpoint}
58+
openInEditor={openInEditor}
6359
/>
6460
);
6561
const lastElement = index === stackFrames.length - 1;

packages/react-error-overlay/src/index.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,35 @@ import { applyStyles } from './utils/dom/css';
1616
import iframeScript from 'iframeScript';
1717

1818
import type { ErrorRecord } from './listenToRuntimeErrors';
19+
import type { ErrorLocation } from './utils/parseCompileError';
1920

2021
type RuntimeReportingOptions = {|
2122
onError: () => void,
22-
launchEditorEndpoint: string,
2323
filename?: string,
2424
|};
2525

26+
type OpenInEditorListener = (errorLoc: ErrorLocation) => void;
27+
2628
let iframe: null | HTMLIFrameElement = null;
2729
let isLoadingIframe: boolean = false;
2830
var isIframeReady: boolean = false;
2931

32+
let openInEditorListener: null | OpenInEditorListener = null;
3033
let currentBuildError: null | string = null;
3134
let currentRuntimeErrorRecords: Array<ErrorRecord> = [];
3235
let currentRuntimeErrorOptions: null | RuntimeReportingOptions = null;
3336
let stopListeningToRuntimeErrors: null | (() => void) = null;
3437

38+
export function listenToOpenInEditor(listener: OpenInEditorListener) {
39+
openInEditorListener = listener;
40+
}
41+
42+
function openInEditor(errorLoc: ErrorLocation) {
43+
if (typeof openInEditorListener === 'function') {
44+
openInEditorListener(errorLoc);
45+
}
46+
}
47+
3548
export function reportBuildError(error: string) {
3649
currentBuildError = error;
3750
update();
@@ -46,6 +59,13 @@ export function startReportingRuntimeErrors(options: RuntimeReportingOptions) {
4659
if (stopListeningToRuntimeErrors !== null) {
4760
throw new Error('Already listening');
4861
}
62+
if (options.launchEditorEndpoint) {
63+
console.warn(
64+
'Warning: `startReportingRuntimeErrors` doesn’t accept ' +
65+
'`launchEditorEndpoint` argument anymore. Use `listenToOpenInEditor` ' +
66+
'instead with your own implementation to open errors in editor '
67+
);
68+
}
4969
currentRuntimeErrorOptions = options;
5070
listenToRuntimeErrors(errorRecord => {
5171
try {

packages/react-error-overlay/src/utils/parseCompileError.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import Anser from 'anser';
33

44
export type ErrorLocation = {|
5-
filePath: string,
5+
fileName: string,
66
lineNumber: number,
77
|};
88

@@ -23,15 +23,15 @@ const lineNumberRegexes = [
2323
// https://github.com/webpack/webpack/blob/v3.5.5/lib/Stats.js#L183-L217
2424
function parseCompileError(message: string): ?ErrorLocation {
2525
const lines: Array<string> = message.split('\n');
26-
let filePath: string = '';
26+
let fileName: string = '';
2727
let lineNumber: number = 0;
2828

2929
for (let i = 0; i < lines.length; i++) {
3030
const line: string = Anser.ansiToText(lines[i]).trim();
3131
if (!line) continue;
3232

33-
if (!filePath && line.match(filePathRegex)) {
34-
filePath = line;
33+
if (!fileName && line.match(filePathRegex)) {
34+
fileName = line;
3535
}
3636

3737
let k = 0;
@@ -44,10 +44,10 @@ function parseCompileError(message: string): ?ErrorLocation {
4444
k++;
4545
}
4646

47-
if (filePath && lineNumber) break;
47+
if (fileName && lineNumber) break;
4848
}
4949

50-
return filePath && lineNumber ? { filePath, lineNumber } : null;
50+
return fileName && lineNumber ? { fileName, lineNumber } : null;
5151
}
5252

5353
export default parseCompileError;

0 commit comments

Comments
 (0)