@@ -63,6 +63,7 @@ import type {
63
63
ReactComponentInfo ,
64
64
ReactAsyncInfo ,
65
65
ReactStackTrace ,
66
+ ReactCallSite ,
66
67
} from 'shared/ReactTypes' ;
67
68
import type { ReactElement } from 'shared/ReactElementType' ;
68
69
import type { LazyComponent } from 'react/src/ReactLazy' ;
@@ -78,6 +79,7 @@ import {
78
79
requestStorage ,
79
80
createHints ,
80
81
initAsyncDebugInfo ,
82
+ parseStackTrace ,
81
83
} from './ReactFlightServerConfig' ;
82
84
83
85
import {
@@ -132,83 +134,19 @@ import binaryToComparableString from 'shared/binaryToComparableString';
132
134
import { SuspenseException , getSuspendedThenable } from './ReactFlightThenable' ;
133
135
134
136
// TODO: Make this configurable on the Request.
135
- const externalRegExp = / \/ n o d e \_ m o d u l e s \/ | \( n o d e \: | n o d e \: | \( \< a n o n y m o u s \> \) / ;
137
+ const externalRegExp = / \/ n o d e \_ m o d u l e s \/ | ^ n o d e \: | ^ $ / ;
136
138
137
- function isNotExternal ( stackFrame : string ) : boolean {
138
- return ! externalRegExp . test ( stackFrame ) ;
139
+ function isNotExternal ( stackFrame : ReactCallSite ) : boolean {
140
+ const filename = stackFrame [ 1 ] ;
141
+ return ! externalRegExp . test ( filename ) ;
139
142
}
140
143
141
- function prepareStackTrace (
142
- error : Error ,
143
- structuredStackTrace : CallSite [ ] ,
144
- ) : string {
145
- const name = error . name || 'Error' ;
146
- const message = error . message || '' ;
147
- let stack = name + ': ' + message ;
148
- for ( let i = 0 ; i < structuredStackTrace . length ; i ++ ) {
149
- stack += '\n at ' + structuredStackTrace [ i ] . toString ( ) ;
150
- }
151
- return stack ;
152
- }
153
-
154
- function getStack ( error : Error ) : string {
155
- // We override Error.prepareStackTrace with our own version that normalizes
156
- // the stack to V8 formatting even if the server uses other formatting.
157
- // It also ensures that source maps are NOT applied to this since that can
158
- // be slow we're better off doing that lazily from the client instead of
159
- // eagerly on the server. If the stack has already been read, then we might
160
- // not get a normalized stack and it might still have been source mapped.
161
- // So the client still needs to be resilient to this.
162
- const previousPrepare = Error . prepareStackTrace ;
163
- Error . prepareStackTrace = prepareStackTrace ;
164
- try {
165
- // eslint-disable-next-line react-internal/safe-string-coercion
166
- return String ( error . stack ) ;
167
- } finally {
168
- Error . prepareStackTrace = previousPrepare ;
169
- }
170
- }
171
-
172
- // This matches either of these V8 formats.
173
- // at name (filename:0:0)
174
- // at filename:0:0
175
- // at async filename:0:0
176
- const frameRegExp =
177
- / ^ { 3 } a t (?: ( .+ ) \( ( [ ^ \) ] + ) : ( \d + ) : ( \d + ) \) | (?: a s y n c ) ? ( [ ^ \) ] + ) : ( \d + ) : ( \d + ) ) $ / ;
178
-
179
- function parseStackTrace ( error : Error , skipFrames : number ) : ReactStackTrace {
144
+ function filterStackTrace ( error : Error , skipFrames : number ) : ReactStackTrace {
180
145
// Since stacks can be quite large and we pass a lot of them, we filter them out eagerly
181
146
// to save bandwidth even in DEV. We'll also replay these stacks on the client so by
182
147
// stripping them early we avoid that overhead. Otherwise we'd normally just rely on
183
148
// the DevTools or framework's ignore lists to filter them out.
184
- let stack = getStack ( error ) ;
185
- if ( stack . startsWith ( 'Error: react-stack-top-frame\n' ) ) {
186
- // V8's default formatting prefixes with the error message which we
187
- // don't want/need.
188
- stack = stack . slice ( 29 ) ;
189
- }
190
- let idx = stack . indexOf ( 'react-stack-bottom-frame' ) ;
191
- if ( idx !== - 1 ) {
192
- idx = stack . lastIndexOf ( '\n' , idx ) ;
193
- }
194
- if ( idx !== - 1 ) {
195
- // Cut off everything after the bottom frame since it'll be internals.
196
- stack = stack . slice ( 0 , idx ) ;
197
- }
198
- const frames = stack . split ( '\n' ) . slice ( skipFrames ) . filter ( isNotExternal ) ;
199
- const parsedFrames : ReactStackTrace = [ ] ;
200
- for ( let i = 0 ; i < frames . length ; i ++ ) {
201
- const parsed = frameRegExp . exec ( frames [ i ] ) ;
202
- if ( ! parsed ) {
203
- continue ;
204
- }
205
- const name = parsed [ 1 ] || '' ;
206
- const filename = parsed [ 2 ] || parsed [ 5 ] || '' ;
207
- const line = + ( parsed [ 3 ] || parsed [ 6 ] ) ;
208
- const col = + ( parsed [ 4 ] || parsed [ 7 ] ) ;
209
- parsedFrames . push ( [ name , filename , line , col ] ) ;
210
- }
211
- return parsedFrames ;
149
+ return parseStackTrace ( error , skipFrames ) . filter ( isNotExternal ) ;
212
150
}
213
151
214
152
initAsyncDebugInfo ( ) ;
@@ -234,7 +172,7 @@ function patchConsole(consoleInst: typeof console, methodName: string) {
234
172
// Extract the stack. Not all console logs print the full stack but they have at
235
173
// least the line it was called from. We could optimize transfer by keeping just
236
174
// one stack frame but keeping it simple for now and include all frames.
237
- const stack = parseStackTrace ( new Error ( 'react-stack-top-frame' ) , 1 ) ;
175
+ const stack = filterStackTrace ( new Error ( 'react-stack-top-frame' ) , 1 ) ;
238
176
request . pendingChunks ++ ;
239
177
// We don't currently use this id for anything but we emit it so that we can later
240
178
// refer to previous logs in debug info to associate them with a component.
@@ -993,7 +931,7 @@ function callWithDebugContextInDEV<A, T>(
993
931
if ( enableOwnerStacks ) {
994
932
// $FlowFixMe[cannot-write]
995
933
componentDebugInfo . stack =
996
- task . debugStack === null ? null : parseStackTrace ( task . debugStack , 1 ) ;
934
+ task . debugStack === null ? null : filterStackTrace ( task . debugStack , 1 ) ;
997
935
// $FlowFixMe[cannot-write]
998
936
componentDebugInfo . debugStack = task . debugStack ;
999
937
// $FlowFixMe[cannot-write]
@@ -1055,7 +993,9 @@ function renderFunctionComponent<Props>(
1055
993
if ( enableOwnerStacks ) {
1056
994
// $FlowFixMe[cannot-write]
1057
995
componentDebugInfo. stack =
1058
- task . debugStack === null ? null : parseStackTrace ( task . debugStack , 1 ) ;
996
+ task . debugStack === null
997
+ ? null
998
+ : filterStackTrace ( task . debugStack , 1 ) ;
1059
999
// $FlowFixMe[cannot-write]
1060
1000
componentDebugInfo . debugStack = task . debugStack ;
1061
1001
// $FlowFixMe[cannot-write]
@@ -1449,7 +1389,9 @@ function renderClientElement(
1449
1389
key ,
1450
1390
props ,
1451
1391
task . debugOwner ,
1452
- task . debugStack === null ? null : parseStackTrace ( task . debugStack , 1 ) ,
1392
+ task . debugStack === null
1393
+ ? null
1394
+ : filterStackTrace ( task . debugStack , 1 ) ,
1453
1395
validated ,
1454
1396
]
1455
1397
: [ REACT_ELEMENT_TYPE , type , key , props , task . debugOwner ]
@@ -2848,7 +2790,7 @@ function emitPostponeChunk(
2848
2790
try {
2849
2791
// eslint-disable-next-line react-internal/safe-string-coercion
2850
2792
reason = String ( postponeInstance . message ) ;
2851
- stack = parseStackTrace ( postponeInstance , 0 ) ;
2793
+ stack = filterStackTrace ( postponeInstance , 0 ) ;
2852
2794
} catch ( x ) {
2853
2795
stack = [ ] ;
2854
2796
}
@@ -2876,7 +2818,7 @@ function emitErrorChunk(
2876
2818
if ( error instanceof Error ) {
2877
2819
// eslint-disable-next-line react-internal/safe-string-coercion
2878
2820
message = String ( error . message ) ;
2879
- stack = parseStackTrace ( error , 0 ) ;
2821
+ stack = filterStackTrace ( error , 0 ) ;
2880
2822
const errorEnv = ( error : any ) . environmentName ;
2881
2823
if ( typeof errorEnv === 'string' ) {
2882
2824
// This probably came from another FlightClient as a pass through.
0 commit comments