62
ReactDebugInfo,
63
ReactComponentInfo,
64
ReactAsyncInfo,
65
+ ReactStackTrace,
66
+ ReactCallSite,
67
} from 'shared/ReactTypes';
68
import type {ReactElement} from 'shared/ReactElementType';
69
import type {LazyComponent} from 'react/src/ReactLazy';
79
requestStorage,
80
createHints,
81
initAsyncDebugInfo,
82
+ parseStackTrace,
83
} from './ReactFlightServerConfig';
84
85
import {
134
import {SuspenseException, getSuspendedThenable} from './ReactFlightThenable';
135
136
// TODO: Make this configurable on the Request.
134
-const externalRegExp = /\/node\_modules\/| \(node\:| node\:|\(\<anonymous\>\)/;
137
+const externalRegExp = /\/node\_modules\/|^node\:|^$/;
138
136
-function isNotExternal(stackFrame: string): boolean {
137
- return !externalRegExp.test(stackFrame);
139
+function isNotExternal(stackFrame: ReactCallSite): boolean {
140
+ const filename = stackFrame[1];
141
+ return !externalRegExp.test(filename);
142
}
143
140
-function prepareStackTrace(
141
- error: Error,
142
- structuredStackTrace: CallSite[],
143
-): string {
144
- const name = error.name || 'Error';
145
- const message = error.message || '';
146
- let stack = name + ': ' + message;
147
- for (let i = 0; i < structuredStackTrace.length; i++) {
148
- stack += '\n at ' + structuredStackTrace[i].toString();
149
- }
150
- return stack;
151
-}
152
-
153
-function getStack(error: Error): string {
154
- // We override Error.prepareStackTrace with our own version that normalizes
155
- // the stack to V8 formatting even if the server uses other formatting.
156
- // It also ensures that source maps are NOT applied to this since that can
157
- // be slow we're better off doing that lazily from the client instead of
158
- // eagerly on the server. If the stack has already been read, then we might
159
- // not get a normalized stack and it might still have been source mapped.
160
- // So the client still needs to be resilient to this.
161
- const previousPrepare = Error.prepareStackTrace;
162
- Error.prepareStackTrace = prepareStackTrace;
163
- try {
164
- // eslint-disable-next-line react-internal/safe-string-coercion
165
- return String(error.stack);
166
- } finally {
167
- Error.prepareStackTrace = previousPrepare;
168
- }
169
-}
170
-
171
-function filterDebugStack(error: Error): string {
144
+function filterStackTrace(error: Error, skipFrames: number): ReactStackTrace {
145
// Since stacks can be quite large and we pass a lot of them, we filter them out eagerly
146
// to save bandwidth even in DEV. We'll also replay these stacks on the client so by
147
// stripping them early we avoid that overhead. Otherwise we'd normally just rely on
148
// the DevTools or framework's ignore lists to filter them out.
176
- let stack = getStack(error);
177
- if (stack.startsWith('Error: react-stack-top-frame\n')) {
178
- // V8's default formatting prefixes with the error message which we
179
- // don't want/need.
180
- stack = stack.slice(29);
181
- }
182
- let idx = stack.indexOf('react-stack-bottom-frame');
183
- if (idx !== -1) {
184
- idx = stack.lastIndexOf('\n', idx);
185
- }
186
- if (idx !== -1) {
187
- // Cut off everything after the bottom frame since it'll be internals.
188
- stack = stack.slice(0, idx);
189
- }
190
- const frames = stack.split('\n').slice(1);
191
- return frames.filter(isNotExternal).join('\n');
149
+ return parseStackTrace(error, skipFrames).filter(isNotExternal);
150
}
151
152
initAsyncDebugInfo();
172
// Extract the stack. Not all console logs print the full stack but they have at
173
// least the line it was called from. We could optimize transfer by keeping just
174
// one stack frame but keeping it simple for now and include all frames.
217
- const stack = filterDebugStack(new Error('react-stack-top-frame'));
175
+ const stack = filterStackTrace(new Error('react-stack-top-frame'), 1);
176
request.pendingChunks++;
177
// We don't currently use this id for anything but we emit it so that we can later
178
// refer to previous logs in debug info to associate them with a component.
931
if (enableOwnerStacks) {
932
// $FlowFixMe[cannot-write]
933
componentDebugInfo.stack =
976
- task.debugStack === null ? null : filterDebugStack(task.debugStack);
934
+ task.debugStack === null ? null : filterStackTrace(task.debugStack, 1);
935
// $FlowFixMe[cannot-write]
936
componentDebugInfo.debugStack = task.debugStack;
937
// $FlowFixMe[cannot-write]
993
if (enableOwnerStacks) {
994
// $FlowFixMe[cannot-write]
995
componentDebugInfo.stack =
1038
- task.debugStack === null ? null : filterDebugStack(task.debugStack);
996
+ task.debugStack === null
997
+ ? null
998
+ : filterStackTrace(task.debugStack, 1);
999
// $FlowFixMe[cannot-write]
1000
componentDebugInfo.debugStack = task.debugStack;
1001
// $FlowFixMe[cannot-write]
1389
key,
1390
props,
1391
task.debugOwner,
1432
- task.debugStack === null ? null : filterDebugStack(task.debugStack),
1392
+ task.debugStack === null
1393
+ ? null
1394
+ : filterStackTrace(task.debugStack, 1),
1395
validated,
1396
]
1397
: [REACT_ELEMENT_TYPE, type, key, props, task.debugOwner]
2481
// $FlowFixMe[method-unbinding]
2482
typeof value.debugTask.run === 'function') ||
2483
value.debugStack instanceof Error) &&
2484
+ (enableOwnerStacks
2485
+ ? isArray((value: any).stack)
2486
+ : typeof (value: any).stack === 'undefined') &&
2487
typeof value.name === 'string' &&
2488
typeof value.env === 'string' &&
2524
- value.owner !== undefined &&
2525
- (enableOwnerStacks
2526
- ? typeof (value: any).stack === 'string'
2527
- : typeof (value: any).stack === 'undefined')
2489
+ value.owner !== undefined
2490
) {
2491
// This looks like a ReactComponentInfo. We can't serialize the ConsoleTask object so we
2492
// need to omit it before serializing.
2786
let row;
2787
if (__DEV__) {
2788
let reason = '';
2827
- let stack = '';
2789
+ let stack: ReactStackTrace;
2790
try {
2791
// eslint-disable-next-line react-internal/safe-string-coercion
2792
reason = String(postponeInstance.message);
2831
- stack = getStack(postponeInstance);
2832
- } catch (x) {}
2793
+ stack = filterStackTrace(postponeInstance, 0);
2794
+ } catch (x) {
2795
+ stack = [];
2796
+ }
2797
row = serializeRowHeader('P', id) + stringify({reason, stack}) + '\n';
2798
} else {
2799
// No reason included in prod.
2812
let errorInfo: any;
2813
if (__DEV__) {
2814
let message;
2851
- let stack = '';
2815
+ let stack: ReactStackTrace;
2816
let env = request.environmentName();
2817
try {
2818
if (error instanceof Error) {
2819
// eslint-disable-next-line react-internal/safe-string-coercion
2820
message = String(error.message);
2857
- stack = getStack(error);
2821
+ stack = filterStackTrace(error, 0);
2822
const errorEnv = (error: any).environmentName;
2823
if (typeof errorEnv === 'string') {
2824
// This probably came from another FlightClient as a pass through.
2827
}
2828
} else if (typeof error === 'object' && error !== null) {
2829
message = describeObjectForErrorMessage(error);
2830
+ stack = [];
2831
} else {
2832
// eslint-disable-next-line react-internal/safe-string-coercion
2833
message = String(error);
2834
+ stack = [];
2835
}
2836
} catch (x) {
2837
message = 'An error occurred but serializing the error message failed.';
3282
id: number,
3283
methodName: string,
3284
owner: null | ReactComponentInfo,
3319
- stackTrace: string,
3285
+ stackTrace: ReactStackTrace,
3286
args: Array<any>,
3287
): void {
3288
if (!__DEV__) {