[Flight] Write Debug Info to Separate Priority Queue (#33654)
This writes all debug info to a separate priority queue. In the future I'll put this on a different channel. Ideally I think we'd put it in the bottom of the stream but because it actually blocks the elements from resolving anyway it ends up being better to put them ahead. At least for now. When we have two separate channels it's not possible to rely on the order for consistency Even then we might write to that queue first for this reason. We can't rely on it though. Which will show up like things turning into Lazy instead of Element similar to how outlining can.
Sebastian Markbåge committed
Jun 27, 2025 at 09:45 UTC
bfc8801e0f0bfacb46bc71244e8244736bd995f4
3 files changed
+354
-135
packages/react-client/src/ReactFlightClient.js
+87
-66
@@ -764,6 +764,78 @@ function getTaskName(type: mixed): string {
764
}
765
}
766
767
+function initializeElement(response: Response, element: any): void {
768
+ if (!__DEV__) {
769
+ return;
770
+ }
771
+ const stack = element._debugStack;
772
+ const owner = element._owner;
773
+ if (owner === null) {
774
+ element._owner = response._debugRootOwner;
775
+ }
776
+ let env = response._rootEnvironmentName;
777
+ if (owner !== null && owner.env != null) {
778
+ // Interestingly we don't actually have the environment name of where
779
+ // this JSX was created if it doesn't have an owner but if it does
780
+ // it must be the same environment as the owner. We could send it separately
781
+ // but it seems a bit unnecessary for this edge case.
782
+ env = owner.env;
783
+ }
784
+ let normalizedStackTrace: null | Error = null;
785
+ if (owner === null && response._debugRootStack != null) {
786
+ // We override the stack if we override the owner since the stack where the root JSX
787
+ // was created on the server isn't very useful but where the request was made is.
788
+ normalizedStackTrace = response._debugRootStack;
789
+ } else if (stack !== null) {
790
+ // We create a fake stack and then create an Error object inside of it.
791
+ // This means that the stack trace is now normalized into the native format
792
+ // of the browser and the stack frames will have been registered with
793
+ // source mapping information.
794
+ // This can unfortunately happen within a user space callstack which will
795
+ // remain on the stack.
796
+ normalizedStackTrace = createFakeJSXCallStackInDEV(response, stack, env);
797
+ }
798
+ element._debugStack = normalizedStackTrace;
799
+ let task: null | ConsoleTask = null;
800
+ if (supportsCreateTask && stack !== null) {
801
+ const createTaskFn = (console: any).createTask.bind(
802
+ console,
803
+ getTaskName(element.type),
804
+ );
805
+ const callStack = buildFakeCallStack(
806
+ response,
807
+ stack,
808
+ env,
809
+ false,
810
+ createTaskFn,
811
+ );
812
+ // This owner should ideally have already been initialized to avoid getting
813
+ // user stack frames on the stack.
814
+ const ownerTask =
815
+ owner === null ? null : initializeFakeTask(response, owner);
816
+ if (ownerTask === null) {
817
+ const rootTask = response._debugRootTask;
818
+ if (rootTask != null) {
819
+ task = rootTask.run(callStack);
820
+ } else {
821
+ task = callStack();
822
+ }
823
+ } else {
824
+ task = ownerTask.run(callStack);
825
+ }
826
+ }
827
+ element._debugTask = task;
828
+
829
+ // This owner should ideally have already been initialized to avoid getting
830
+ // user stack frames on the stack.
831
+ if (owner !== null) {
832
+ initializeFakeStack(response, owner);
833
+ }
834
+ // TODO: We should be freezing the element but currently, we might write into
835
+ // _debugInfo later. We could move it into _store which remains mutable.
836
+ Object.freeze(element.props);
837
+}
838
+
839
function createElement(
840
response: Response,
841
type: mixed,
@@ -783,7 +855,7 @@ function createElement(
855
type,
856
key,
857
props,
786
- _owner: __DEV__ && owner === null ? response._debugRootOwner : owner,
858
+ _owner: owner,
859
}: any);
860
Object.defineProperty(element, 'ref', {
861
enumerable: false,
@@ -821,75 +893,18 @@ function createElement(
893
writable: true,
894
value: null,
895
});
824
- let env = response._rootEnvironmentName;
825
- if (owner !== null && owner.env != null) {
826
- // Interestingly we don't actually have the environment name of where
827
- // this JSX was created if it doesn't have an owner but if it does
828
- // it must be the same environment as the owner. We could send it separately
829
- // but it seems a bit unnecessary for this edge case.
830
- env = owner.env;
831
- }
832
- let normalizedStackTrace: null | Error = null;
833
- if (owner === null && response._debugRootStack != null) {
834
- // We override the stack if we override the owner since the stack where the root JSX
835
- // was created on the server isn't very useful but where the request was made is.
836
- normalizedStackTrace = response._debugRootStack;
837
- } else if (stack !== null) {
838
- // We create a fake stack and then create an Error object inside of it.
839
- // This means that the stack trace is now normalized into the native format
840
- // of the browser and the stack frames will have been registered with
841
- // source mapping information.
842
- // This can unfortunately happen within a user space callstack which will
843
- // remain on the stack.
844
- normalizedStackTrace = createFakeJSXCallStackInDEV(response, stack, env);
845
- }
896
Object.defineProperty(element, '_debugStack', {
897
configurable: false,
898
enumerable: false,
899
writable: true,
850
- value: normalizedStackTrace,
900
+ value: stack,
901
});
852
-
853
- let task: null | ConsoleTask = null;
854
- if (supportsCreateTask && stack !== null) {
855
- const createTaskFn = (console: any).createTask.bind(
856
- console,
857
- getTaskName(type),
858
- );
859
- const callStack = buildFakeCallStack(
860
- response,
861
- stack,
862
- env,
863
- false,
864
- createTaskFn,
865
- );
866
- // This owner should ideally have already been initialized to avoid getting
867
- // user stack frames on the stack.
868
- const ownerTask =
869
- owner === null ? null : initializeFakeTask(response, owner);
870
- if (ownerTask === null) {
871
- const rootTask = response._debugRootTask;
872
- if (rootTask != null) {
873
- task = rootTask.run(callStack);
874
- } else {
875
- task = callStack();
876
- }
877
- } else {
878
- task = ownerTask.run(callStack);
879
- }
880
- }
902
Object.defineProperty(element, '_debugTask', {
903
configurable: false,
904
enumerable: false,
905
writable: true,
885
- value: task,
906
+ value: null,
907
});
887
-
888
- // This owner should ideally have already been initialized to avoid getting
889
- // user stack frames on the stack.
890
- if (owner !== null) {
891
- initializeFakeStack(response, owner);
892
- }
908
}
909
910
if (initializingHandler !== null) {
@@ -905,6 +920,7 @@ function createElement(
920
handler.value,
921
);
922
if (__DEV__) {
923
+ initializeElement(response, element);
924
// Conceptually the error happened inside this Element but right before
925
// it was rendered. We don't have a client side component to render but
926
// we can add some DebugInfo to explain that this was conceptually a
@@ -933,15 +949,15 @@ function createElement(
949
handler.value = element;
950
handler.chunk = blockedChunk;
951
if (__DEV__) {
936
- const freeze = Object.freeze.bind(Object, element.props);
937
- blockedChunk.then(freeze, freeze);
952
+ /// After we have initialized any blocked references, initialize stack etc.
953
+ const init = initializeElement.bind(null, response, element);
954
+ blockedChunk.then(init, init);
955
}
956
return createLazyChunkWrapper(blockedChunk);
957
}
941
- } else if (__DEV__) {
942
- // TODO: We should be freezing the element but currently, we might write into
943
- // _debugInfo later. We could move it into _store which remains mutable.
944
- Object.freeze(element.props);
958
+ }
959
+ if (__DEV__) {
960
+ initializeElement(response, element);
961
}
962
963
return element;
@@ -1055,6 +1071,11 @@ function waitForReference<T>(
1071
element._owner = mappedValue;
1072
}
1073
break;
1074
+ case '5':
1075
+ if (__DEV__) {
1076
+ element._debugStack = mappedValue;
1077
+ }
1078
+ break;
1079
}
1080
}
1081
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js
+1
-1
@@ -674,7 +674,7 @@ describe('ReactFlightDOMEdge', () => {
674
const [stream2, drip] = dripStream(stream);
675
676
// Allow some of the content through.
677
- drip(5000);
677
+ drip(__DEV__ ? 7500 : 5000);
678
679
const result = await ReactServerDOMClient.createFromReadableStream(
680
stream2,
packages/react-server/src/ReactFlightServer.js
+266
-68
@@ -454,6 +454,7 @@ export type Request = {
454
// Profiling-only
455
timeOrigin: number,
456
// DEV-only
457
+ completedDebugChunks: Array<Chunk | BinaryChunk>,
458
environmentName: () => string,
459
filterStackFrame: (url: string, functionName: string) => boolean,
460
didWarnForKey: null | WeakSet<ReactComponentInfo>,
@@ -567,6 +568,7 @@ function RequestInstance(
568
this.onFatalError = onFatalError;
569
570
if (__DEV__) {
571
+ this.completedDebugChunks = ([]: Array<Chunk>);
572
this.environmentName =
573
environmentName === undefined
574
? () => 'Server'
@@ -727,7 +729,7 @@ function serializeDebugThenable(
729
} else {
730
// We don't log these errors since they didn't actually throw into Flight.
731
const digest = '';
730
- emitErrorChunk(request, id, digest, x);
732
+ emitErrorChunk(request, id, digest, x, true);
733
}
734
return ref;
735
}
@@ -777,7 +779,7 @@ function serializeDebugThenable(
779
} else {
780
// We don't log these errors since they didn't actually throw into Flight.
781
const digest = '';
780
- emitErrorChunk(request, id, digest, reason);
782
+ emitErrorChunk(request, id, digest, reason, true);
783
}
784
enqueueFlush(request);
785
},
@@ -1776,25 +1778,32 @@ function renderClientElement(
1778
} else if (keyPath !== null) {
1779
key = keyPath + ',' + key;
1780
}
1781
+ let debugOwner = null;
1782
+ let debugStack = null;
1783
if (__DEV__) {
1780
- if (task.debugOwner !== null) {
1784
+ debugOwner = task.debugOwner;
1785
+ if (debugOwner !== null) {
1786
// Ensure we outline this owner if it is the first time we see it.
1787
// So that we can refer to it directly.
1783
- outlineComponentInfo(request, task.debugOwner);
1788
+ outlineComponentInfo(request, debugOwner);
1789
+ }
1790
+ if (task.debugStack !== null) {
1791
+ // Outline the debug stack so that we write to the completedDebugChunks instead.
1792
+ debugStack = filterStackTrace(
1793
+ request,
1794
+ parseStackTrace(task.debugStack, 1),
1795
+ );
1796
+ const id = outlineDebugModel(
1797
+ request,
1798
+ {objectLimit: debugStack.length * 2 + 1},
1799
+ debugStack,
1800
+ );
1801
+ // We also store this in the main dedupe set so that it can be referenced by inline React Elements.
1802
+ request.writtenObjects.set(debugStack, serializeByValueID(id));
1803
}
1804
}
1805
const element = __DEV__
1787
- ? [
1788
- REACT_ELEMENT_TYPE,
1789
- type,
1790
- key,
1791
- props,
1792
- task.debugOwner,
1793
- task.debugStack === null
1794
- ? null
1795
- : filterStackTrace(request, parseStackTrace(task.debugStack, 1)),
1796
- validated,
1797
- ]
1806
+ ? [REACT_ELEMENT_TYPE, type, key, props, debugOwner, debugStack, validated]
1807
: [REACT_ELEMENT_TYPE, type, key, props];
1808
if (task.implicitSlot && key !== null) {
1809
// The root Server Component had no key so it was in an implicit slot.
@@ -2461,7 +2470,7 @@ function serializeClientReference(
2470
resolveClientReferenceMetadata(request.bundlerConfig, clientReference);
2471
request.pendingChunks++;
2472
const importId = request.nextChunkId++;
2464
- emitImportChunk(request, importId, clientReferenceMetadata);
2473
+ emitImportChunk(request, importId, clientReferenceMetadata, false);
2474
writtenClientReferences.set(clientReferenceKey, importId);
2475
if (parent[0] === REACT_ELEMENT_TYPE && parentPropertyName === '1') {
2476
// If we're encoding the "type" of an element, we can refer
@@ -2476,7 +2485,56 @@ function serializeClientReference(
2485
request.pendingChunks++;
2486
const errorId = request.nextChunkId++;
2487
const digest = logRecoverableError(request, x, null);
2479
- emitErrorChunk(request, errorId, digest, x);
2488
+ emitErrorChunk(request, errorId, digest, x, false);
2489
+ return serializeByValueID(errorId);
2490
+ }
2491
+}
2492
+
2493
+function serializeDebugClientReference(
2494
+ request: Request,
2495
+ parent:
2496
+ | {+[propertyName: string | number]: ReactClientValue}
2497
+ | $ReadOnlyArray<ReactClientValue>,
2498
+ parentPropertyName: string,
2499
+ clientReference: ClientReference<any>,
2500
+): string {
2501
+ // Like serializeDebugClientReference but it doesn't dedupe in the regular set
2502
+ // and it writes to completedDebugChunk instead of imports.
2503
+ const clientReferenceKey: ClientReferenceKey =
2504
+ getClientReferenceKey(clientReference);
2505
+ const writtenClientReferences = request.writtenClientReferences;
2506
+ const existingId = writtenClientReferences.get(clientReferenceKey);
2507
+ if (existingId !== undefined) {
2508
+ if (parent[0] === REACT_ELEMENT_TYPE && parentPropertyName === '1') {
2509
+ // If we're encoding the "type" of an element, we can refer
2510
+ // to that by a lazy reference instead of directly since React
2511
+ // knows how to deal with lazy values. This lets us suspend
2512
+ // on this component rather than its parent until the code has
2513
+ // loaded.
2514
+ return serializeLazyID(existingId);
2515
+ }
2516
+ return serializeByValueID(existingId);
2517
+ }
2518
+ try {
2519
+ const clientReferenceMetadata: ClientReferenceMetadata =
2520
+ resolveClientReferenceMetadata(request.bundlerConfig, clientReference);
2521
+ request.pendingChunks++;
2522
+ const importId = request.nextChunkId++;
2523
+ emitImportChunk(request, importId, clientReferenceMetadata, true);
2524
+ if (parent[0] === REACT_ELEMENT_TYPE && parentPropertyName === '1') {
2525
+ // If we're encoding the "type" of an element, we can refer
2526
+ // to that by a lazy reference instead of directly since React
2527
+ // knows how to deal with lazy values. This lets us suspend
2528
+ // on this component rather than its parent until the code has
2529
+ // loaded.
2530
+ return serializeLazyID(importId);
2531
+ }
2532
+ return serializeByValueID(importId);
2533
+ } catch (x) {
2534
+ request.pendingChunks++;
2535
+ const errorId = request.nextChunkId++;
2536
+ const digest = logRecoverableError(request, x, null);
2537
+ emitErrorChunk(request, errorId, digest, x, true);
2538
return serializeByValueID(errorId);
2539
}
2540
}
@@ -2571,7 +2629,14 @@ function serializeTemporaryReference(
2629
function serializeLargeTextString(request: Request, text: string): string {
2630
request.pendingChunks++;
2631
const textId = request.nextChunkId++;
2574
- emitTextChunk(request, textId, text);
2632
+ emitTextChunk(request, textId, text, false);
2633
+ return serializeByValueID(textId);
2634
+}
2635
+
2636
+function serializeDebugLargeTextString(request: Request, text: string): string {
2637
+ request.pendingChunks++;
2638
+ const textId = request.nextChunkId++;
2639
+ emitTextChunk(request, textId, text, true);
2640
return serializeByValueID(textId);
2641
}
2642
@@ -2590,6 +2655,16 @@ function serializeFormData(request: Request, formData: FormData): string {
2655
return '$K' + id.toString(16);
2656
}
2657
2658
+function serializeDebugFormData(request: Request, formData: FormData): string {
2659
+ const entries = Array.from(formData.entries());
2660
+ const id = outlineDebugModel(
2661
+ request,
2662
+ {objectLimit: entries.length * 2 + 1},
2663
+ (entries: any),
2664
+ );
2665
+ return '$K' + id.toString(16);
2666
+}
2667
+
2668
function serializeSet(request: Request, set: Set<ReactClientValue>): string {
2669
const entries = Array.from(set);
2670
const id = outlineModel(request, entries);
@@ -2659,10 +2734,55 @@ function serializeTypedArray(
2734
): string {
2735
request.pendingChunks++;
2736
const bufferId = request.nextChunkId++;
2662
- emitTypedArrayChunk(request, bufferId, tag, typedArray);
2737
+ emitTypedArrayChunk(request, bufferId, tag, typedArray, false);
2738
return serializeByValueID(bufferId);
2739
}
2740
2741
+function serializeDebugTypedArray(
2742
+ request: Request,
2743
+ tag: string,
2744
+ typedArray: $ArrayBufferView,
2745
+): string {
2746
+ request.pendingChunks++;
2747
+ const bufferId = request.nextChunkId++;
2748
+ emitTypedArrayChunk(request, bufferId, tag, typedArray, true);
2749
+ return serializeByValueID(bufferId);
2750
+}
2751
+
2752
+function serializeDebugBlob(request: Request, blob: Blob): string {
2753
+ const model: Array<string | Uint8Array> = [blob.type];
2754
+ const reader = blob.stream().getReader();
2755
+ const id = request.nextChunkId++;
2756
+ function progress(
2757
+ entry: {done: false, value: Uint8Array} | {done: true, value: void},
2758
+ ): Promise<void> | void {
2759
+ if (entry.done) {
2760
+ emitOutlinedDebugModelChunk(
2761
+ request,
2762
+ id,
2763
+ {objectLimit: model.length + 2},
2764
+ model,
2765
+ );
2766
+ enqueueFlush(request);
2767
+ return;
2768
+ }
2769
+ // TODO: Emit the chunk early and refer to it later by dedupe.
2770
+ model.push(entry.value);
2771
+ // $FlowFixMe[incompatible-call]
2772
+ return reader.read().then(progress).catch(error);
2773
+ }
2774
+ function error(reason: mixed) {
2775
+ const digest = '';
2776
+ emitErrorChunk(request, id, digest, reason, true);
2777
+ enqueueFlush(request);
2778
+ // $FlowFixMe should be able to pass mixed
2779
+ reader.cancel(reason).then(noop, noop);
2780
+ }
2781
+ // $FlowFixMe[incompatible-call]
2782
+ reader.read().then(progress).catch(error);
2783
+ return '$B' + id.toString(16);
2784
+}
2785
+
2786
function serializeBlob(request: Request, blob: Blob): string {
2787
const model: Array<string | Uint8Array> = [blob.type];
2788
const newTask = createTask(
@@ -2849,7 +2969,7 @@ function renderModel(
2969
emitPostponeChunk(request, errorId, postponeInstance);
2970
} else {
2971
const digest = logRecoverableError(request, x, task);
2852
- emitErrorChunk(request, errorId, digest, x);
2972
+ emitErrorChunk(request, errorId, digest, x, false);
2973
}
2974
if (wasReactNode) {
2975
// We'll replace this element with a lazy reference that throws on the client
@@ -3600,11 +3720,48 @@ function serializeErrorValue(request: Request, error: Error): string {
3720
}
3721
}
3722
3723
+function serializeDebugErrorValue(request: Request, error: Error): string {
3724
+ if (__DEV__) {
3725
+ let name: string = 'Error';
3726
+ let message: string;
3727
+ let stack: ReactStackTrace;
3728
+ let env = (0, request.environmentName)();
3729
+ try {
3730
+ name = error.name;
3731
+ // eslint-disable-next-line react-internal/safe-string-coercion
3732
+ message = String(error.message);
3733
+ stack = filterStackTrace(request, parseStackTrace(error, 0));
3734
+ const errorEnv = (error: any).environmentName;
3735
+ if (typeof errorEnv === 'string') {
3736
+ // This probably came from another FlightClient as a pass through.
3737
+ // Keep the environment name.
3738
+ env = errorEnv;
3739
+ }
3740
+ } catch (x) {
3741
+ message = 'An error occurred but serializing the error message failed.';
3742
+ stack = [];
3743
+ }
3744
+ const errorInfo: ReactErrorInfoDev = {name, message, stack, env};
3745
+ const id = outlineDebugModel(
3746
+ request,
3747
+ {objectLimit: stack.length * 2 + 1},
3748
+ errorInfo,
3749
+ );
3750
+ return '$Z' + id.toString(16);
3751
+ } else {
3752
+ // In prod we don't emit any information about this Error object to avoid
3753
+ // unintentional leaks. Since this doesn't actually throw on the server
3754
+ // we don't go through onError and so don't register any digest neither.
3755
+ return '$Z';
3756
+ }
3757
+}
3758
+
3759
function emitErrorChunk(
3760
request: Request,
3761
id: number,
3762
digest: string,
3763
error: mixed,
3764
+ debug: boolean,
3765
): void {
3766
let errorInfo: ReactErrorInfo;
3767
if (__DEV__) {
@@ -3642,19 +3799,28 @@ function emitErrorChunk(
3799
}
3800
const row = serializeRowHeader('E', id) + stringify(errorInfo) + '\n';
3801
const processedChunk = stringToChunk(row);
3645
- request.completedErrorChunks.push(processedChunk);
3802
+ if (__DEV__ && debug) {
3803
+ request.completedDebugChunks.push(processedChunk);
3804
+ } else {
3805
+ request.completedErrorChunks.push(processedChunk);
3806
+ }
3807
}
3808
3809
function emitImportChunk(
3810
request: Request,
3811
id: number,
3812
clientReferenceMetadata: ClientReferenceMetadata,
3813
+ debug: boolean,
3814
): void {
3815
// $FlowFixMe[incompatible-type] stringify can return null
3816
const json: string = stringify(clientReferenceMetadata);
3817
const row = serializeRowHeader('I', id) + json + '\n';
3818
const processedChunk = stringToChunk(row);
3657
- request.completedImportChunks.push(processedChunk);
3819
+ if (__DEV__ && debug) {
3820
+ request.completedDebugChunks.push(processedChunk);
3821
+ } else {
3822
+ request.completedImportChunks.push(processedChunk);
3823
+ }
3824
}
3825
3826
function emitHintChunk<Code: HintCode>(
@@ -3692,7 +3858,7 @@ function emitDebugHaltChunk(request: Request, id: number): void {
3858
// even when the client stream is closed. We use just the lack of data to indicate this.
3859
const row = id.toString(16) + ':\n';
3860
const processedChunk = stringToChunk(row);
3695
- request.completedRegularChunks.push(processedChunk);
3861
+ request.completedDebugChunks.push(processedChunk);
3862
}
3863
3864
function emitDebugChunk(
@@ -3715,7 +3881,7 @@ function emitDebugChunk(
3881
const json: string = serializeDebugModel(request, 500, debugInfo);
3882
const row = serializeRowHeader('D', id) + json + '\n';
3883
const processedChunk = stringToChunk(row);
3718
- request.completedRegularChunks.push(processedChunk);
3884
+ request.completedDebugChunks.push(processedChunk);
3885
}
3886
3887
function outlineComponentInfo(
@@ -3839,7 +4005,7 @@ function emitIOInfoChunk(
4005
const json: string = serializeDebugModel(request, objectLimit, debugIOInfo);
4006
const row = id.toString(16) + ':J' + json + '\n';
4007
const processedChunk = stringToChunk(row);
3842
- request.completedRegularChunks.push(processedChunk);
4008
+ request.completedDebugChunks.push(processedChunk);
4009
}
4010
4011
function outlineIOInfo(request: Request, ioInfo: ReactIOInfo): void {
@@ -3949,6 +4115,7 @@ function emitTypedArrayChunk(
4115
id: number,
4116
tag: string,
4117
typedArray: $ArrayBufferView,
4118
+ debug: boolean,
4119
): void {
4120
if (enableTaint) {
4121
if (TaintRegistryByteLengths.has(typedArray.byteLength)) {
@@ -3968,10 +4135,19 @@ function emitTypedArrayChunk(
4135
const binaryLength = byteLengthOfBinaryChunk(binaryChunk);
4136
const row = id.toString(16) + ':' + tag + binaryLength.toString(16) + ',';
4137
const headerChunk = stringToChunk(row);
3971
- request.completedRegularChunks.push(headerChunk, binaryChunk);
4138
+ if (__DEV__ && debug) {
4139
+ request.completedDebugChunks.push(headerChunk, binaryChunk);
4140
+ } else {
4141
+ request.completedRegularChunks.push(headerChunk, binaryChunk);
4142
+ }
4143
}
4144
3974
-function emitTextChunk(request: Request, id: number, text: string): void {
4145
+function emitTextChunk(
4146
+ request: Request,
4147
+ id: number,
4148
+ text: string,
4149
+ debug: boolean,
4150
+): void {
4151
if (byteLengthOfChunk === null) {
4152
// eslint-disable-next-line react-internal/prod-error-codes
4153
throw new Error(
@@ -3983,7 +4159,11 @@ function emitTextChunk(request: Request, id: number, text: string): void {
4159
const binaryLength = byteLengthOfChunk(textChunk);
4160
const row = id.toString(16) + ':T' + binaryLength.toString(16) + ',';
4161
const headerChunk = stringToChunk(row);
3986
- request.completedRegularChunks.push(headerChunk, textChunk);
4162
+ if (__DEV__ && debug) {
4163
+ request.completedDebugChunks.push(headerChunk, textChunk);
4164
+ } else {
4165
+ request.completedRegularChunks.push(headerChunk, textChunk);
4166
+ }
4167
}
4168
4169
function serializeEval(source: string): string {
@@ -4027,7 +4207,7 @@ function renderDebugModel(
4207
// This might be confusing though because on the Server it won't actually
4208
// be this value, so if you're debugging client references maybe you'd be
4209
// better with a place holder.
4030
- return serializeClientReference(
4210
+ return serializeDebugClientReference(
4211
request,
4212
parent,
4213
parentPropertyName,
@@ -4191,65 +4371,65 @@ function renderDebugModel(
4371
}
4372
// TODO: FormData is not available in old Node. Remove the typeof later.
4373
if (typeof FormData === 'function' && value instanceof FormData) {
4194
- return serializeFormData(request, value);
4374
+ return serializeDebugFormData(request, value);
4375
}
4376
if (value instanceof Error) {
4197
- return serializeErrorValue(request, value);
4377
+ return serializeDebugErrorValue(request, value);
4378
}
4379
if (value instanceof ArrayBuffer) {
4200
- return serializeTypedArray(request, 'A', new Uint8Array(value));
4380
+ return serializeDebugTypedArray(request, 'A', new Uint8Array(value));
4381
}
4382
if (value instanceof Int8Array) {
4383
// char
4204
- return serializeTypedArray(request, 'O', value);
4384
+ return serializeDebugTypedArray(request, 'O', value);
4385
}
4386
if (value instanceof Uint8Array) {
4387
// unsigned char
4208
- return serializeTypedArray(request, 'o', value);
4388
+ return serializeDebugTypedArray(request, 'o', value);
4389
}
4390
if (value instanceof Uint8ClampedArray) {
4391
// unsigned clamped char
4212
- return serializeTypedArray(request, 'U', value);
4392
+ return serializeDebugTypedArray(request, 'U', value);
4393
}
4394
if (value instanceof Int16Array) {
4395
// sort
4216
- return serializeTypedArray(request, 'S', value);
4396
+ return serializeDebugTypedArray(request, 'S', value);
4397
}
4398
if (value instanceof Uint16Array) {
4399
// unsigned short
4220
- return serializeTypedArray(request, 's', value);
4400
+ return serializeDebugTypedArray(request, 's', value);
4401
}
4402
if (value instanceof Int32Array) {
4403
// long
4224
- return serializeTypedArray(request, 'L', value);
4404
+ return serializeDebugTypedArray(request, 'L', value);
4405
}
4406
if (value instanceof Uint32Array) {
4407
// unsigned long
4228
- return serializeTypedArray(request, 'l', value);
4408
+ return serializeDebugTypedArray(request, 'l', value);
4409
}
4410
if (value instanceof Float32Array) {
4411
// float
4232
- return serializeTypedArray(request, 'G', value);
4412
+ return serializeDebugTypedArray(request, 'G', value);
4413
}
4414
if (value instanceof Float64Array) {
4415
// double
4236
- return serializeTypedArray(request, 'g', value);
4416
+ return serializeDebugTypedArray(request, 'g', value);
4417
}
4418
if (value instanceof BigInt64Array) {
4419
// number
4240
- return serializeTypedArray(request, 'M', value);
4420
+ return serializeDebugTypedArray(request, 'M', value);
4421
}
4422
if (value instanceof BigUint64Array) {
4423
// unsigned number
4424
// We use "m" instead of "n" since JSON can start with "null"
4245
- return serializeTypedArray(request, 'm', value);
4425
+ return serializeDebugTypedArray(request, 'm', value);
4426
}
4427
if (value instanceof DataView) {
4248
- return serializeTypedArray(request, 'V', value);
4428
+ return serializeDebugTypedArray(request, 'V', value);
4429
}
4430
// TODO: Blob is not available in old Node. Remove the typeof check later.
4431
if (typeof Blob === 'function' && value instanceof Blob) {
4252
- return serializeBlob(request, value);
4432
+ return serializeDebugBlob(request, value);
4433
}
4434
4435
const iteratorFn = getIteratorFn(value);
@@ -4310,7 +4490,7 @@ function renderDebugModel(
4490
// For large strings, we encode them outside the JSON payload so that we
4491
// don't have to double encode and double parse the strings. This can also
4492
// be more compact in case the string has a lot of escaped characters.
4313
- return serializeLargeTextString(request, value);
4493
+ return serializeDebugLargeTextString(request, value);
4494
}
4495
return escapeStringValue(value);
4496
}
@@ -4329,7 +4509,7 @@ function renderDebugModel(
4509
4510
if (typeof value === 'function') {
4511
if (isClientReference(value)) {
4332
- return serializeClientReference(
4512
+ return serializeDebugClientReference(
4513
request,
4514
parent,
4515
parentPropertyName,
@@ -4362,7 +4542,7 @@ function renderDebugModel(
4542
request.pendingChunks++;
4543
const id = request.nextChunkId++;
4544
const processedChunk = encodeReferenceChunk(request, id, serializedValue);
4365
- request.completedRegularChunks.push(processedChunk);
4545
+ request.completedDebugChunks.push(processedChunk);
4546
const reference = serializeByValueID(id);
4547
writtenDebugObjects.set(value, reference);
4548
return reference;
@@ -4500,7 +4680,7 @@ function emitOutlinedDebugModelChunk(
4680
4681
const row = id.toString(16) + ':' + json + '\n';
4682
const processedChunk = stringToChunk(row);
4503
- request.completedRegularChunks.push(processedChunk);
4683
+ request.completedDebugChunks.push(processedChunk);
4684
}
4685
4686
function outlineDebugModel(
@@ -4560,7 +4740,7 @@ function emitConsoleChunk(
4740
}
4741
const row = ':W' + json + '\n';
4742
const processedChunk = stringToChunk(row);
4563
- request.completedRegularChunks.push(processedChunk);
4743
+ request.completedDebugChunks.push(processedChunk);
4744
}
4745
4746
function emitTimeOriginChunk(request: Request, timeOrigin: number): void {
@@ -4571,7 +4751,7 @@ function emitTimeOriginChunk(request: Request, timeOrigin: number): void {
4751
const row = ':N' + timeOrigin + '\n';
4752
const processedChunk = stringToChunk(row);
4753
// TODO: Move to its own priority queue.
4574
- request.completedRegularChunks.push(processedChunk);
4754
+ request.completedDebugChunks.push(processedChunk);
4755
}
4756
4757
function forwardDebugInfo(
@@ -4783,7 +4963,7 @@ function emitTimingChunk(
4963
serializeRowHeader('D', id) + '{"time":' + relativeTimestamp + '}\n';
4964
const processedChunk = stringToChunk(row);
4965
// TODO: Move to its own priority queue.
4786
- request.completedRegularChunks.push(processedChunk);
4966
+ request.completedDebugChunks.push(processedChunk);
4967
}
4968
4969
function advanceTaskTime(
@@ -4839,71 +5019,71 @@ function emitChunk(
5019
throwTaintViolation(tainted.message);
5020
}
5021
}
4842
- emitTextChunk(request, id, value);
5022
+ emitTextChunk(request, id, value, false);
5023
return;
5024
}
5025
if (value instanceof ArrayBuffer) {
4846
- emitTypedArrayChunk(request, id, 'A', new Uint8Array(value));
5026
+ emitTypedArrayChunk(request, id, 'A', new Uint8Array(value), false);
5027
return;
5028
}
5029
if (value instanceof Int8Array) {
5030
// char
4851
- emitTypedArrayChunk(request, id, 'O', value);
5031
+ emitTypedArrayChunk(request, id, 'O', value, false);
5032
return;
5033
}
5034
if (value instanceof Uint8Array) {
5035
// unsigned char
4856
- emitTypedArrayChunk(request, id, 'o', value);
5036
+ emitTypedArrayChunk(request, id, 'o', value, false);
5037
return;
5038
}
5039
if (value instanceof Uint8ClampedArray) {
5040
// unsigned clamped char
4861
- emitTypedArrayChunk(request, id, 'U', value);
5041
+ emitTypedArrayChunk(request, id, 'U', value, false);
5042
return;
5043
}
5044
if (value instanceof Int16Array) {
5045
// sort
4866
- emitTypedArrayChunk(request, id, 'S', value);
5046
+ emitTypedArrayChunk(request, id, 'S', value, false);
5047
return;
5048
}
5049
if (value instanceof Uint16Array) {
5050
// unsigned short
4871
- emitTypedArrayChunk(request, id, 's', value);
5051
+ emitTypedArrayChunk(request, id, 's', value, false);
5052
return;
5053
}
5054
if (value instanceof Int32Array) {
5055
// long
4876
- emitTypedArrayChunk(request, id, 'L', value);
5056
+ emitTypedArrayChunk(request, id, 'L', value, false);
5057
return;
5058
}
5059
if (value instanceof Uint32Array) {
5060
// unsigned long
4881
- emitTypedArrayChunk(request, id, 'l', value);
5061
+ emitTypedArrayChunk(request, id, 'l', value, false);
5062
return;
5063
}
5064
if (value instanceof Float32Array) {
5065
// float
4886
- emitTypedArrayChunk(request, id, 'G', value);
5066
+ emitTypedArrayChunk(request, id, 'G', value, false);
5067
return;
5068
}
5069
if (value instanceof Float64Array) {
5070
// double
4891
- emitTypedArrayChunk(request, id, 'g', value);
5071
+ emitTypedArrayChunk(request, id, 'g', value, false);
5072
return;
5073
}
5074
if (value instanceof BigInt64Array) {
5075
// number
4896
- emitTypedArrayChunk(request, id, 'M', value);
5076
+ emitTypedArrayChunk(request, id, 'M', value, false);
5077
return;
5078
}
5079
if (value instanceof BigUint64Array) {
5080
// unsigned number
5081
// We use "m" instead of "n" since JSON can start with "null"
4902
- emitTypedArrayChunk(request, id, 'm', value);
5082
+ emitTypedArrayChunk(request, id, 'm', value, false);
5083
return;
5084
}
5085
if (value instanceof DataView) {
4906
- emitTypedArrayChunk(request, id, 'V', value);
5086
+ emitTypedArrayChunk(request, id, 'V', value, false);
5087
return;
5088
}
5089
// For anything else we need to try to serialize it using JSON.
@@ -4930,7 +5110,7 @@ function erroredTask(request: Request, task: Task, error: mixed): void {
5110
emitPostponeChunk(request, task.id, postponeInstance);
5111
} else {
5112
const digest = logRecoverableError(request, error, task);
4933
- emitErrorChunk(request, task.id, digest, error);
5113
+ emitErrorChunk(request, task.id, digest, error, false);
5114
}
5115
request.abortableTasks.delete(task);
5116
callOnAllReadyIfReady(request);
@@ -5183,6 +5363,24 @@ function flushCompletedChunks(
5363
}
5364
hintChunks.splice(0, i);
5365
5366
+ // Debug meta data comes before the model data because it will often end up blocking the model from
5367
+ // completing since the JSX will reference the debug data.
5368
+ if (__DEV__) {
5369
+ const debugChunks = request.completedDebugChunks;
5370
+ i = 0;
5371
+ for (; i < debugChunks.length; i++) {
5372
+ request.pendingChunks--;
5373
+ const chunk = debugChunks[i];
5374
+ const keepWriting: boolean = writeChunkAndReturn(destination, chunk);
5375
+ if (!keepWriting) {
5376
+ request.destination = null;
5377
+ i++;
5378
+ break;
5379
+ }
5380
+ }
5381
+ debugChunks.splice(0, i);
5382
+ }
5383
+
5384
// Next comes model data.
5385
const regularChunks = request.completedRegularChunks;
5386
i = 0;
@@ -5359,7 +5557,7 @@ export function abort(request: Request, reason: mixed): void {
5557
const errorId = request.nextChunkId++;
5558
request.fatalError = errorId;
5559
request.pendingChunks++;
5362
- emitErrorChunk(request, errorId, digest, error);
5560
+ emitErrorChunk(request, errorId, digest, error, false);
5561
abortableTasks.forEach(task => abortTask(task, request, errorId));
5562
abortableTasks.clear();
5563
callOnAllReadyIfReady(request);