@samitouri / QOS-React-1 / commits / bfc8801e0f

[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);