17
enableTaint,
18
enableRefAsProp,
19
enableServerComponentLogs,
20
+ enableOwnerStacks,
21
} from 'shared/ReactFeatureFlags';
22
23
import {enableFlightReadableStream} from 'shared/ReactFeatureFlags';
124
125
import {SuspenseException, getSuspendedThenable} from './ReactFlightThenable';
126
127
+// TODO: Make this configurable on the Request.
128
+const externalRegExp = /\/node\_modules\/| \(node\:| node\:|\(\<anonymous\>\)/;
129
+
130
+let callComponentFrame: null | string = null;
131
+let callIteratorFrame: null | string = null;
132
+let callLazyInitFrame: null | string = null;
133
+
134
+function isNotExternal(stackFrame: string): boolean {
135
+ return !externalRegExp.test(stackFrame);
136
+}
137
+
138
+function initCallComponentFrame(): string {
139
+ // Extract the stack frame of the callComponentInDEV function.
140
+ const error = callComponentInDEV(Error, 'react-stack-top-frame', {});
141
+ const stack = error.stack;
142
+ const startIdx = stack.startsWith('Error: react-stack-top-frame\n') ? 29 : 0;
143
+ const endIdx = stack.indexOf('\n', startIdx);
144
+ if (endIdx === -1) {
145
+ return stack.slice(startIdx);
146
+ }
147
+ return stack.slice(startIdx, endIdx);
148
+}
149
+
150
+function initCallIteratorFrame(): string {
151
+ // Extract the stack frame of the callIteratorInDEV function.
152
+ try {
153
+ (callIteratorInDEV: any)({next: null});
154
+ return '';
155
+ } catch (error) {
156
+ const stack = error.stack;
157
+ const startIdx = stack.startsWith('TypeError: ')
158
+ ? stack.indexOf('\n') + 1
159
+ : 0;
160
+ const endIdx = stack.indexOf('\n', startIdx);
161
+ if (endIdx === -1) {
162
+ return stack.slice(startIdx);
163
+ }
164
+ return stack.slice(startIdx, endIdx);
165
+ }
166
+}
167
+
168
+function initCallLazyInitFrame(): string {
169
+ // Extract the stack frame of the callLazyInitInDEV function.
170
+ const error = callLazyInitInDEV({
171
+ $$typeof: REACT_LAZY_TYPE,
172
+ _init: Error,
173
+ _payload: 'react-stack-top-frame',
174
+ });
175
+ const stack = error.stack;
176
+ const startIdx = stack.startsWith('Error: react-stack-top-frame\n') ? 29 : 0;
177
+ const endIdx = stack.indexOf('\n', startIdx);
178
+ if (endIdx === -1) {
179
+ return stack.slice(startIdx);
180
+ }
181
+ return stack.slice(startIdx, endIdx);
182
+}
183
+
184
+function filterDebugStack(error: Error): string {
185
+ // Since stacks can be quite large and we pass a lot of them, we filter them out eagerly
186
+ // to save bandwidth even in DEV. We'll also replay these stacks on the client so by
187
+ // stripping them early we avoid that overhead. Otherwise we'd normally just rely on
188
+ // the DevTools or framework's ignore lists to filter them out.
189
+ let stack = error.stack;
190
+ if (stack.startsWith('Error: react-stack-top-frame\n')) {
191
+ // V8's default formatting prefixes with the error message which we
192
+ // don't want/need.
193
+ stack = stack.slice(29);
194
+ }
195
+ const frames = stack.split('\n').slice(1);
196
+ if (callComponentFrame === null) {
197
+ callComponentFrame = initCallComponentFrame();
198
+ }
199
+ let lastFrameIdx = frames.indexOf(callComponentFrame);
200
+ if (lastFrameIdx === -1) {
201
+ if (callLazyInitFrame === null) {
202
+ callLazyInitFrame = initCallLazyInitFrame();
203
+ }
204
+ lastFrameIdx = frames.indexOf(callLazyInitFrame);
205
+ if (lastFrameIdx === -1) {
206
+ if (callIteratorFrame === null) {
207
+ callIteratorFrame = initCallIteratorFrame();
208
+ }
209
+ lastFrameIdx = frames.indexOf(callIteratorFrame);
210
+ }
211
+ }
212
+ if (lastFrameIdx !== -1) {
213
+ // Cut off everything after our "callComponent" slot since it'll be Flight internals.
214
+ frames.length = lastFrameIdx;
215
+ }
216
+ return frames.filter(isNotExternal).join('\n');
217
+}
218
+
219
initAsyncDebugInfo();
220
221
function patchConsole(consoleInst: typeof console, methodName: string) {
239
// Extract the stack. Not all console logs print the full stack but they have at
240
// least the line it was called from. We could optimize transfer by keeping just
241
// one stack frame but keeping it simple for now and include all frames.
149
- let stack = new Error().stack;
150
- if (stack.startsWith('Error: \n')) {
151
- stack = stack.slice(8);
152
- }
242
+ let stack = filterDebugStack(new Error('react-stack-top-frame'));
243
const firstLine = stack.indexOf('\n');
244
if (firstLine === -1) {
245
stack = '';
711
return serializeByValueID(streamTask.id);
712
}
713
714
+// This indirect exists so we can exclude its stack frame in DEV (and anything below it).
715
+/** @noinline */
716
+function callIteratorInDEV(
717
+ iterator: $AsyncIterator<ReactClientValue, ReactClientValue, void>,
718
+ progress: (
719
+ entry:
720
+ | {done: false, +value: ReactClientValue, ...}
721
+ | {done: true, +value: ReactClientValue, ...},
722
+ ) => void,
723
+ error: (reason: mixed) => void,
724
+) {
725
+ iterator.next().then(progress, error);
726
+}
727
+
728
function serializeAsyncIterable(
729
request: Request,
730
task: Task,
801
request.pendingChunks++;
802
tryStreamTask(request, streamTask);
803
enqueueFlush(request);
700
- iterator.next().then(progress, error);
804
+ if (__DEV__) {
805
+ callIteratorInDEV(iterator, progress, error);
806
+ } else {
807
+ iterator.next().then(progress, error);
808
+ }
809
} catch (x) {
810
error(x);
811
return;
839
}
840
}
841
request.abortListeners.add(error);
734
- iterator.next().then(progress, error);
842
+ if (__DEV__) {
843
+ callIteratorInDEV(iterator, progress, error);
844
+ } else {
845
+ iterator.next().then(progress, error);
846
+ }
847
return serializeByValueID(streamTask.id);
848
}
849
921
return lazyType;
922
}
923
924
+// This indirect exists so we can exclude its stack frame in DEV (and anything below it).
925
+/** @noinline */
926
+function callComponentInDEV<Props, R>(
927
+ Component: (p: Props, arg: void) => R,
928
+ props: Props,
929
+ componentDebugInfo: ReactComponentInfo,
930
+): R {
931
+ // The secondArg is always undefined in Server Components since refs error early.
932
+ const secondArg = undefined;
933
+ setCurrentOwner(componentDebugInfo);
934
+ try {
935
+ if (supportsComponentStorage) {
936
+ // Run the component in an Async Context that tracks the current owner.
937
+ return componentStorage.run(
938
+ componentDebugInfo,
939
+ Component,
940
+ props,
941
+ secondArg,
942
+ );
943
+ } else {
944
+ return Component(props, secondArg);
945
+ }
946
+ } finally {
947
+ setCurrentOwner(null);
948
+ }
949
+}
950
+
951
+// This indirect exists so we can exclude its stack frame in DEV (and anything below it).
952
+/** @noinline */
953
+function callLazyInitInDEV(lazy: LazyComponent<any, any>): any {
954
+ const payload = lazy._payload;
955
+ const init = lazy._init;
956
+ return init(payload);
957
+}
958
+
959
function renderFunctionComponent<Props>(
960
request: Request,
961
task: Task,
962
key: null | string,
963
Component: (p: Props, arg: void) => any,
964
props: Props,
818
- owner: null | ReactComponentInfo,
965
+ owner: null | ReactComponentInfo, // DEV-only
966
+ stack: null | string, // DEV-only
967
): ReactJSONValue {
968
// Reset the task's thenable state before continuing, so that if a later
969
// component suspends we can reuse the same task object. If the same
971
const prevThenableState = task.thenableState;
972
task.thenableState = null;
973
826
- // The secondArg is always undefined in Server Components since refs error early.
827
- const secondArg = undefined;
974
let result;
975
976
let componentDebugInfo: ReactComponentInfo;
996
env: request.environmentName,
997
owner: owner,
998
};
999
+ if (enableOwnerStacks) {
1000
+ (componentDebugInfo: any).stack = stack;
1001
+ }
1002
// We outline this model eagerly so that we can refer to by reference as an owner.
1003
// If we had a smarter way to dedupe we might not have to do this if there ends up
1004
// being no references to this as an owner.
1006
emitDebugChunk(request, componentDebugID, componentDebugInfo);
1007
}
1008
prepareToUseHooksForComponent(prevThenableState, componentDebugInfo);
860
- setCurrentOwner(componentDebugInfo);
861
- try {
862
- if (supportsComponentStorage) {
863
- // Run the component in an Async Context that tracks the current owner.
864
- result = componentStorage.run(
865
- componentDebugInfo,
866
- Component,
867
- props,
868
- secondArg,
869
- );
870
- } else {
871
- result = Component(props, secondArg);
872
- }
873
- } finally {
874
- setCurrentOwner(null);
875
- }
1009
+ result = callComponentInDEV(Component, props, componentDebugInfo);
1010
} else {
1011
prepareToUseHooksForComponent(prevThenableState, null);
1012
+ // The secondArg is always undefined in Server Components since refs error early.
1013
+ const secondArg = undefined;
1014
result = Component(props, secondArg);
1015
}
1016
if (typeof result === 'object' && result !== null) {
1229
key: null | string,
1230
props: any,
1231
owner: null | ReactComponentInfo, // DEV-only
1232
+ stack: null | string, // DEV-only
1233
): ReactJSONValue {
1234
// We prepend the terminal client element that actually gets serialized with
1235
// the keys of any Server Components which are not serialized.
1240
key = keyPath + ',' + key;
1241
}
1242
const element = __DEV__
1106
- ? [REACT_ELEMENT_TYPE, type, key, props, owner]
1243
+ ? enableOwnerStacks
1244
+ ? [REACT_ELEMENT_TYPE, type, key, props, owner, stack]
1245
+ : [REACT_ELEMENT_TYPE, type, key, props, owner]
1246
: [REACT_ELEMENT_TYPE, type, key, props];
1247
if (task.implicitSlot && key !== null) {
1248
// The root Server Component had no key so it was in an implicit slot.
1290
ref: mixed,
1291
props: any,
1292
owner: null | ReactComponentInfo, // DEV only
1293
+ stack: null | string, // DEV only
1294
): ReactJSONValue {
1295
if (ref !== null && ref !== undefined) {
1296
// When the ref moves to the regular props object this will implicitly
1311
if (typeof type === 'function') {
1312
if (isClientReference(type) || isTemporaryReference(type)) {
1313
// This is a reference to a Client Component.
1174
- return renderClientElement(task, type, key, props, owner);
1314
+ return renderClientElement(task, type, key, props, owner, stack);
1315
}
1316
// This is a Server Component.
1177
- return renderFunctionComponent(request, task, key, type, props, owner);
1317
+ return renderFunctionComponent(
1318
+ request,
1319
+ task,
1320
+ key,
1321
+ type,
1322
+ props,
1323
+ owner,
1324
+ stack,
1325
+ );
1326
} else if (typeof type === 'string') {
1327
// This is a host element. E.g. HTML.
1180
- return renderClientElement(task, type, key, props, owner);
1328
+ return renderClientElement(task, type, key, props, owner, stack);
1329
} else if (typeof type === 'symbol') {
1330
if (type === REACT_FRAGMENT_TYPE && key === null) {
1331
// For key-less fragments, we add a small optimization to avoid serializing
1346
}
1347
// This might be a built-in React component. We'll let the client decide.
1348
// Any built-in works as long as its props are serializable.
1201
- return renderClientElement(task, type, key, props, owner);
1349
+ return renderClientElement(task, type, key, props, owner, stack);
1350
} else if (type != null && typeof type === 'object') {
1351
if (isClientReference(type)) {
1352
// This is a reference to a Client Component.
1205
- return renderClientElement(task, type, key, props, owner);
1353
+ return renderClientElement(task, type, key, props, owner, stack);
1354
}
1355
switch (type.$$typeof) {
1356
case REACT_LAZY_TYPE: {
1209
- const payload = type._payload;
1210
- const init = type._init;
1211
- const wrappedType = init(payload);
1357
+ let wrappedType;
1358
+ if (__DEV__) {
1359
+ wrappedType = callLazyInitInDEV(type);
1360
+ } else {
1361
+ const payload = type._payload;
1362
+ const init = type._init;
1363
+ wrappedType = init(payload);
1364
+ }
1365
return renderElement(
1366
request,
1367
task,
1370
ref,
1371
props,
1372
owner,
1373
+ stack,
1374
);
1375
}
1376
case REACT_FORWARD_REF_TYPE: {
1381
type.render,
1382
props,
1383
owner,
1384
+ stack,
1385
);
1386
}
1387
case REACT_MEMO_TYPE: {
1233
- return renderElement(request, task, type.type, key, ref, props, owner);
1388
+ return renderElement(
1389
+ request,
1390
+ task,
1391
+ type.type,
1392
+ key,
1393
+ ref,
1394
+ props,
1395
+ owner,
1396
+ stack,
1397
+ );
1398
}
1399
}
1400
}
1986
ref,
1987
props,
1988
__DEV__ ? element._owner : null,
1989
+ __DEV__ && enableOwnerStacks
1990
+ ? filterDebugStack(element._debugStack)
1991
+ : null,
1992
);
1993
}
1994
case REACT_LAZY_TYPE: {
1997
task.thenableState = null;
1998
1999
const lazy: LazyComponent<any, any> = (value: any);
1833
- const payload = lazy._payload;
1834
- const init = lazy._init;
1835
- const resolvedModel = init(payload);
2000
+ let resolvedModel;
2001
+ if (__DEV__) {
2002
+ resolvedModel = callLazyInitInDEV(lazy);
2003
+ } else {
2004
+ const payload = lazy._payload;
2005
+ const init = lazy._init;
2006
+ resolvedModel = init(payload);
2007
+ }
2008
if (__DEV__) {
2009
const debugInfo: ?ReactDebugInfo = lazy._debugInfo;
2010
if (debugInfo) {