main
js 7,047 lines 236 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {Chunk, BinaryChunk, Destination} from './ReactServerStreamConfig';
11
12 import type {TemporaryReferenceSet} from './ReactFlightServerTemporaryReferences';
13
14 import {
15 enableTaint,
16 enableProfilerTimer,
17 enableComponentPerformanceTrack,
18 enableAsyncDebugInfo,
19 enableFlightWeakThenables,
20 } from 'shared/ReactFeatureFlags';
21
22 import {
23 scheduleWork,
24 scheduleMicrotask,
25 flushBuffered,
26 beginWriting,
27 writeChunk,
28 writeChunkAndReturn,
29 stringToChunk,
30 typedArrayToBinaryChunk,
31 byteLengthOfChunk,
32 byteLengthOfBinaryChunk,
33 completeWriting,
34 close,
35 closeWithError,
36 } from './ReactServerStreamConfig';
37
38 export type {Destination, Chunk} from './ReactServerStreamConfig';
39
40 import type {
41 ClientManifest,
42 ClientReferenceMetadata,
43 ClientReference,
44 ClientReferenceKey,
45 ServerReference,
46 ServerReferenceId,
47 Hints,
48 HintCode,
49 HintModel,
50 FormatContext,
51 } from './ReactFlightServerConfig';
52 import type {ThenableState} from './ReactFlightThenable';
53 import type {
54 Wakeable,
55 Thenable,
56 PendingThenable,
57 FulfilledThenable,
58 RejectedThenable,
59 ReactDebugInfo,
60 ReactDebugInfoEntry,
61 ReactComponentInfo,
62 ReactIOInfo,
63 ReactAsyncInfo,
64 ReactStackTrace,
65 ReactCallSite,
66 ReactFunctionLocation,
67 ReactErrorInfo,
68 ReactErrorInfoDev,
69 ReactKey,
70 } from 'shared/ReactTypes';
71 import type {ReactElement} from 'shared/ReactElementType';
72 import type {LazyComponent} from 'react/src/ReactLazy';
73 import type {
74 AsyncSequence,
75 IONode,
76 PromiseNode,
77 UnresolvedPromiseNode,
78 } from './ReactFlightAsyncSequence';
79
80 import {
81 resolveClientReferenceMetadata,
82 getServerReferenceId,
83 getServerReferenceBoundArguments,
84 getServerReferenceLocation,
85 getClientReferenceKey,
86 isClientReference,
87 isServerReference,
88 supportsRequestStorage,
89 requestStorage,
90 createHints,
91 createRootFormatContext,
92 getChildFormatContext,
93 initAsyncDebugInfo,
94 markAsyncSequenceRootTask,
95 getCurrentAsyncSequence,
96 getAsyncSequenceFromPromise,
97 parseStackTrace,
98 parseStackTracePrivate,
99 supportsComponentStorage,
100 componentStorage,
101 unbadgeConsole,
102 } from './ReactFlightServerConfig';
103
104 import {
105 resolveTemporaryReference,
106 isOpaqueTemporaryReference,
107 } from './ReactFlightServerTemporaryReferences';
108
109 import {
110 HooksDispatcher,
111 prepareToUseHooksForRequest,
112 prepareToUseHooksForComponent,
113 getThenableStateAfterSuspending,
114 getTrackedThenablesAfterRendering,
115 resetHooksForRequest,
116 } from './ReactFlightHooks';
117 import {DefaultAsyncDispatcher} from './flight/ReactFlightAsyncDispatcher';
118
119 import {resolveOwner, setCurrentOwner} from './flight/ReactFlightCurrentOwner';
120
121 import {getOwnerStackByComponentInfoInDev} from 'shared/ReactComponentInfoStack';
122 import {resetOwnerStackLimit} from 'shared/ReactOwnerStackReset';
123
124 import noop from 'shared/noop';
125
126 import {
127 callComponentInDEV,
128 callLazyInitInDEV,
129 callIteratorInDEV,
130 } from './ReactFlightCallUserSpace';
131
132 import {
133 getIteratorFn,
134 REACT_ELEMENT_TYPE,
135 REACT_LEGACY_ELEMENT_TYPE,
136 REACT_FORWARD_REF_TYPE,
137 REACT_FRAGMENT_TYPE,
138 REACT_LAZY_TYPE,
139 REACT_MEMO_TYPE,
140 ASYNC_ITERATOR,
141 REACT_OPTIMISTIC_KEY,
142 } from 'shared/ReactSymbols';
143
144 import {
145 describeObjectForErrorMessage,
146 isGetter,
147 isSimpleObject,
148 jsxPropsParents,
149 jsxChildrenParents,
150 objectName,
151 } from 'shared/ReactSerializationErrors';
152
153 import ReactSharedInternals from './ReactSharedInternalsServer';
154 import isArray from 'shared/isArray';
155 import getPrototypeOf from 'shared/getPrototypeOf';
156 import hasOwnProperty from 'shared/hasOwnProperty';
157 import binaryToComparableString from 'shared/binaryToComparableString';
158
159 import {SuspenseException, getSuspendedThenable} from './ReactFlightThenable';
160
161 import {
162 IO_NODE,
163 PROMISE_NODE,
164 AWAIT_NODE,
165 UNRESOLVED_AWAIT_NODE,
166 UNRESOLVED_PROMISE_NODE,
167 } from './ReactFlightAsyncSequence';
168
169 // DEV-only set containing internal objects that should not be limited and turned into getters.
170 const doNotLimit: WeakSet<Reference> = __DEV__ ? new WeakSet() : (null as any);
171
172 function defaultFilterStackFrame(
173 filename: string,
174 functionName: string,
175 ): boolean {
176 return (
177 filename !== '' &&
178 !filename.startsWith('node:') &&
179 !filename.includes('node_modules')
180 );
181 }
182
183 function devirtualizeURL(url: string): string {
184 if (url.startsWith('about://React/')) {
185 // This callsite is a virtual fake callsite that came from another Flight client.
186 // We need to reverse it back into the original location by stripping its prefix
187 // and suffix. We don't need the environment name because it's available on the
188 // parent object that will contain the stack.
189 const envIdx = url.indexOf('/', 'about://React/'.length);
190 const suffixIdx = url.lastIndexOf('?');
191 if (envIdx > -1 && suffixIdx > -1) {
192 return decodeURI(url.slice(envIdx + 1, suffixIdx));
193 }
194 }
195 return url;
196 }
197
198 function isPromiseCreationInternal(url: string, functionName: string): boolean {
199 // Various internals of the JS VM can create Promises but the call frame of the
200 // internals are not very interesting for our purposes so we need to skip those.
201 if (url === 'node:internal/async_hooks') {
202 // Ignore the stack frames from the async hooks themselves.
203 return true;
204 }
205 if (url !== '') {
206 return false;
207 }
208 // V8 used to name the frames of static methods on the Promise constructor
209 // "Function.x" but newer versions name them "Promise.x". We match both.
210 switch (functionName) {
211 case 'new Promise':
212 case 'Function.withResolvers':
213 case 'Promise.withResolvers':
214 case 'Function.reject':
215 case 'Promise.reject':
216 case 'Function.resolve':
217 case 'Promise.resolve':
218 case 'Function.all':
219 case 'Promise.all':
220 case 'Function.allSettled':
221 case 'Promise.allSettled':
222 case 'Function.race':
223 case 'Promise.race':
224 case 'Function.try':
225 case 'Promise.try':
226 return true;
227 default:
228 return false;
229 }
230 }
231
232 function stripLeadingPromiseCreationFrames(
233 stack: ReactStackTrace,
234 ): ReactStackTrace {
235 for (let i = 0; i < stack.length; i++) {
236 const callsite = stack[i];
237 const functionName = callsite[0];
238 const url = callsite[1];
239 if (!isPromiseCreationInternal(url, functionName)) {
240 if (i > 0) {
241 return stack.slice(i);
242 } else {
243 return stack;
244 }
245 }
246 }
247 return [];
248 }
249
250 function findCalledFunctionNameFromStackTrace(
251 request: Request,
252 stack: ReactStackTrace,
253 ): string {
254 // Gets the name of the first function called from first party code.
255 let bestMatch = '';
256 const filterStackFrame = request.filterStackFrame;
257 for (let i = 0; i < stack.length; i++) {
258 const callsite = stack[i];
259 const functionName = callsite[0];
260 const url = devirtualizeURL(callsite[1]);
261 const lineNumber = callsite[2];
262 const columnNumber = callsite[3];
263 if (
264 filterStackFrame(url, functionName, lineNumber, columnNumber) &&
265 // Don't consider anonymous code first party even if the filter wants to include them in the stack.
266 url !== ''
267 ) {
268 if (bestMatch === '') {
269 // If we had no good stack frames for internal calls, just use the last
270 // first party function name.
271 return functionName;
272 }
273 return bestMatch;
274 } else {
275 bestMatch = functionName;
276 }
277 }
278 return '';
279 }
280
281 function filterStackTrace(
282 request: Request,
283 stack: ReactStackTrace,
284 ): ReactStackTrace {
285 // Since stacks can be quite large and we pass a lot of them, we filter them out eagerly
286 // to save bandwidth even in DEV. We'll also replay these stacks on the client so by
287 // stripping them early we avoid that overhead. Otherwise we'd normally just rely on
288 // the DevTools or framework's ignore lists to filter them out.
289 const filterStackFrame = request.filterStackFrame;
290 const filteredStack: ReactStackTrace = [];
291 for (let i = 0; i < stack.length; i++) {
292 const callsite = stack[i];
293 const functionName = callsite[0];
294 const url = devirtualizeURL(callsite[1]);
295 const lineNumber = callsite[2];
296 const columnNumber = callsite[3];
297 if (filterStackFrame(url, functionName, lineNumber, columnNumber)) {
298 // Use a clone because the Flight protocol isn't yet resilient to deduping
299 // objects in the debug info. TODO: Support deduping stacks.
300 const clone: ReactCallSite = callsite.slice(0) as any;
301 clone[1] = url;
302 filteredStack.push(clone);
303 }
304 }
305 return filteredStack;
306 }
307
308 function hasUnfilteredFrame(request: Request, stack: ReactStackTrace): boolean {
309 const filterStackFrame = request.filterStackFrame;
310 for (let i = 0; i < stack.length; i++) {
311 const callsite = stack[i];
312 const functionName = callsite[0];
313 const url = devirtualizeURL(callsite[1]);
314 const lineNumber = callsite[2];
315 const columnNumber = callsite[3];
316 // Ignore async stack frames because they're not "real". We'd expect to have at least
317 // one non-async frame if we're actually executing inside a first party function.
318 // Otherwise we might just be in the resume of a third party function that resumed
319 // inside a first party stack.
320 const isAsync = callsite[6];
321 if (
322 !isAsync &&
323 filterStackFrame(url, functionName, lineNumber, columnNumber) &&
324 // Ignore anonymous stack frames like internals. They are also not in first party
325 // code even though it might be useful to include them in the final stack.
326 url !== ''
327 ) {
328 return true;
329 }
330 }
331 return false;
332 }
333
334 function isPromiseAwaitInternal(url: string, functionName: string): boolean {
335 // Various internals of the JS VM can await internally on a Promise. If those are at
336 // the top of the stack then we don't want to consider them as internal frames. The
337 // true "await" conceptually is the thing that called the helper.
338 // Ideally we'd also include common third party helpers for this.
339 if (url === 'node:internal/async_hooks') {
340 // Ignore the stack frames from the async hooks themselves.
341 return true;
342 }
343 if (url !== '') {
344 return false;
345 }
346 // V8 used to name the frames of static methods on the Promise constructor
347 // "Function.x" but newer versions name them "Promise.x". We match both.
348 switch (functionName) {
349 case 'Promise.then':
350 case 'Promise.catch':
351 case 'Promise.finally':
352 case 'Function.reject':
353 case 'Promise.reject':
354 case 'Function.resolve':
355 case 'Promise.resolve':
356 case 'Function.all':
357 case 'Promise.all':
358 case 'Function.allSettled':
359 case 'Promise.allSettled':
360 case 'Function.any':
361 case 'Promise.any':
362 case 'Function.race':
363 case 'Promise.race':
364 case 'Function.try':
365 case 'Promise.try':
366 case 'Function.withResolvers':
367 case 'Promise.withResolvers':
368 return true;
369 default:
370 return false;
371 }
372 }
373
374 export function isAwaitInUserspace(
375 request: Request,
376 stack: ReactStackTrace,
377 ): boolean {
378 let firstFrame = 0;
379 while (
380 stack.length > firstFrame &&
381 isPromiseAwaitInternal(stack[firstFrame][1], stack[firstFrame][0])
382 ) {
383 // Skip the internal frame that awaits itself.
384 firstFrame++;
385 }
386 if (stack.length > firstFrame) {
387 // Check if the very first stack frame that awaited this Promise was in user space.
388 // TODO: This doesn't take into account wrapper functions such as our fake .then()
389 // in FlightClient which will always be considered third party awaits if you call
390 // .then directly.
391 const filterStackFrame = request.filterStackFrame;
392 const callsite = stack[firstFrame];
393 const functionName = callsite[0];
394 const url = devirtualizeURL(callsite[1]);
395 const lineNumber = callsite[2];
396 const columnNumber = callsite[3];
397 return (
398 filterStackFrame(url, functionName, lineNumber, columnNumber) &&
399 url !== ''
400 );
401 }
402 return false;
403 }
404
405 initAsyncDebugInfo();
406
407 function patchConsole(consoleInst: typeof console, methodName: string) {
408 const descriptor = Object.getOwnPropertyDescriptor(consoleInst, methodName);
409 if (
410 descriptor &&
411 (descriptor.configurable || descriptor.writable) &&
412 typeof descriptor.value === 'function'
413 ) {
414 const originalMethod = descriptor.value;
415 const originalName = Object.getOwnPropertyDescriptor(
416 // $FlowFixMe[incompatible-type]: We should be able to get descriptors from any function.
417 originalMethod,
418 'name',
419 );
420 const wrapperMethod = function (this: typeof console) {
421 const request = resolveRequest();
422 if (methodName === 'assert' && arguments[0]) {
423 // assert doesn't emit anything unless first argument is falsy so we can skip it.
424 } else if (request !== null) {
425 // Extract the stack. Not all console logs print the full stack but they have at
426 // least the line it was called from. We could optimize transfer by keeping just
427 // one stack frame but keeping it simple for now and include all frames.
428 const stack = filterStackTrace(
429 request,
430 parseStackTracePrivate(new Error('react-stack-top-frame'), 1) || [],
431 );
432 request.pendingDebugChunks++;
433 const owner: null | ReactComponentInfo = resolveOwner();
434 const args = Array.from(arguments);
435 // Extract the env if this is a console log that was replayed from another env.
436 let env = unbadgeConsole(methodName, args);
437 if (env === null) {
438 // Otherwise add the current environment.
439 env = (0, request.environmentName)();
440 }
441
442 emitConsoleChunk(request, methodName, owner, env, stack, args);
443 }
444 // $FlowFixMe[incompatible-call]
445 // $FlowFixMe[incompatible-type]
446 return originalMethod.apply(this, arguments);
447 };
448 if (originalName) {
449 Object.defineProperty(
450 wrapperMethod,
451 // $FlowFixMe[cannot-write] yes it is
452 'name',
453 originalName,
454 );
455 }
456 Object.defineProperty(consoleInst, methodName, {
457 value: wrapperMethod,
458 });
459 }
460 }
461
462 // $FlowFixMe[invalid-compare]
463 if (__DEV__ && typeof console === 'object' && console !== null) {
464 // Instrument console to capture logs for replaying on the client.
465 patchConsole(console, 'assert');
466 patchConsole(console, 'debug');
467 patchConsole(console, 'dir');
468 patchConsole(console, 'dirxml');
469 patchConsole(console, 'error');
470 patchConsole(console, 'group');
471 patchConsole(console, 'groupCollapsed');
472 patchConsole(console, 'groupEnd');
473 patchConsole(console, 'info');
474 patchConsole(console, 'log');
475 patchConsole(console, 'table');
476 patchConsole(console, 'trace');
477 patchConsole(console, 'warn');
478 }
479
480 function getCurrentStackInDEV(): string {
481 if (__DEV__) {
482 const owner: null | ReactComponentInfo = resolveOwner();
483 if (owner === null) {
484 return '';
485 }
486 return getOwnerStackByComponentInfoInDev(owner);
487 }
488 return '';
489 }
490
491 const ObjectPrototype = Object.prototype;
492
493 const stringify = JSON.stringify;
494
495 type ReactJSONValue =
496 | string
497 | boolean
498 | number
499 | null
500 | $ReadOnlyArray<ReactClientValue>
501 | ReactClientObject;
502
503 // Serializable values
504 export type ReactClientValue =
505 // Server Elements and Lazy Components are unwrapped on the Server
506 | React$Element<component(...props: any)>
507 | LazyComponent<ReactClientValue, any>
508 // References are passed by their value
509 | ClientReference<any>
510 | ServerReference<any>
511 // The rest are passed as is. Sub-types can be passed in but lose their
512 // subtype, so the receiver can only accept once of these.
513 | React$Element<string>
514 | React$Element<ClientReference<any> & any>
515 | ReactComponentInfo
516 | ReactErrorInfo
517 | string
518 | boolean
519 | number
520 | symbol
521 | null
522 | void
523 | bigint
524 | ReadableStream
525 | $AsyncIterable<ReactClientValue, ReactClientValue, void>
526 | $AsyncIterator<ReactClientValue, ReactClientValue, void>
527 | Iterable<ReactClientValue>
528 | Iterator<ReactClientValue>
529 | Array<ReactClientValue>
530 | Map<ReactClientValue, ReactClientValue>
531 | Set<ReactClientValue>
532 | FormData
533 | $ArrayBufferView
534 | ArrayBuffer
535 | Date
536 | ReactClientObject
537 | Promise<ReactClientValue>; // Thenable<ReactClientValue>
538
539 type ReactClientObject = {+[key: string]: ReactClientValue};
540
541 // task status
542 const PENDING = 0;
543 const COMPLETED = 1;
544 const ABORTED = 3;
545 const ERRORED = 4;
546 const RENDERING = 5;
547
548 type Task = {
549 id: number,
550 status: 0 | 1 | 3 | 4 | 5,
551 model: ReactClientValue,
552 ping: () => void,
553 keyPath: ReactKey, // parent server component keys
554 implicitSlot: boolean, // true if the root server component of this sequence had a null key
555 formatContext: FormatContext, // an approximate parent context from host components
556 thenableState: ThenableState | null,
557 timed: boolean, // Profiling-only. Whether we need to track the completion time of this task.
558 time: number, // Profiling-only. The last time stamp emitted for this task.
559 environmentName: string, // DEV-only. Used to track if the environment for this task changed.
560 debugOwner: null | ReactComponentInfo, // DEV-only
561 debugStack: null | Error, // DEV-only
562 debugTask: null | ConsoleTask, // DEV-only
563 };
564
565 interface Reference {}
566
567 type ReactClientReference = Reference & ReactClientValue;
568
569 type DeferredDebugStore = {
570 retained: Map<number, ReactClientReference | string>,
571 existing: Map<ReactClientReference | string, number>,
572 };
573
574 const __PROTO__ = '__proto__';
575
576 const OPENING = 10;
577 const OPEN = 11;
578 const ABORTING = 12;
579 const CLOSING = 13;
580 const CLOSED = 14;
581
582 const RENDER = 20;
583 const PRERENDER = 21;
584
585 // Marker pushed before a [headerChunk, contentChunk] pair in
586 // completedRegularChunks / completedDebugChunks to signal that the next two
587 // entries must be written atomically — see emitTextChunk and
588 // emitTypedArrayChunk for why, and flushCompletedChunks for how it's read.
589 const NEXT_TWO_CHUNKS_ARE_ATOMIC: symbol = Symbol();
590
591 export type Request = {
592 status: 10 | 11 | 12 | 13 | 14,
593 type: 20 | 21,
594 flushScheduled: boolean,
595 fatalError: mixed,
596 destination: null | Destination,
597 bundlerConfig: ClientManifest,
598 cache: Map<Function, mixed>,
599 cacheController: AbortController,
600 nextChunkId: number,
601 pendingChunks: number,
602 hints: Hints,
603 abortableTasks: Set<Task>,
604 pingedTasks: Array<Task>,
605 completedImportChunks: Array<Chunk>,
606 completedHintChunks: Array<Chunk>,
607 // Text and TypedArray rows are pushed as a NEXT_TWO_CHUNKS_ARE_ATOMIC
608 // sentinel followed by their [headerChunk, contentChunk] pair, so that
609 // flushCompletedChunks can write the pair atomically and never strand the
610 // content chunk on a backpressure break.
611 completedRegularChunks: Array<
612 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,
613 >,
614 completedErrorChunks: Array<Chunk>,
615 writtenSymbols: Map<symbol, number>,
616 writtenClientReferences: Map<ClientReferenceKey, number>,
617 writtenServerReferences: Map<ServerReference<any>, number>,
618 writtenObjects: WeakMap<Reference, string>,
619 writtenImportStrings: Map<string, string>,
620 // The combined length of the keys in writtenImportStrings.
621 writtenImportStringsSize: number,
622 temporaryReferences: void | TemporaryReferenceSet,
623 identifierPrefix: string,
624 identifierCount: number,
625 taintCleanupQueue: Array<string | bigint>,
626 onError: (error: mixed) => ?string,
627 onAllReady: () => void,
628 onFatalError: mixed => void,
629 // Profiling-only
630 timeOrigin: number,
631 abortTime: number,
632 // DEV-only
633 pendingDebugChunks: number,
634 // See completedRegularChunks for why some entries are preceded by the
635 // NEXT_TWO_CHUNKS_ARE_ATOMIC sentinel.
636 completedDebugChunks: Array<
637 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,
638 >,
639 debugDestination: null | Destination,
640 environmentName: () => string,
641 filterStackFrame: (
642 url: string,
643 functionName: string,
644 lineNumber: number,
645 columnNumber: number,
646 ) => boolean,
647 didWarnForKey: null | WeakSet<ReactComponentInfo>,
648 writtenDebugObjects: WeakMap<Reference, string>,
649 deferredDebugObjects: null | DeferredDebugStore,
650 };
651
652 const {
653 TaintRegistryObjects,
654 TaintRegistryValues,
655 TaintRegistryByteLengths,
656 TaintRegistryPendingRequests,
657 } = ReactSharedInternals;
658
659 function throwTaintViolation(message: string) {
660 // eslint-disable-next-line react-internal/prod-error-codes
661 throw new Error(message);
662 }
663
664 function cleanupTaintQueue(request: Request): void {
665 const cleanupQueue = request.taintCleanupQueue;
666 TaintRegistryPendingRequests.delete(cleanupQueue);
667 for (let i = 0; i < cleanupQueue.length; i++) {
668 const entryValue = cleanupQueue[i];
669 const entry = TaintRegistryValues.get(entryValue);
670 if (entry !== undefined) {
671 if (entry.count === 1) {
672 TaintRegistryValues.delete(entryValue);
673 } else {
674 entry.count--;
675 }
676 }
677 }
678 cleanupQueue.length = 0;
679 }
680
681 function defaultErrorHandler(error: mixed) {
682 console['error'](error);
683 // Don't transform to our wrapper
684 }
685
686 function RequestInstance(
687 this: $FlowFixMe,
688 type: 20 | 21,
689 model: ReactClientValue,
690 bundlerConfig: ClientManifest,
691 onError: void | ((error: mixed) => ?string),
692 onAllReady: () => void,
693 onFatalError: (error: mixed) => void,
694 identifierPrefix?: string,
695 temporaryReferences: void | TemporaryReferenceSet,
696 debugStartTime: void | number, // Profiling-only
697 environmentName: void | string | (() => string), // DEV-only
698 filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
699 keepDebugAlive: boolean, // DEV-only
700 ) {
701 if (
702 ReactSharedInternals.A !== null &&
703 ReactSharedInternals.A !== DefaultAsyncDispatcher
704 ) {
705 throw new Error(
706 'Currently React only supports one RSC renderer at a time.',
707 );
708 }
709 ReactSharedInternals.A = DefaultAsyncDispatcher;
710 if (__DEV__) {
711 // Unlike Fizz or Fiber, we don't reset this and just keep it on permanently.
712 // This lets it act more like the AsyncDispatcher so that we can get the
713 // stack asynchronously too.
714 ReactSharedInternals.getCurrentStack = getCurrentStackInDEV;
715 }
716
717 const abortSet: Set<Task> = new Set();
718 const pingedTasks: Array<Task> = [];
719 const cleanupQueue: Array<string | bigint> = [];
720 if (enableTaint) {
721 TaintRegistryPendingRequests.add(cleanupQueue);
722 }
723 const hints = createHints();
724 this.type = type;
725 this.status = OPENING;
726 this.flushScheduled = false;
727 this.fatalError = null;
728 this.destination = null;
729 this.bundlerConfig = bundlerConfig;
730 this.cache = new Map();
731 this.cacheController = new AbortController();
732 this.nextChunkId = 0;
733 this.pendingChunks = 0;
734 this.hints = hints;
735 this.abortableTasks = abortSet;
736 this.pingedTasks = pingedTasks;
737 this.completedImportChunks = [] as Array<Chunk>;
738 this.completedHintChunks = [] as Array<Chunk>;
739 this.completedRegularChunks = [] as Array<
740 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,
741 >;
742 this.completedErrorChunks = [] as Array<Chunk>;
743 this.writtenSymbols = new Map();
744 this.writtenClientReferences = new Map();
745 this.writtenServerReferences = new Map();
746 this.writtenObjects = new WeakMap();
747 this.writtenImportStrings = new Map();
748 this.writtenImportStringsSize = 0;
749 this.temporaryReferences = temporaryReferences;
750 this.identifierPrefix = identifierPrefix || '';
751 this.identifierCount = 1;
752 this.taintCleanupQueue = cleanupQueue;
753 this.onError = onError === undefined ? defaultErrorHandler : onError;
754 this.onAllReady = onAllReady;
755 this.onFatalError = onFatalError;
756
757 if (__DEV__) {
758 this.pendingDebugChunks = 0;
759 this.completedDebugChunks = [] as Array<
760 Chunk | BinaryChunk | typeof NEXT_TWO_CHUNKS_ARE_ATOMIC,
761 >;
762 this.debugDestination = null;
763 this.environmentName =
764 environmentName === undefined
765 ? () => 'Server'
766 : typeof environmentName !== 'function'
767 ? () => environmentName
768 : environmentName;
769 this.filterStackFrame =
770 filterStackFrame === undefined
771 ? defaultFilterStackFrame
772 : filterStackFrame;
773 this.didWarnForKey = null;
774 this.writtenDebugObjects = new WeakMap();
775 this.deferredDebugObjects = keepDebugAlive
776 ? {
777 retained: new Map(),
778 existing: new Map(),
779 }
780 : null;
781 }
782
783 let timeOrigin: number;
784 if (
785 enableProfilerTimer &&
786 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
787 ) {
788 // We start by serializing the time origin. Any future timestamps will be
789 // emitted relatively to this origin. Instead of using performance.timeOrigin
790 // as this origin, we use the timestamp at the start of the request.
791 // This avoids leaking unnecessary information like how long the server has
792 // been running and allows for more compact representation of each timestamp.
793 // The time origin is stored as an offset in the time space of this environment.
794 if (typeof debugStartTime === 'number') {
795 // We expect `startTime` to be an absolute timestamp, so relativize it to match the other case.
796 timeOrigin = this.timeOrigin =
797 debugStartTime -
798 // $FlowFixMe[prop-missing]
799 performance.timeOrigin;
800 } else {
801 timeOrigin = this.timeOrigin = performance.now();
802 }
803 emitTimeOriginChunk(
804 this,
805 timeOrigin +
806 // $FlowFixMe[prop-missing]
807 performance.timeOrigin,
808 );
809 this.abortTime = -0.0;
810 } else {
811 timeOrigin = 0;
812 }
813
814 const rootTask = createTask(
815 this,
816 model,
817 null,
818 false,
819 createRootFormatContext(),
820 abortSet,
821 timeOrigin,
822 null,
823 null,
824 null,
825 );
826 pingedTasks.push(rootTask);
827 }
828
829 export function createRequest(
830 model: ReactClientValue,
831 bundlerConfig: ClientManifest,
832 onError: void | ((error: mixed) => ?string),
833 identifierPrefix: void | string,
834 temporaryReferences: void | TemporaryReferenceSet,
835 debugStartTime: void | number, // Profiling-only
836 environmentName: void | string | (() => string), // DEV-only
837 filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
838 keepDebugAlive: boolean, // DEV-only
839 ): Request {
840 if (__DEV__) {
841 resetOwnerStackLimit();
842 }
843
844 // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
845 return new RequestInstance(
846 RENDER,
847 model,
848 bundlerConfig,
849 onError,
850 noop,
851 noop,
852 identifierPrefix,
853 temporaryReferences,
854 debugStartTime,
855 environmentName,
856 filterStackFrame,
857 keepDebugAlive,
858 );
859 }
860
861 export function createPrerenderRequest(
862 model: ReactClientValue,
863 bundlerConfig: ClientManifest,
864 onAllReady: () => void,
865 onFatalError: () => void,
866 onError: void | ((error: mixed) => ?string),
867 identifierPrefix: void | string,
868 temporaryReferences: void | TemporaryReferenceSet,
869 debugStartTime: void | number, // Profiling-only
870 environmentName: void | string | (() => string), // DEV-only
871 filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
872 keepDebugAlive: boolean, // DEV-only
873 ): Request {
874 if (__DEV__) {
875 resetOwnerStackLimit();
876 }
877
878 // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
879 return new RequestInstance(
880 PRERENDER,
881 model,
882 bundlerConfig,
883 onError,
884 onAllReady,
885 onFatalError,
886 identifierPrefix,
887 temporaryReferences,
888 debugStartTime,
889 environmentName,
890 filterStackFrame,
891 keepDebugAlive,
892 );
893 }
894
895 let currentRequest: null | Request = null;
896
897 export function resolveRequest(): null | Request {
898 if (currentRequest) return currentRequest;
899 // $FlowFixMe[constant-condition]
900 if (supportsRequestStorage) {
901 const store = requestStorage.getStore();
902 if (store) return store;
903 }
904 return null;
905 }
906
907 function isTypedArray(value: any): boolean {
908 if (value instanceof ArrayBuffer) {
909 return true;
910 }
911 if (value instanceof Int8Array) {
912 return true;
913 }
914 if (value instanceof Uint8Array) {
915 return true;
916 }
917 if (value instanceof Uint8ClampedArray) {
918 return true;
919 }
920 if (value instanceof Int16Array) {
921 return true;
922 }
923 if (value instanceof Uint16Array) {
924 return true;
925 }
926 if (value instanceof Int32Array) {
927 return true;
928 }
929 if (value instanceof Uint32Array) {
930 return true;
931 }
932 if (value instanceof Float32Array) {
933 return true;
934 }
935 if (value instanceof Float64Array) {
936 return true;
937 }
938 if (value instanceof BigInt64Array) {
939 return true;
940 }
941 if (value instanceof BigUint64Array) {
942 return true;
943 }
944 if (value instanceof DataView) {
945 return true;
946 }
947 return false;
948 }
949
950 function serializeDebugThenable(
951 request: Request,
952 counter: {objectLimit: number},
953 thenable: Thenable<any>,
954 ): string {
955 // Like serializeThenable but for renderDebugModel
956 request.pendingDebugChunks++;
957 const id = request.nextChunkId++;
958 const ref = serializePromiseID(id);
959 request.writtenDebugObjects.set(thenable, ref);
960
961 switch (thenable.status) {
962 case 'fulfilled': {
963 emitOutlinedDebugModelChunk(request, id, counter, thenable.value);
964 return ref;
965 }
966 case 'rejected': {
967 const x = thenable.reason;
968 // We don't log these errors since they didn't actually throw into Flight.
969 const digest = '';
970 emitErrorChunk(request, id, digest, x, true, null);
971 return ref;
972 }
973 }
974
975 if (request.status === ABORTING) {
976 // Ensure that we have time to emit the halt chunk if we're sync aborting.
977 emitDebugHaltChunk(request, id);
978 return ref;
979 }
980
981 const deferredDebugObjects = request.deferredDebugObjects;
982 if (deferredDebugObjects !== null) {
983 // For Promises that are not yet resolved, we always defer them. They are async anyway so it's
984 // safe to defer them. This also ensures that we don't eagerly call .then() on a Promise that
985 // otherwise wouldn't have initialized. It also ensures that we don't "handle" a rejection
986 // that otherwise would have triggered unhandled rejection.
987 deferredDebugObjects.retained.set(id, thenable as any);
988 const deferredRef = '$Y@' + id.toString(16);
989 // We can now refer to the deferred object in the future.
990 request.writtenDebugObjects.set(thenable, deferredRef);
991 return deferredRef;
992 }
993
994 let cancelled = false;
995
996 thenable.then(
997 value => {
998 if (cancelled) {
999 return;
1000 }
1001 cancelled = true;
1002 if (request.status === ABORTING) {
1003 emitDebugHaltChunk(request, id);
1004 enqueueFlush(request);
1005 return;
1006 }
1007 if (
1008 (isArray(value) && value.length > 200) ||
1009 (isTypedArray(value) && value.byteLength > 1000)
1010 ) {
1011 // If this should be deferred, but we don't have a debug channel installed
1012 // it would get omitted. We can't omit outlined models but we can avoid
1013 // resolving the Promise at all by halting it.
1014 emitDebugHaltChunk(request, id);
1015 enqueueFlush(request);
1016 return;
1017 }
1018 emitOutlinedDebugModelChunk(request, id, counter, value);
1019 enqueueFlush(request);
1020 },
1021 reason => {
1022 if (cancelled) {
1023 return;
1024 }
1025 cancelled = true;
1026 if (request.status === ABORTING) {
1027 emitDebugHaltChunk(request, id);
1028 enqueueFlush(request);
1029 return;
1030 }
1031 // We don't log these errors since they didn't actually throw into Flight.
1032 const digest = '';
1033 emitErrorChunk(request, id, digest, reason, true, null);
1034 enqueueFlush(request);
1035 },
1036 );
1037
1038 // We don't use scheduleMicrotask here because it doesn't actually schedule a microtask
1039 // in all our configs which is annoying.
1040 Promise.resolve().then(() => {
1041 // If we don't resolve the Promise within a microtask. Leave it as hanging since we
1042 // don't want to block the render forever on a Promise that might never resolve.
1043 if (cancelled) {
1044 return;
1045 }
1046 cancelled = true;
1047 emitDebugHaltChunk(request, id);
1048 enqueueFlush(request);
1049 // Clean up the request so we don't leak this forever.
1050 request = null as any;
1051 counter = null as any;
1052 });
1053
1054 return ref;
1055 }
1056
1057 function emitRequestedDebugThenable(
1058 request: Request,
1059 id: number,
1060 counter: {objectLimit: number},
1061 thenable: Thenable<any>,
1062 ): void {
1063 thenable.then(
1064 value => {
1065 if (request.status === ABORTING) {
1066 emitDebugHaltChunk(request, id);
1067 enqueueFlush(request);
1068 return;
1069 }
1070 emitOutlinedDebugModelChunk(request, id, counter, value);
1071 enqueueFlush(request);
1072 },
1073 reason => {
1074 if (request.status === ABORTING) {
1075 emitDebugHaltChunk(request, id);
1076 enqueueFlush(request);
1077 return;
1078 }
1079 // We don't log these errors since they didn't actually throw into Flight.
1080 const digest = '';
1081 emitErrorChunk(request, id, digest, reason, true, null);
1082 enqueueFlush(request);
1083 },
1084 );
1085 }
1086
1087 function createThenableTask(
1088 request: Request,
1089 task: Task,
1090 thenable: Thenable<any>,
1091 ): Task {
1092 return createTask(
1093 request,
1094 thenable as any, // will be replaced by the value before we retry. used for debug info.
1095 task.keyPath, // the server component sequence continues through Promise-as-a-child.
1096 task.implicitSlot,
1097 task.formatContext,
1098 request.abortableTasks,
1099 enableProfilerTimer &&
1100 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
1101 ? task.time
1102 : 0,
1103 __DEV__ ? task.debugOwner : null,
1104 __DEV__ ? task.debugStack : null,
1105 __DEV__ ? task.debugTask : null,
1106 );
1107 }
1108
1109 function serializeThenable(
1110 request: Request,
1111 task: Task,
1112 thenable: Thenable<any>,
1113 ): number {
1114 switch (thenable.status) {
1115 case 'fulfilled': {
1116 const newTask = createThenableTask(request, task, thenable);
1117 forwardDebugInfoFromThenable(request, newTask, thenable, null, null);
1118 // We have the resolved value, we can go ahead and schedule it for serialization.
1119 newTask.model = thenable.value;
1120 pingTask(request, newTask);
1121 return newTask.id;
1122 }
1123 case 'rejected': {
1124 const newTask = createThenableTask(request, task, thenable);
1125 forwardDebugInfoFromThenable(request, newTask, thenable, null, null);
1126 const x = thenable.reason;
1127 erroredTask(request, newTask, x);
1128 return newTask.id;
1129 }
1130 case 'pending_weak': {
1131 if (enableFlightWeakThenables) {
1132 // A weak-pending thenable doesn't block the stream from closing, so
1133 // we don't create a task for it yet. We only reserve an id for its
1134 // reference. If it settles while the stream is still open, we
1135 // create the task at that point, the same as if we had serialized
1136 // an already settled thenable.
1137 //
1138 // Delivery is driven by the thenable's notification. If the stream
1139 // closes before the listeners are notified, the value is dropped
1140 // and the reference is left unfulfilled. Since the stream may close
1141 // synchronously when the last task completes, a thenable that
1142 // notifies its listeners synchronously (unlike a native Promise,
1143 // which notifies in a microtask) is guaranteed delivery of any
1144 // value it settles with before the stream closes.
1145 const id = request.nextChunkId++;
1146 // The parent task is mutated as serialization continues, so we
1147 // snapshot the context that the new task needs if it's created
1148 // later.
1149 const keyPath = task.keyPath;
1150 const implicitSlot = task.implicitSlot;
1151 const formatContext = task.formatContext;
1152 const lastTimestamp =
1153 enableProfilerTimer &&
1154 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
1155 ? task.time
1156 : 0;
1157 const debugOwner = __DEV__ ? task.debugOwner : null;
1158 const debugStack = __DEV__ ? task.debugStack : null;
1159 const debugTask = __DEV__ ? task.debugTask : null;
1160 let settled = false;
1161 thenable.then(
1162 (value: any) => {
1163 if (settled || request.status > OPEN) {
1164 // Too late. The stream already closed (or the request was
1165 // aborted), so the reference stays unfulfilled.
1166 return;
1167 }
1168 settled = true;
1169 const newTask = createTaskWithID(
1170 request,
1171 id,
1172 value,
1173 keyPath,
1174 implicitSlot,
1175 formatContext,
1176 request.abortableTasks,
1177 lastTimestamp,
1178 debugOwner,
1179 debugStack,
1180 debugTask,
1181 );
1182 forwardDebugInfoFromCurrentContext(request, newTask, thenable);
1183 pingTask(request, newTask);
1184 },
1185 (reason: mixed) => {
1186 if (settled || request.status > OPEN) {
1187 return;
1188 }
1189 settled = true;
1190 const newTask = createTaskWithID(
1191 request,
1192 id,
1193 thenable as any, // never rendered. used for debug info.
1194 keyPath,
1195 implicitSlot,
1196 formatContext,
1197 request.abortableTasks,
1198 lastTimestamp,
1199 debugOwner,
1200 debugStack,
1201 debugTask,
1202 );
1203 if (
1204 enableProfilerTimer &&
1205 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
1206 ) {
1207 // If this is async we need to time when this task finishes.
1208 newTask.timed = true;
1209 }
1210 erroredTask(request, newTask, reason);
1211 enqueueFlush(request);
1212 },
1213 );
1214 return id;
1215 }
1216 // Fallthrough
1217 }
1218 default: {
1219 const newTask = createThenableTask(request, task, thenable);
1220 if (request.status === ABORTING) {
1221 // We can no longer accept any resolved values
1222 request.abortableTasks.delete(newTask);
1223 if (request.type === PRERENDER) {
1224 haltTask(newTask, request);
1225 finishHaltedTask(newTask, request);
1226 } else {
1227 const errorId: number = request.fatalError as any;
1228 abortTask(newTask, request, errorId);
1229 finishAbortedTask(newTask, request, errorId);
1230 }
1231 return newTask.id;
1232 }
1233 if (typeof thenable.status !== 'string') {
1234 // Only instrument the thenable if the status if not defined. If
1235 // it's defined, but an unknown value, assume it's been instrumented by
1236 // some custom userspace implementation. We treat it as "pending".
1237 const pendingThenable: PendingThenable<mixed> = thenable as any;
1238 pendingThenable.status = 'pending';
1239 pendingThenable.then(
1240 fulfilledValue => {
1241 if (thenable.status === 'pending') {
1242 const fulfilledThenable: FulfilledThenable<mixed> =
1243 thenable as any;
1244 fulfilledThenable.status = 'fulfilled';
1245 fulfilledThenable.value = fulfilledValue;
1246 }
1247 },
1248 (error: mixed) => {
1249 if (thenable.status === 'pending') {
1250 const rejectedThenable: RejectedThenable<mixed> = thenable as any;
1251 rejectedThenable.status = 'rejected';
1252 rejectedThenable.reason = error;
1253 }
1254 },
1255 );
1256 }
1257 thenable.then(
1258 value => {
1259 forwardDebugInfoFromCurrentContext(request, newTask, thenable);
1260 newTask.model = value;
1261 pingTask(request, newTask);
1262 },
1263 reason => {
1264 if (newTask.status === PENDING) {
1265 if (
1266 enableProfilerTimer &&
1267 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
1268 ) {
1269 // If this is async we need to time when this task finishes.
1270 newTask.timed = true;
1271 }
1272 // We expect that the only status it might be otherwise is ABORTED.
1273 // When we abort we emit chunks in each pending task slot and don't need
1274 // to do so again here.
1275 erroredTask(request, newTask, reason);
1276 enqueueFlush(request);
1277 }
1278 },
1279 );
1280 return newTask.id;
1281 }
1282 }
1283 }
1284
1285 function serializeReadableStream(
1286 request: Request,
1287 task: Task,
1288 stream: ReadableStream,
1289 ): string {
1290 // Detect if this is a BYOB stream. BYOB streams should be able to be read as bytes on the
1291 // receiving side. It also implies that different chunks can be split up or merged as opposed
1292 // to a readable stream that happens to have Uint8Array as the type which might expect it to be
1293 // received in the same slices.
1294 // $FlowFixMe[prop-missing]: This is a Node.js extension.
1295 let supportsBYOB: void | boolean = stream.supportsBYOB;
1296 if (supportsBYOB === undefined) {
1297 try {
1298 // $FlowFixMe[extra-arg]: This argument is accepted.
1299 stream.getReader({mode: 'byob'}).releaseLock();
1300 supportsBYOB = true;
1301 } catch (x) {
1302 supportsBYOB = false;
1303 }
1304 }
1305 // At this point supportsBYOB is guaranteed to be a boolean.
1306 const isByteStream: boolean = supportsBYOB;
1307
1308 const reader = stream.getReader();
1309
1310 // This task won't actually be retried. We just use it to attempt synchronous renders.
1311 const streamTask = createTask(
1312 request,
1313 task.model,
1314 task.keyPath,
1315 task.implicitSlot,
1316 task.formatContext,
1317 request.abortableTasks,
1318 enableProfilerTimer &&
1319 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
1320 ? task.time
1321 : 0,
1322 __DEV__ ? task.debugOwner : null,
1323 __DEV__ ? task.debugStack : null,
1324 __DEV__ ? task.debugTask : null,
1325 );
1326
1327 // The task represents the Stop row. This adds a Start row.
1328 request.pendingChunks++;
1329 const startStreamRow =
1330 streamTask.id.toString(16) + ':' + (isByteStream ? 'r' : 'R') + '\n';
1331 request.completedRegularChunks.push(stringToChunk(startStreamRow));
1332
1333 function progress(entry: {done: boolean, value: ReactClientValue, ...}) {
1334 if (streamTask.status !== PENDING) {
1335 return;
1336 }
1337
1338 if (entry.done) {
1339 streamTask.status = COMPLETED;
1340 const endStreamRow = streamTask.id.toString(16) + ':C\n';
1341 request.completedRegularChunks.push(stringToChunk(endStreamRow));
1342 request.abortableTasks.delete(streamTask);
1343 request.cacheController.signal.removeEventListener('abort', abortStream);
1344 enqueueFlush(request);
1345 callOnAllReadyIfReady(request);
1346 } else {
1347 try {
1348 request.pendingChunks++;
1349 streamTask.model = entry.value;
1350 if (isByteStream) {
1351 // Chunks of byte streams are always Uint8Array instances.
1352 const chunk: Uint8Array = streamTask.model as any;
1353 emitTypedArrayChunk(request, streamTask.id, 'b', chunk, false);
1354 } else {
1355 tryStreamTask(request, streamTask);
1356 }
1357 enqueueFlush(request);
1358 reader.read().then(progress, error);
1359 } catch (x) {
1360 error(x);
1361 }
1362 }
1363 }
1364 function error(reason: mixed) {
1365 if (streamTask.status !== PENDING) {
1366 return;
1367 }
1368 request.cacheController.signal.removeEventListener('abort', abortStream);
1369 erroredTask(request, streamTask, reason);
1370 enqueueFlush(request);
1371
1372 // $FlowFixMe[incompatible-type] should be able to pass mixed
1373 // $FlowFixMe[incompatible-use]
1374 reader.cancel(reason).then(error, error);
1375 }
1376 function abortStream() {
1377 if (streamTask.status !== PENDING) {
1378 return;
1379 }
1380 const signal = request.cacheController.signal;
1381 signal.removeEventListener('abort', abortStream);
1382 const reason = signal.reason;
1383 if (request.type === PRERENDER) {
1384 request.abortableTasks.delete(streamTask);
1385 haltTask(streamTask, request);
1386 finishHaltedTask(streamTask, request);
1387 } else {
1388 // TODO: Make this use abortTask() instead.
1389 erroredTask(request, streamTask, reason);
1390 enqueueFlush(request);
1391 }
1392 // $FlowFixMe[incompatible-use] should be able to pass mixed
1393 reader.cancel(reason).then(error, error);
1394 }
1395
1396 request.cacheController.signal.addEventListener('abort', abortStream);
1397 reader.read().then(progress, error);
1398 return serializeByValueID(streamTask.id);
1399 }
1400
1401 function serializeAsyncIterable(
1402 request: Request,
1403 task: Task,
1404 iterable: $AsyncIterable<ReactClientValue, ReactClientValue, void>,
1405 iterator: $AsyncIterator<ReactClientValue, ReactClientValue, void>,
1406 ): string {
1407 // Generators/Iterators are Iterables but they're also their own iterator
1408 // functions. If that's the case, we treat them as single-shot. Otherwise,
1409 // we assume that this iterable might be a multi-shot and allow it to be
1410 // iterated more than once on the client.
1411 const isIterator = iterable === iterator;
1412
1413 // This task won't actually be retried. We just use it to attempt synchronous renders.
1414 const streamTask = createTask(
1415 request,
1416 task.model,
1417 task.keyPath,
1418 task.implicitSlot,
1419 task.formatContext,
1420 request.abortableTasks,
1421 enableProfilerTimer &&
1422 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
1423 ? task.time
1424 : 0,
1425 __DEV__ ? task.debugOwner : null,
1426 __DEV__ ? task.debugStack : null,
1427 __DEV__ ? task.debugTask : null,
1428 );
1429
1430 if (__DEV__) {
1431 const debugInfo: ?ReactDebugInfo = (iterable as any)._debugInfo;
1432 if (debugInfo) {
1433 forwardDebugInfo(request, streamTask, debugInfo);
1434 }
1435 }
1436
1437 // The task represents the Stop row. This adds a Start row.
1438 request.pendingChunks++;
1439 const startStreamRow =
1440 streamTask.id.toString(16) + ':' + (isIterator ? 'x' : 'X') + '\n';
1441 request.completedRegularChunks.push(stringToChunk(startStreamRow));
1442
1443 function progress(
1444 entry:
1445 | {done: false, +value: ReactClientValue, ...}
1446 | {done: true, +value: ReactClientValue, ...},
1447 ) {
1448 if (streamTask.status !== PENDING) {
1449 return;
1450 }
1451
1452 if (entry.done) {
1453 streamTask.status = COMPLETED;
1454 let endStreamRow;
1455 if (entry.value === undefined) {
1456 endStreamRow = streamTask.id.toString(16) + ':C\n';
1457 } else {
1458 // Unlike streams, the last value may not be undefined. If it's not
1459 // we outline it and encode a reference to it in the closing instruction.
1460 try {
1461 const chunkId = outlineModel(request, entry.value);
1462 endStreamRow =
1463 streamTask.id.toString(16) +
1464 ':C' +
1465 stringify(serializeByValueID(chunkId)) +
1466 '\n';
1467 } catch (x) {
1468 error(x);
1469 return;
1470 }
1471 }
1472 request.completedRegularChunks.push(stringToChunk(endStreamRow));
1473 request.abortableTasks.delete(streamTask);
1474 request.cacheController.signal.removeEventListener(
1475 'abort',
1476 abortIterable,
1477 );
1478 enqueueFlush(request);
1479 callOnAllReadyIfReady(request);
1480 } else {
1481 try {
1482 streamTask.model = entry.value;
1483 request.pendingChunks++;
1484 tryStreamTask(request, streamTask);
1485 enqueueFlush(request);
1486 if (__DEV__) {
1487 callIteratorInDEV(iterator, progress, error);
1488 } else {
1489 iterator.next().then(progress, error);
1490 }
1491 } catch (x) {
1492 error(x);
1493 return;
1494 }
1495 }
1496 }
1497 function error(reason: mixed) {
1498 if (streamTask.status !== PENDING) {
1499 return;
1500 }
1501 request.cacheController.signal.removeEventListener('abort', abortIterable);
1502 erroredTask(request, streamTask, reason);
1503 enqueueFlush(request);
1504 if (typeof (iterator as any).throw === 'function') {
1505 // The iterator protocol doesn't necessarily include this but a generator do.
1506 // $FlowFixMe[prop-missing] should be able to pass mixed
1507 iterator.throw(reason).then(noop, noop);
1508 }
1509 }
1510 function abortIterable() {
1511 if (streamTask.status !== PENDING) {
1512 return;
1513 }
1514 const signal = request.cacheController.signal;
1515 signal.removeEventListener('abort', abortIterable);
1516 const reason = signal.reason;
1517 if (request.type === PRERENDER) {
1518 request.abortableTasks.delete(streamTask);
1519 haltTask(streamTask, request);
1520 finishHaltedTask(streamTask, request);
1521 } else {
1522 // TODO: Make this use abortTask() instead.
1523 erroredTask(request, streamTask, signal.reason);
1524 enqueueFlush(request);
1525 }
1526 if (typeof (iterator as any).throw === 'function') {
1527 // TODO: Premature exits should call return() on the iterator if it exists
1528 // to allow cleanup. See https://tc39.es/ecma262/multipage/control-abstraction-objects.html#table-async-iterator-optional
1529 // The iterator protocol doesn't necessarily include this but a generator do.
1530 // $FlowFixMe[prop-missing] should be able to pass mixed
1531 iterator.throw(reason).then(noop, noop);
1532 }
1533 }
1534 request.cacheController.signal.addEventListener('abort', abortIterable);
1535 if (__DEV__) {
1536 callIteratorInDEV(iterator, progress, error);
1537 } else {
1538 iterator.next().then(progress, error);
1539 }
1540 return serializeByValueID(streamTask.id);
1541 }
1542
1543 export function emitHint<Code: HintCode>(
1544 request: Request,
1545 code: Code,
1546 model: HintModel<Code>,
1547 ): void {
1548 emitHintChunk(request, code, model);
1549 enqueueFlush(request);
1550 }
1551
1552 export function getHints(request: Request): Hints {
1553 return request.hints;
1554 }
1555
1556 export function getCache(request: Request): Map<Function, mixed> {
1557 return request.cache;
1558 }
1559
1560 function readThenable<T>(thenable: Thenable<T>): T {
1561 if (thenable.status === 'fulfilled') {
1562 return thenable.value;
1563 } else if (thenable.status === 'rejected') {
1564 throw thenable.reason;
1565 }
1566 throw thenable;
1567 }
1568
1569 function createLazyWrapperAroundWakeable(
1570 request: Request,
1571 task: Task,
1572 wakeable: Wakeable,
1573 ) {
1574 // This is a temporary fork of the `use` implementation until we accept
1575 // promises everywhere.
1576 const thenable: Thenable<mixed> = wakeable as any;
1577 switch (thenable.status) {
1578 case 'fulfilled': {
1579 forwardDebugInfoFromThenable(request, task, thenable, null, null);
1580 return thenable.value;
1581 }
1582 case 'rejected':
1583 forwardDebugInfoFromThenable(request, task, thenable, null, null);
1584 break;
1585 default: {
1586 if (typeof thenable.status === 'string') {
1587 // Only instrument the thenable if the status if not defined. If
1588 // it's defined, but an unknown value, assume it's been instrumented by
1589 // some custom userspace implementation. We treat it as "pending".
1590 break;
1591 }
1592 const pendingThenable: PendingThenable<mixed> = thenable as any;
1593 pendingThenable.status = 'pending';
1594 pendingThenable.then(
1595 fulfilledValue => {
1596 forwardDebugInfoFromCurrentContext(request, task, thenable);
1597 if (thenable.status === 'pending') {
1598 const fulfilledThenable: FulfilledThenable<mixed> = thenable as any;
1599 fulfilledThenable.status = 'fulfilled';
1600 fulfilledThenable.value = fulfilledValue;
1601 }
1602 },
1603 (error: mixed) => {
1604 forwardDebugInfoFromCurrentContext(request, task, thenable);
1605 if (thenable.status === 'pending') {
1606 const rejectedThenable: RejectedThenable<mixed> = thenable as any;
1607 rejectedThenable.status = 'rejected';
1608 rejectedThenable.reason = error;
1609 }
1610 },
1611 );
1612 break;
1613 }
1614 }
1615 const lazyType: LazyComponent<any, Thenable<any>> = {
1616 $$typeof: REACT_LAZY_TYPE,
1617 _payload: thenable,
1618 _init: readThenable,
1619 };
1620 return lazyType;
1621 }
1622
1623 function callWithDebugContextInDEV<A, T>(
1624 request: Request,
1625 task: Task,
1626 callback: A => T,
1627 arg: A,
1628 ): T {
1629 // We don't have a Server Component instance associated with this callback and
1630 // the nearest context is likely a Client Component being serialized. We create
1631 // a fake owner during this callback so we can get the stack trace from it.
1632 // This also gets sent to the client as the owner for the replaying log.
1633 const componentDebugInfo: ReactComponentInfo = {
1634 name: '',
1635 env: task.environmentName,
1636 key: null,
1637 owner: task.debugOwner,
1638 };
1639 // $FlowFixMe[cannot-write]
1640 componentDebugInfo.stack =
1641 task.debugStack === null
1642 ? null
1643 : filterStackTrace(request, parseStackTrace(task.debugStack, 1));
1644 // $FlowFixMe[cannot-write]
1645 componentDebugInfo.debugStack = task.debugStack;
1646 // $FlowFixMe[cannot-write]
1647 componentDebugInfo.debugTask = task.debugTask;
1648 const debugTask = task.debugTask;
1649 // We don't need the async component storage context here so we only set the
1650 // synchronous tracking of owner.
1651 setCurrentOwner(componentDebugInfo);
1652 try {
1653 if (debugTask) {
1654 return debugTask.run(callback.bind(null, arg));
1655 }
1656 return callback(arg);
1657 } finally {
1658 setCurrentOwner(null);
1659 }
1660 }
1661
1662 const voidHandler = () => {};
1663
1664 function processServerComponentReturnValue(
1665 request: Request,
1666 task: Task,
1667 Component: any,
1668 result: any,
1669 ): any {
1670 // A Server Component's return value has a few special properties due to being
1671 // in the return position of a Component. We convert them here.
1672 if (
1673 typeof result !== 'object' ||
1674 result === null ||
1675 isClientReference(result)
1676 ) {
1677 return result;
1678 }
1679
1680 if (typeof result.then === 'function') {
1681 // When the return value is in children position we can resolve it immediately,
1682 // to its value without a wrapper if it's synchronously available.
1683 const thenable: Thenable<any> = result;
1684 if (__DEV__) {
1685 // If the thenable resolves to an element, then it was in a static position,
1686 // the return value of a Server Component. That doesn't need further validation
1687 // of keys. The Server Component itself would have had a key.
1688 thenable.then(resolvedValue => {
1689 if (
1690 typeof resolvedValue === 'object' &&
1691 resolvedValue !== null &&
1692 resolvedValue.$$typeof === REACT_ELEMENT_TYPE
1693 ) {
1694 resolvedValue._store.validated = 1;
1695 }
1696 }, voidHandler);
1697 }
1698 // TODO: Once we accept Promises as children on the client, we can just return
1699 // the thenable here.
1700 return createLazyWrapperAroundWakeable(request, task, result);
1701 }
1702
1703 if (__DEV__) {
1704 if ((result as any).$$typeof === REACT_ELEMENT_TYPE) {
1705 // If the server component renders to an element, then it was in a static position.
1706 // That doesn't need further validation of keys. The Server Component itself would
1707 // have had a key.
1708 (result as any)._store.validated = 1;
1709 }
1710 }
1711
1712 // Normally we'd serialize an Iterator/AsyncIterator as a single-shot which is not compatible
1713 // to be rendered as a React Child. However, because we have the function to recreate
1714 // an iterable from rendering the element again, we can effectively treat it as multi-
1715 // shot. Therefore we treat this as an Iterable/AsyncIterable, whether it was one or not, by
1716 // adding a wrapper so that this component effectively renders down to an AsyncIterable.
1717 const iteratorFn = getIteratorFn(result);
1718 if (iteratorFn) {
1719 const iterableChild = result;
1720 const multiShot = {
1721 [Symbol.iterator]: function () {
1722 const iterator = iteratorFn.call(iterableChild);
1723 if (__DEV__) {
1724 // If this was an Iterator but not a GeneratorFunction we warn because
1725 // it might have been a mistake. Technically you can make this mistake with
1726 // GeneratorFunctions and even single-shot Iterables too but it's extra
1727 // tempting to try to return the value from a generator.
1728 if (iterator === iterableChild) {
1729 const isGeneratorComponent =
1730 // $FlowFixMe[method-unbinding]
1731 Object.prototype.toString.call(Component) ===
1732 '[object GeneratorFunction]' &&
1733 // $FlowFixMe[method-unbinding]
1734 Object.prototype.toString.call(iterableChild) ===
1735 '[object Generator]';
1736 if (!isGeneratorComponent) {
1737 callWithDebugContextInDEV(request, task, () => {
1738 console.error(
1739 'Returning an Iterator from a Server Component is not supported ' +
1740 'since it cannot be looped over more than once. ',
1741 );
1742 });
1743 }
1744 }
1745 }
1746 return iterator as any;
1747 },
1748 };
1749 if (__DEV__) {
1750 (multiShot as any)._debugInfo = iterableChild._debugInfo;
1751 }
1752 return multiShot;
1753 }
1754 if (
1755 typeof (result as any)[ASYNC_ITERATOR] === 'function' &&
1756 (typeof ReadableStream !== 'function' ||
1757 !(result instanceof ReadableStream))
1758 ) {
1759 const iterableChild = result;
1760 const multishot = {
1761 [ASYNC_ITERATOR]: function () {
1762 const iterator = (iterableChild as any)[ASYNC_ITERATOR]();
1763 if (__DEV__) {
1764 // If this was an AsyncIterator but not an AsyncGeneratorFunction we warn because
1765 // it might have been a mistake. Technically you can make this mistake with
1766 // AsyncGeneratorFunctions and even single-shot AsyncIterables too but it's extra
1767 // tempting to try to return the value from a generator.
1768 if (iterator === iterableChild) {
1769 const isGeneratorComponent =
1770 // $FlowFixMe[method-unbinding]
1771 Object.prototype.toString.call(Component) ===
1772 '[object AsyncGeneratorFunction]' &&
1773 // $FlowFixMe[method-unbinding]
1774 Object.prototype.toString.call(iterableChild) ===
1775 '[object AsyncGenerator]';
1776 if (!isGeneratorComponent) {
1777 callWithDebugContextInDEV(request, task, () => {
1778 console.error(
1779 'Returning an AsyncIterator from a Server Component is not supported ' +
1780 'since it cannot be looped over more than once. ',
1781 );
1782 });
1783 }
1784 }
1785 }
1786 return iterator;
1787 },
1788 };
1789 if (__DEV__) {
1790 (multishot as any)._debugInfo = iterableChild._debugInfo;
1791 }
1792 return multishot;
1793 }
1794 return result;
1795 }
1796
1797 function renderFunctionComponent<Props>(
1798 request: Request,
1799 task: Task,
1800 key: ReactKey,
1801 Component: (p: Props, arg: void) => any,
1802 props: Props,
1803 validated: number, // DEV-only
1804 ): ReactJSONValue {
1805 // Reset the task's thenable state before continuing, so that if a later
1806 // component suspends we can reuse the same task object. If the same
1807 // component suspends again, the thenable state will be restored.
1808 const prevThenableState = task.thenableState;
1809 task.thenableState = null;
1810
1811 let result;
1812
1813 let componentDebugInfo: ReactComponentInfo;
1814 if (__DEV__) {
1815 if (!canEmitDebugInfo) {
1816 // We don't have a chunk to assign debug info. We need to outline this
1817 // component to assign it an ID.
1818 return outlineTask(request, task);
1819 } else if (prevThenableState !== null) {
1820 // This is a replay and we've already emitted the debug info of this component
1821 // in the first pass. We skip emitting a duplicate line.
1822 // As a hack we stashed the previous component debug info on this object in DEV.
1823 componentDebugInfo = (prevThenableState as any)._componentDebugInfo;
1824 } else {
1825 // This is a new component in the same task so we can emit more debug info.
1826 const componentDebugID = task.id;
1827 const componentName =
1828 (Component as any).displayName || Component.name || '';
1829 const componentEnv = (0, request.environmentName)();
1830 request.pendingChunks++;
1831 componentDebugInfo = {
1832 name: componentName,
1833 env: componentEnv,
1834 key: key,
1835 owner: task.debugOwner,
1836 } as ReactComponentInfo;
1837 // $FlowFixMe[cannot-write]
1838 componentDebugInfo.stack =
1839 task.debugStack === null
1840 ? null
1841 : filterStackTrace(request, parseStackTrace(task.debugStack, 1));
1842 // $FlowFixMe[cannot-write]
1843 componentDebugInfo.props = props;
1844 // $FlowFixMe[cannot-write]
1845 componentDebugInfo.debugStack = task.debugStack;
1846 // $FlowFixMe[cannot-write]
1847 componentDebugInfo.debugTask = task.debugTask;
1848
1849 // We outline this model eagerly so that we can refer to by reference as an owner.
1850 // If we had a smarter way to dedupe we might not have to do this if there ends up
1851 // being no references to this as an owner.
1852
1853 outlineComponentInfo(request, componentDebugInfo);
1854
1855 // Track when we started rendering this component.
1856 if (
1857 enableProfilerTimer &&
1858 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
1859 ) {
1860 advanceTaskTime(request, task, performance.now());
1861 }
1862
1863 emitDebugChunk(request, componentDebugID, componentDebugInfo);
1864
1865 // We've emitted the latest environment for this task so we track that.
1866 task.environmentName = componentEnv;
1867
1868 if (validated === 2) {
1869 warnForMissingKey(request, key, componentDebugInfo, task.debugTask);
1870 }
1871 }
1872 prepareToUseHooksForComponent(prevThenableState, componentDebugInfo);
1873 // $FlowFixMe[constant-condition]
1874 if (supportsComponentStorage) {
1875 // Run the component in an Async Context that tracks the current owner.
1876 if (task.debugTask) {
1877 result = task.debugTask.run(
1878 // $FlowFixMe[method-unbinding]
1879 componentStorage.run.bind(
1880 componentStorage,
1881 componentDebugInfo,
1882 callComponentInDEV,
1883 Component,
1884 props,
1885 componentDebugInfo,
1886 ),
1887 );
1888 } else {
1889 result = componentStorage.run(
1890 componentDebugInfo,
1891 callComponentInDEV,
1892 Component,
1893 props,
1894 componentDebugInfo,
1895 );
1896 }
1897 } else {
1898 if (task.debugTask) {
1899 result = task.debugTask.run(
1900 callComponentInDEV.bind(null, Component, props, componentDebugInfo),
1901 );
1902 } else {
1903 result = callComponentInDEV(Component, props, componentDebugInfo);
1904 }
1905 }
1906 } else {
1907 componentDebugInfo = null as any;
1908 prepareToUseHooksForComponent(prevThenableState, null);
1909 // The secondArg is always undefined in Server Components since refs error early.
1910 const secondArg = undefined;
1911 result = Component(props, secondArg);
1912 }
1913
1914 if (request.status === ABORTING) {
1915 if (
1916 typeof result === 'object' &&
1917 // $FlowFixMe[invalid-compare]
1918 result !== null &&
1919 typeof result.then === 'function' &&
1920 !isClientReference(result)
1921 ) {
1922 result.then(voidHandler, voidHandler);
1923 }
1924 // If we aborted during rendering we should interrupt the render but
1925 // we don't need to provide an error because the renderer will encode
1926 // the abort error as the reason.
1927 // eslint-disable-next-line no-throw-literal
1928 throw null;
1929 }
1930
1931 if (__DEV__ || (enableProfilerTimer && enableAsyncDebugInfo)) {
1932 // Forward any debug information for any Promises that we use():ed during the render.
1933 // We do this at the end so that we don't keep doing this for each retry.
1934 const trackedThenables = getTrackedThenablesAfterRendering();
1935 if (trackedThenables !== null) {
1936 const stacks: Array<Error> =
1937 __DEV__ && enableAsyncDebugInfo
1938 ? (trackedThenables as any)._stacks ||
1939 ((trackedThenables as any)._stacks = [])
1940 : (null as any);
1941 for (let i = 0; i < trackedThenables.length; i++) {
1942 const stack = __DEV__ && enableAsyncDebugInfo ? stacks[i] : null;
1943 forwardDebugInfoFromThenable(
1944 request,
1945 task,
1946 trackedThenables[i],
1947 __DEV__ ? componentDebugInfo : null,
1948 stack,
1949 );
1950 }
1951 }
1952 }
1953
1954 // Apply special cases.
1955 result = processServerComponentReturnValue(request, task, Component, result);
1956
1957 if (__DEV__) {
1958 // From this point on, the parent is the component we just rendered until we
1959 // hit another JSX element.
1960 task.debugOwner = componentDebugInfo;
1961 // Unfortunately, we don't have a stack frame for this position. Conceptually
1962 // it would be the location of the `return` inside component that just rendered.
1963 task.debugStack = null;
1964 task.debugTask = null;
1965 }
1966
1967 // Track this element's key on the Server Component on the keyPath context..
1968 const prevKeyPath = task.keyPath;
1969 const prevImplicitSlot = task.implicitSlot;
1970 if (key !== null) {
1971 // Append the key to the path. Technically a null key should really add the child
1972 // index. We don't do that to hold the payload small and implementation simple.
1973 if (key === REACT_OPTIMISTIC_KEY || prevKeyPath === REACT_OPTIMISTIC_KEY) {
1974 // The optimistic key is viral. It turns the whole key into optimistic if any part is.
1975 task.keyPath = REACT_OPTIMISTIC_KEY;
1976 } else {
1977 task.keyPath = prevKeyPath === null ? key : prevKeyPath + ',' + key;
1978 }
1979 } else if (prevKeyPath === null) {
1980 // This sequence of Server Components has no keys. This means that it was rendered
1981 // in a slot that needs to assign an implicit key. Even if children below have
1982 // explicit keys, they should not be used for the outer most key since it might
1983 // collide with other slots in that set.
1984 task.implicitSlot = true;
1985 }
1986 const json = renderModelDestructive(request, task, emptyRoot, '', result);
1987 task.keyPath = prevKeyPath;
1988 task.implicitSlot = prevImplicitSlot;
1989 return json;
1990 }
1991
1992 function warnForMissingKey(
1993 request: Request,
1994 key: ReactKey,
1995 componentDebugInfo: ReactComponentInfo,
1996 debugTask: null | ConsoleTask,
1997 ): void {
1998 if (__DEV__) {
1999 let didWarnForKey = request.didWarnForKey;
2000 if (didWarnForKey == null) {
2001 didWarnForKey = request.didWarnForKey = new WeakSet();
2002 }
2003 const parentOwner = componentDebugInfo.owner;
2004 if (parentOwner != null) {
2005 if (didWarnForKey.has(parentOwner)) {
2006 // We already warned for other children in this parent.
2007 return;
2008 }
2009 didWarnForKey.add(parentOwner);
2010 }
2011
2012 // Call with the server component as the currently rendering component
2013 // for context.
2014 const logKeyError = () => {
2015 console.error(
2016 'Each child in a list should have a unique "key" prop.' +
2017 '%s%s See https://react.dev/link/warning-keys for more information.',
2018 '',
2019 '',
2020 );
2021 };
2022
2023 // $FlowFixMe[constant-condition]
2024 if (supportsComponentStorage) {
2025 // Run the component in an Async Context that tracks the current owner.
2026 if (debugTask) {
2027 debugTask.run(
2028 // $FlowFixMe[method-unbinding]
2029 componentStorage.run.bind(
2030 componentStorage,
2031 componentDebugInfo,
2032 callComponentInDEV,
2033 logKeyError,
2034 null,
2035 componentDebugInfo,
2036 ),
2037 );
2038 } else {
2039 componentStorage.run(
2040 componentDebugInfo,
2041 callComponentInDEV,
2042 logKeyError,
2043 null,
2044 componentDebugInfo,
2045 );
2046 }
2047 } else {
2048 if (debugTask) {
2049 debugTask.run(
2050 callComponentInDEV.bind(null, logKeyError, null, componentDebugInfo),
2051 );
2052 } else {
2053 callComponentInDEV(logKeyError, null, componentDebugInfo);
2054 }
2055 }
2056 }
2057 }
2058
2059 function renderFragment(
2060 request: Request,
2061 task: Task,
2062 children: $ReadOnlyArray<ReactClientValue>,
2063 ): ReactJSONValue {
2064 if (__DEV__) {
2065 for (let i = 0; i < children.length; i++) {
2066 const child = children[i];
2067 if (
2068 child !== null &&
2069 typeof child === 'object' &&
2070 child.$$typeof === REACT_ELEMENT_TYPE
2071 ) {
2072 const element: ReactElement = child as any;
2073 if (element.key === null && !element._store.validated) {
2074 element._store.validated = 2;
2075 }
2076 }
2077 }
2078 }
2079
2080 if (task.keyPath !== null) {
2081 // We have a Server Component that specifies a key but we're now splitting
2082 // the tree using a fragment.
2083 const fragment = __DEV__
2084 ? [
2085 REACT_ELEMENT_TYPE,
2086 REACT_FRAGMENT_TYPE,
2087 task.keyPath,
2088 {children},
2089 null,
2090 null,
2091 0,
2092 ]
2093 : [REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE, task.keyPath, {children}];
2094 if (!task.implicitSlot) {
2095 // If this was keyed inside a set. I.e. the outer Server Component was keyed
2096 // then we need to handle reorders of the whole set. To do this we need to wrap
2097 // this array in a keyed Fragment.
2098 return fragment;
2099 }
2100 // If the outer Server Component was implicit but then an inner one had a key
2101 // we don't actually need to be able to move the whole set around. It'll always be
2102 // in an implicit slot. The key only exists to be able to reset the state of the
2103 // children. We could achieve the same effect by passing on the keyPath to the next
2104 // set of components inside the fragment. This would also allow a keyless fragment
2105 // reconcile against a single child.
2106 // Unfortunately because of JSON.stringify, we can't call the recursive loop for
2107 // each child within this context because we can't return a set with already resolved
2108 // values. E.g. a string would get double encoded. Returning would pop the context.
2109 // So instead, we wrap it with an unkeyed fragment and inner keyed fragment.
2110 return [fragment];
2111 }
2112 // Since we're yielding here, that implicitly resets the keyPath context on the
2113 // way up. Which is what we want since we've consumed it. If this changes to
2114 // be recursive serialization, we need to reset the keyPath and implicitSlot,
2115 // before recursing here.
2116 if (__DEV__) {
2117 const debugInfo: ?ReactDebugInfo = (children as any)._debugInfo;
2118 if (debugInfo) {
2119 // If this came from Flight, forward any debug info into this new row.
2120 if (!canEmitDebugInfo) {
2121 // We don't have a chunk to assign debug info. We need to outline this
2122 // component to assign it an ID.
2123 return outlineTask(request, task);
2124 } else {
2125 // Forward any debug info we have the first time we see it.
2126 // We do this after init so that we have received all the debug info
2127 // from the server by the time we emit it.
2128 forwardDebugInfo(request, task, debugInfo);
2129 }
2130 // Since we're rendering this array again, create a copy that doesn't
2131 // have the debug info so we avoid outlining or emitting debug info again.
2132 children = Array.from(children);
2133 }
2134 }
2135 return children;
2136 }
2137
2138 function renderAsyncFragment(
2139 request: Request,
2140 task: Task,
2141 children: $AsyncIterable<ReactClientValue, ReactClientValue, void>,
2142 getAsyncIterator: () => $AsyncIterator<any, any, any>,
2143 ): ReactJSONValue {
2144 if (task.keyPath !== null) {
2145 // We have a Server Component that specifies a key but we're now splitting
2146 // the tree using a fragment.
2147 const fragment = __DEV__
2148 ? [
2149 REACT_ELEMENT_TYPE,
2150 REACT_FRAGMENT_TYPE,
2151 task.keyPath,
2152 {children},
2153 null,
2154 null,
2155 0,
2156 ]
2157 : [REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE, task.keyPath, {children}];
2158 if (!task.implicitSlot) {
2159 // If this was keyed inside a set. I.e. the outer Server Component was keyed
2160 // then we need to handle reorders of the whole set. To do this we need to wrap
2161 // this array in a keyed Fragment.
2162 return fragment;
2163 }
2164 // If the outer Server Component was implicit but then an inner one had a key
2165 // we don't actually need to be able to move the whole set around. It'll always be
2166 // in an implicit slot. The key only exists to be able to reset the state of the
2167 // children. We could achieve the same effect by passing on the keyPath to the next
2168 // set of components inside the fragment. This would also allow a keyless fragment
2169 // reconcile against a single child.
2170 // Unfortunately because of JSON.stringify, we can't call the recursive loop for
2171 // each child within this context because we can't return a set with already resolved
2172 // values. E.g. a string would get double encoded. Returning would pop the context.
2173 // So instead, we wrap it with an unkeyed fragment and inner keyed fragment.
2174 return [fragment];
2175 }
2176
2177 // Since we're yielding here, that implicitly resets the keyPath context on the
2178 // way up. Which is what we want since we've consumed it. If this changes to
2179 // be recursive serialization, we need to reset the keyPath and implicitSlot,
2180 // before recursing here.
2181 const asyncIterator = getAsyncIterator.call(children);
2182 return serializeAsyncIterable(request, task, children, asyncIterator);
2183 }
2184
2185 function renderClientElement(
2186 request: Request,
2187 task: Task,
2188 type: any,
2189 key: ReactKey,
2190 props: any,
2191 validated: number, // DEV-only
2192 ): ReactJSONValue {
2193 // We prepend the terminal client element that actually gets serialized with
2194 // the keys of any Server Components which are not serialized.
2195 const keyPath = task.keyPath;
2196 if (key === null) {
2197 key = keyPath;
2198 } else if (keyPath !== null) {
2199 if (keyPath === REACT_OPTIMISTIC_KEY || key === REACT_OPTIMISTIC_KEY) {
2200 // Optimistic key is viral and turns the whole key optimistic.
2201 key = REACT_OPTIMISTIC_KEY;
2202 } else {
2203 key = keyPath + ',' + key;
2204 }
2205 }
2206 let debugOwner = null;
2207 let debugStack = null;
2208 if (__DEV__) {
2209 debugOwner = task.debugOwner;
2210 if (debugOwner !== null) {
2211 // Ensure we outline this owner if it is the first time we see it.
2212 // So that we can refer to it directly.
2213 outlineComponentInfo(request, debugOwner);
2214 }
2215 if (task.debugStack !== null) {
2216 // Outline the debug stack so that we write to the completedDebugChunks instead.
2217 debugStack = filterStackTrace(
2218 request,
2219 parseStackTrace(task.debugStack, 1),
2220 );
2221 const id = outlineDebugModel(
2222 request,
2223 {objectLimit: debugStack.length * 2 + 1},
2224 debugStack,
2225 );
2226 // We also store this in the main dedupe set so that it can be referenced by inline React Elements.
2227 request.writtenObjects.set(debugStack, serializeByValueID(id));
2228 }
2229 }
2230 const element = __DEV__
2231 ? [REACT_ELEMENT_TYPE, type, key, props, debugOwner, debugStack, validated]
2232 : [REACT_ELEMENT_TYPE, type, key, props];
2233 if (task.implicitSlot && key !== null) {
2234 // The root Server Component had no key so it was in an implicit slot.
2235 // If we had a key lower, it would end up in that slot with an explicit key.
2236 // We wrap the element in a fragment to give it an implicit key slot with
2237 // an inner explicit key.
2238 return [element];
2239 }
2240 // Since we're yielding here, that implicitly resets the keyPath context on the
2241 // way up. Which is what we want since we've consumed it. If this changes to
2242 // be recursive serialization, we need to reset the keyPath and implicitSlot,
2243 // before recursing here. We also need to reset it once we render into an array
2244 // or anything else too which we also get implicitly.
2245 return element;
2246 }
2247
2248 // Determines if we're currently rendering at the top level of a task and therefore
2249 // is safe to emit debug info associated with that task. Otherwise, if we're in
2250 // a nested context, we need to first outline.
2251 let canEmitDebugInfo: boolean = false;
2252
2253 // Approximate string length of the currently serializing row.
2254 // Used to power outlining heuristics.
2255 let serializedSize = 0;
2256 const MAX_ROW_SIZE = 3200;
2257
2258 // Bundler metadata repeats the same chunk URLs across every client reference of
2259 // a route, so strings in it at least this long get outlined and deduplicated
2260 // when they repeat. The threshold is bounded away from zero because outlining
2261 // something as short as an export name costs more than copying it.
2262 const MIN_DEDUPLICATED_IMPORT_STRING_LENGTH = 16;
2263
2264 // Tracked strings are retained for the rest of the request, so their combined
2265 // length is capped.
2266 const MAX_DEDUPLICATED_IMPORT_STRINGS_SIZE = 32768;
2267
2268 function deferTask(request: Request, task: Task): ReactJSONValue {
2269 // Like outlineTask but instead the item is scheduled to be serialized
2270 // after its parent in the stream.
2271 const newTask = createTask(
2272 request,
2273 task.model, // the currently rendering element
2274 task.keyPath, // unlike outlineModel this one carries along context
2275 task.implicitSlot,
2276 task.formatContext,
2277 request.abortableTasks,
2278 enableProfilerTimer &&
2279 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
2280 ? task.time
2281 : 0,
2282 __DEV__ ? task.debugOwner : null,
2283 __DEV__ ? task.debugStack : null,
2284 __DEV__ ? task.debugTask : null,
2285 );
2286
2287 pingTask(request, newTask);
2288 return serializeLazyID(newTask.id);
2289 }
2290
2291 function outlineTask(request: Request, task: Task): ReactJSONValue {
2292 const newTask = createTask(
2293 request,
2294 task.model, // the currently rendering element
2295 task.keyPath, // unlike outlineModel this one carries along context
2296 task.implicitSlot,
2297 task.formatContext,
2298 request.abortableTasks,
2299 enableProfilerTimer &&
2300 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
2301 ? task.time
2302 : 0,
2303 __DEV__ ? task.debugOwner : null,
2304 __DEV__ ? task.debugStack : null,
2305 __DEV__ ? task.debugTask : null,
2306 );
2307
2308 retryTask(request, newTask);
2309 if (newTask.status === COMPLETED) {
2310 // We completed synchronously so we can refer to this by reference. This
2311 // makes it behaves the same as prod during deserialization.
2312 return serializeByValueID(newTask.id);
2313 }
2314 // This didn't complete synchronously so it wouldn't have even if we didn't
2315 // outline it, so this would reduce to a lazy reference even in prod.
2316 return serializeLazyID(newTask.id);
2317 }
2318
2319 function outlineHaltedTask(
2320 request: Request,
2321 task: Task,
2322 allowLazy: boolean,
2323 ): ReactJSONValue {
2324 // In the future if we track task state for resuming we'll maybe need to
2325 // construnct an actual task here but since we're never going to retry it
2326 // we just claim the id and serialize it according to the proper convention
2327 const taskId = request.nextChunkId++;
2328 if (allowLazy) {
2329 // We're halting in a position that can handle a lazy reference
2330 return serializeLazyID(taskId);
2331 } else {
2332 // We're halting in a position that needs a value reference
2333 return serializeByValueID(taskId);
2334 }
2335 }
2336
2337 function renderElement(
2338 request: Request,
2339 task: Task,
2340 type: any,
2341 key: ReactKey,
2342 ref: mixed,
2343 props: any,
2344 validated: number, // DEV only
2345 ): ReactJSONValue {
2346 if (ref !== null && ref !== undefined) {
2347 // When the ref moves to the regular props object this will implicitly
2348 // throw for functions. We could probably relax it to a DEV warning for other
2349 // cases.
2350 // TODO: `ref` is now just a prop when `enableRefAsProp` is on. Should we
2351 // do what the above comment says?
2352 throw new Error(
2353 'Refs cannot be used in Server Components, nor passed to Client Components.',
2354 );
2355 }
2356 if (__DEV__) {
2357 jsxPropsParents.set(props, type);
2358 if (typeof props.children === 'object' && props.children !== null) {
2359 jsxChildrenParents.set(props.children, type);
2360 }
2361 }
2362 if (
2363 typeof type === 'function' &&
2364 !isClientReference(type) &&
2365 !isOpaqueTemporaryReference(type)
2366 ) {
2367 // This is a Server Component.
2368 return renderFunctionComponent(request, task, key, type, props, validated);
2369 } else if (type === REACT_FRAGMENT_TYPE && key === null) {
2370 // For key-less fragments, we add a small optimization to avoid serializing
2371 // it as a wrapper.
2372 if (__DEV__ && validated === 2) {
2373 // Create a fake owner node for the error stack.
2374 const componentDebugInfo: ReactComponentInfo = {
2375 name: 'Fragment',
2376 env: (0, request.environmentName)(),
2377 key: key,
2378 owner: task.debugOwner,
2379 stack:
2380 task.debugStack === null
2381 ? null
2382 : filterStackTrace(request, parseStackTrace(task.debugStack, 1)),
2383 props: props,
2384 debugStack: task.debugStack,
2385 debugTask: task.debugTask,
2386 };
2387 warnForMissingKey(request, key, componentDebugInfo, task.debugTask);
2388 }
2389 const prevImplicitSlot = task.implicitSlot;
2390 if (task.keyPath === null) {
2391 task.implicitSlot = true;
2392 }
2393 const json = renderModelDestructive(
2394 request,
2395 task,
2396 emptyRoot,
2397 '',
2398 props.children,
2399 );
2400 task.implicitSlot = prevImplicitSlot;
2401 return json;
2402 } else if (
2403 type != null &&
2404 typeof type === 'object' &&
2405 !isClientReference(type)
2406 ) {
2407 switch (type.$$typeof) {
2408 case REACT_LAZY_TYPE: {
2409 let wrappedType;
2410 if (__DEV__) {
2411 wrappedType = callLazyInitInDEV(type);
2412 } else {
2413 const payload = type._payload;
2414 const init = type._init;
2415 wrappedType = init(payload);
2416 }
2417 if (request.status === ABORTING) {
2418 // lazy initializers are user code and could abort during render
2419 // we don't wan to return any value resolved from the lazy initializer
2420 // if it aborts so we interrupt rendering here
2421 // eslint-disable-next-line no-throw-literal
2422 throw null;
2423 }
2424 return renderElement(
2425 request,
2426 task,
2427 wrappedType,
2428 key,
2429 ref,
2430 props,
2431 validated,
2432 );
2433 }
2434 case REACT_FORWARD_REF_TYPE: {
2435 return renderFunctionComponent(
2436 request,
2437 task,
2438 key,
2439 type.render,
2440 props,
2441 validated,
2442 );
2443 }
2444 case REACT_MEMO_TYPE: {
2445 return renderElement(
2446 request,
2447 task,
2448 type.type,
2449 key,
2450 ref,
2451 props,
2452 validated,
2453 );
2454 }
2455 case REACT_ELEMENT_TYPE: {
2456 // This is invalid but we'll let the client determine that it is.
2457 if (__DEV__) {
2458 // Disable the key warning that would happen otherwise because this
2459 // element gets serialized inside an array. We'll error later anyway.
2460 type._store.validated = 1;
2461 }
2462 }
2463 }
2464 } else if (typeof type === 'string') {
2465 const parentFormatContext = task.formatContext;
2466 const newFormatContext = getChildFormatContext(
2467 parentFormatContext,
2468 type,
2469 props,
2470 );
2471 if (parentFormatContext !== newFormatContext && props.children != null) {
2472 // We've entered a new context. We need to create another Task which has
2473 // the new context set up since it's not safe to push/pop in the middle of
2474 // a tree. Additionally this means that any deduping within this tree now
2475 // assumes the new context even if it's reused outside in a different context.
2476 // We'll rely on this to dedupe the value later as we discover it again
2477 // inside the returned element's tree.
2478 outlineModelWithFormatContext(request, props.children, newFormatContext);
2479 }
2480 }
2481 // For anything else, try it on the client instead.
2482 // We don't know if the client will support it or not. This might error on the
2483 // client or error during serialization but the stack will point back to the
2484 // server.
2485 return renderClientElement(request, task, type, key, props, validated);
2486 }
2487
2488 function visitAsyncNode(
2489 request: Request,
2490 task: Task,
2491 node: AsyncSequence,
2492 visited: Map<
2493 AsyncSequence | ReactDebugInfo,
2494 void | null | PromiseNode | IONode,
2495 >,
2496 cutOff: number,
2497 ): void | null | PromiseNode | IONode {
2498 // Collect the previous chain iteratively instead of recursively to avoid
2499 // stack overflow on deep chains. We process from deepest to shallowest so
2500 // each node has its previousIONode available.
2501 const chain: Array<AsyncSequence> = [];
2502 let current: AsyncSequence | null = node;
2503
2504 while (current !== null) {
2505 if (visited.has(current)) {
2506 break;
2507 }
2508 chain.push(current);
2509 current = current.previous;
2510 }
2511
2512 let previousIONode: void | null | PromiseNode | IONode =
2513 current !== null ? visited.get(current) : null;
2514
2515 // Process from deepest to shallowest (reverse order).
2516 for (let i = chain.length - 1; i >= 0; i--) {
2517 const n = chain[i];
2518 // Set it as visited early in case we see the node again before returning.
2519 visited.set(n, null);
2520
2521 const result = visitAsyncNodeImpl(
2522 request,
2523 task,
2524 n,
2525 visited,
2526 cutOff,
2527 previousIONode,
2528 );
2529
2530 if (result !== null) {
2531 // If we ended up with a value, let's use that value for future visits.
2532 visited.set(n, result);
2533 }
2534
2535 if (result === undefined) {
2536 // Undefined is used as a signal that we found a suitable aborted node
2537 // and we don't have to find further aborted nodes.
2538 return undefined;
2539 }
2540
2541 previousIONode = result;
2542 }
2543
2544 return previousIONode;
2545 }
2546
2547 function visitAsyncNodeImpl(
2548 request: Request,
2549 task: Task,
2550 node: AsyncSequence,
2551 visited: Map<
2552 AsyncSequence | ReactDebugInfo,
2553 void | null | PromiseNode | IONode,
2554 >,
2555 cutOff: number,
2556 previousIONode: void | null | PromiseNode | IONode,
2557 ): void | null | PromiseNode | IONode {
2558 if (node.end >= 0 && node.end <= request.timeOrigin) {
2559 // This was already resolved when we started this render. It must have been either something
2560 // that's part of a start up sequence or externally cached data. We exclude that information.
2561 // The technique for debugging the effects of uncached data on the render is to simply uncache it.
2562 return null;
2563 }
2564
2565 // `found` represents the return value of the following switch statement.
2566 // We can't use multiple `return` statements in the switch statement
2567 // since that prevents Closure compiler from inlining `visitAsyncImpl`
2568 // thus doubling the call stack size.
2569 let found: void | null | PromiseNode | IONode;
2570 switch (node.tag) {
2571 case IO_NODE: {
2572 found = node;
2573 break;
2574 }
2575 case UNRESOLVED_PROMISE_NODE: {
2576 found = previousIONode;
2577 break;
2578 }
2579 case PROMISE_NODE: {
2580 const awaited = node.awaited;
2581 let match: void | null | PromiseNode | IONode = previousIONode;
2582 const promise = node.promise.deref();
2583 if (awaited !== null) {
2584 const ioNode = visitAsyncNode(request, task, awaited, visited, cutOff);
2585 if (ioNode === undefined) {
2586 // Undefined is used as a signal that we found a suitable aborted node and we don't have to find
2587 // further aborted nodes.
2588 found = undefined;
2589 break;
2590 } else if (ioNode !== null) {
2591 // This Promise was blocked on I/O. That's a signal that this Promise is interesting to log.
2592 // We don't log it yet though. We return it to be logged by the point where it's awaited.
2593 // The ioNode might be another PromiseNode in the case where none of the AwaitNode had
2594 // unfiltered stacks.
2595 if (ioNode.tag === PROMISE_NODE) {
2596 // If the ioNode was a Promise, then that means we found one in user space since otherwise
2597 // we would've returned an IO node. We assume this has the best stack.
2598 // Note: This might also be a Promise with a displayName but potentially a worse stack.
2599 // We could potentially favor the outer Promise if it has a stack but not the inner.
2600 match = ioNode;
2601 } else if (
2602 (node.stack !== null && hasUnfilteredFrame(request, node.stack)) ||
2603 (promise !== undefined &&
2604 // $FlowFixMe[prop-missing]
2605 typeof promise.displayName === 'string' &&
2606 (ioNode.stack === null ||
2607 !hasUnfilteredFrame(request, ioNode.stack)))
2608 ) {
2609 // If this Promise has a stack trace then we favor that over the I/O node since we're
2610 // mainly dealing with Promises as the abstraction.
2611 // If it has no stack but at least has a displayName and the io doesn't have a better
2612 // stack anyway, then also use this Promise instead since at least it has a name.
2613 match = node;
2614 } else {
2615 // If this Promise was created inside only third party code, then try to use
2616 // the inner I/O node instead. This could happen if third party calls into first
2617 // party to perform some I/O.
2618 match = ioNode;
2619 }
2620 } else if (request.status === ABORTING) {
2621 if (node.start < request.abortTime && node.end > request.abortTime) {
2622 // We aborted this render. If this Promise spanned the abort time it was probably the
2623 // Promise that was aborted. This won't necessarily have I/O associated with it but
2624 // it's a point of interest.
2625 if (
2626 (node.stack !== null &&
2627 hasUnfilteredFrame(request, node.stack)) ||
2628 (promise !== undefined &&
2629 // $FlowFixMe[prop-missing]
2630 typeof promise.displayName === 'string')
2631 ) {
2632 match = node;
2633 }
2634 }
2635 }
2636 }
2637 // We need to forward after we visit awaited nodes because what ever I/O we requested that's
2638 // the thing that generated this node and its virtual children.
2639 if (promise !== undefined) {
2640 const debugInfo = promise._debugInfo;
2641 if (debugInfo != null && !visited.has(debugInfo)) {
2642 visited.set(debugInfo, null);
2643 forwardDebugInfo(request, task, debugInfo);
2644 }
2645 }
2646 found = match;
2647 break;
2648 }
2649 case UNRESOLVED_AWAIT_NODE: {
2650 found = previousIONode;
2651 break;
2652 }
2653 case AWAIT_NODE: {
2654 const awaited = node.awaited;
2655 let match: void | null | PromiseNode | IONode = previousIONode;
2656 if (awaited !== null) {
2657 const ioNode = visitAsyncNode(request, task, awaited, visited, cutOff);
2658 if (ioNode === undefined) {
2659 // Undefined is used as a signal that we found a suitable aborted node and we don't have to find
2660 // further aborted nodes.
2661 found = undefined;
2662 break;
2663 } else if (ioNode !== null) {
2664 const startTime: number = node.start;
2665 const endTime: number = node.end;
2666 if (startTime < cutOff) {
2667 // We started awaiting this node before we started rendering this sequence.
2668 // This means that this particular await was never part of the current sequence.
2669 // If we have another await higher up in the chain it might have a more actionable stack
2670 // from the perspective of this component. If we end up here from the "previous" path,
2671 // then this gets I/O ignored, which is what we want because it means it was likely
2672 // just part of a previous component's rendering.
2673 match = ioNode;
2674 if (
2675 node.stack !== null &&
2676 isAwaitInUserspace(request, node.stack)
2677 ) {
2678 // This await happened earlier but it was done in user space. This is the first time
2679 // that user space saw the value of the I/O. We know we'll emit the I/O eventually
2680 // but if we do it now we can override the promise value of the I/O entry to the
2681 // one observed by this await which will be a better value than the internals of
2682 // the I/O entry. If it's still alive that is.
2683 const promise =
2684 awaited.promise === null ? undefined : awaited.promise.deref();
2685 if (promise !== undefined) {
2686 serializeIONode(request, ioNode, awaited.promise);
2687 }
2688 }
2689 } else {
2690 if (
2691 node.stack === null ||
2692 !isAwaitInUserspace(request, node.stack)
2693 ) {
2694 // If this await was fully filtered out, then it was inside third party code
2695 // such as in an external library. We return the I/O node and try another await.
2696 match = ioNode;
2697 } else if (
2698 request.status === ABORTING &&
2699 startTime > request.abortTime
2700 ) {
2701 // This was awaited after aborting so we skip it.
2702 } else {
2703 // We found a user space await.
2704
2705 // Outline the IO node.
2706 // The ioNode is where the I/O was initiated, but after that it could have been
2707 // processed through various awaits in the internals of the third party code.
2708 // Therefore we don't use the inner most Promise as the conceptual value but the
2709 // Promise that was ultimately awaited by the user space await.
2710 serializeIONode(request, ioNode, awaited.promise);
2711
2712 // If we ever visit this I/O node again, skip it because we already emitted this
2713 // exact entry and we don't need two awaits on the same thing.
2714 visited.set(ioNode, null);
2715
2716 // Ensure the owner is already outlined.
2717 if (node.owner != null) {
2718 outlineComponentInfo(request, node.owner);
2719 }
2720
2721 // We log the environment at the time when the last promise pigned ping which may
2722 // be later than what the environment was when we actually started awaiting.
2723 const env = (0, request.environmentName)();
2724 advanceTaskTime(request, task, startTime);
2725 // Then emit a reference to us awaiting it in the current task.
2726 request.pendingChunks++;
2727 emitDebugChunk(request, task.id, {
2728 awaited: ioNode as any as ReactIOInfo, // This is deduped by this reference.
2729 env: env,
2730 owner: node.owner,
2731 stack:
2732 node.stack === null
2733 ? null
2734 : filterStackTrace(request, node.stack),
2735 });
2736 // Mark the end time of the await. If we're aborting then we don't emit this
2737 // to signal that this never resolved inside this render.
2738 markOperationEndTime(request, task, endTime);
2739 if (request.status === ABORTING) {
2740 // Undefined is used as a signal that we found a suitable aborted node and we don't have to find
2741 // further aborted nodes.
2742 match = undefined;
2743 }
2744 }
2745 }
2746 }
2747 }
2748 // We need to forward after we visit awaited nodes because what ever I/O we requested that's
2749 // the thing that generated this node and its virtual children.
2750 const promise = node.promise.deref();
2751 if (promise !== undefined) {
2752 const debugInfo = promise._debugInfo;
2753 if (debugInfo != null && !visited.has(debugInfo)) {
2754 visited.set(debugInfo, null);
2755 forwardDebugInfo(request, task, debugInfo);
2756 }
2757 }
2758 found = match;
2759 break;
2760 }
2761 default: {
2762 // eslint-disable-next-line react-internal/prod-error-codes
2763 throw new Error('Unknown AsyncSequence tag. This is a bug in React.');
2764 }
2765 }
2766 return found;
2767 }
2768
2769 function emitAsyncSequence(
2770 request: Request,
2771 task: Task,
2772 node: AsyncSequence,
2773 alreadyForwardedDebugInfo: ?ReactDebugInfo,
2774 owner: null | ReactComponentInfo,
2775 stack: null | Error,
2776 ): void {
2777 const visited: Map<
2778 AsyncSequence | ReactDebugInfo,
2779 void | null | PromiseNode | IONode,
2780 > = new Map();
2781 if (__DEV__ && alreadyForwardedDebugInfo) {
2782 visited.set(alreadyForwardedDebugInfo, null);
2783 }
2784 const awaitedNode = visitAsyncNode(request, task, node, visited, task.time);
2785 if (awaitedNode === undefined) {
2786 // Undefined is used as a signal that we found an aborted await and that's good enough
2787 // anything derived from that aborted node might be irrelevant.
2788 } else if (awaitedNode !== null) {
2789 // Nothing in user space (unfiltered stack) awaited this.
2790 serializeIONode(request, awaitedNode, awaitedNode.promise);
2791 request.pendingChunks++;
2792 // We log the environment at the time when we ping which may be later than what the
2793 // environment was when we actually started awaiting.
2794 const env = (0, request.environmentName)();
2795 // If we don't have any thing awaited, the time we started awaiting was internal
2796 // when we yielded after rendering. The current task time is basically that.
2797 const debugInfo: ReactAsyncInfo = {
2798 awaited: awaitedNode as any as ReactIOInfo, // This is deduped by this reference.
2799 env: env,
2800 };
2801 if (__DEV__) {
2802 if (owner === null && stack === null) {
2803 // We have no location for the await. We can use the JSX callsite of the parent
2804 // as the await if this was just passed as a prop.
2805 if (task.debugOwner !== null) {
2806 // $FlowFixMe[cannot-write]
2807 debugInfo.owner = task.debugOwner;
2808 }
2809 if (task.debugStack !== null) {
2810 // $FlowFixMe[cannot-write]
2811 debugInfo.stack = filterStackTrace(
2812 request,
2813 parseStackTrace(task.debugStack, 1),
2814 );
2815 }
2816 } else {
2817 if (owner != null) {
2818 // $FlowFixMe[cannot-write]
2819 debugInfo.owner = owner;
2820 }
2821 if (stack != null) {
2822 // $FlowFixMe[cannot-write]
2823 debugInfo.stack = filterStackTrace(
2824 request,
2825 parseStackTrace(stack, 1),
2826 );
2827 }
2828 }
2829 }
2830 // We don't have a start time for this await but in case there was no start time emitted
2831 // we need to include something. TODO: We should maybe ideally track the time when we
2832 // called .then() but without updating the task.time field since that's used for the cutoff.
2833 advanceTaskTime(request, task, task.time);
2834 emitDebugChunk(request, task.id, debugInfo);
2835 // Mark the end time of the await. If we're aborting then we don't emit this
2836 // to signal that this never resolved inside this render.
2837 // If we're currently aborting, then this never resolved into user space.
2838 markOperationEndTime(request, task, awaitedNode.end);
2839 }
2840 }
2841
2842 function pingTask(request: Request, task: Task): void {
2843 if (
2844 enableProfilerTimer &&
2845 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
2846 ) {
2847 // If this was async we need to emit the time when it completes.
2848 task.timed = true;
2849 }
2850 const pingedTasks = request.pingedTasks;
2851 pingedTasks.push(task);
2852 if (pingedTasks.length === 1) {
2853 request.flushScheduled = request.destination !== null;
2854 if (request.type === PRERENDER || request.status === OPENING) {
2855 scheduleMicrotask(() => performWork(request));
2856 } else {
2857 scheduleWork(() => performWork(request));
2858 }
2859 }
2860 }
2861
2862 function createTask(
2863 request: Request,
2864 model: ReactClientValue,
2865 keyPath: ReactKey,
2866 implicitSlot: boolean,
2867 formatContext: FormatContext,
2868 abortSet: Set<Task>,
2869 lastTimestamp: number, // Profiling-only
2870 debugOwner: null | ReactComponentInfo, // DEV-only
2871 debugStack: null | Error, // DEV-only
2872 debugTask: null | ConsoleTask, // DEV-only
2873 ): Task {
2874 return createTaskWithID(
2875 request,
2876 request.nextChunkId++,
2877 model,
2878 keyPath,
2879 implicitSlot,
2880 formatContext,
2881 abortSet,
2882 lastTimestamp,
2883 debugOwner,
2884 debugStack,
2885 debugTask,
2886 );
2887 }
2888
2889 function createTaskWithID(
2890 request: Request,
2891 id: number,
2892 model: ReactClientValue,
2893 keyPath: ReactKey,
2894 implicitSlot: boolean,
2895 formatContext: FormatContext,
2896 abortSet: Set<Task>,
2897 lastTimestamp: number, // Profiling-only
2898 debugOwner: null | ReactComponentInfo, // DEV-only
2899 debugStack: null | Error, // DEV-only
2900 debugTask: null | ConsoleTask, // DEV-only
2901 ): Task {
2902 request.pendingChunks++;
2903 if (typeof model === 'object' && model !== null) {
2904 // If we're about to write this into a new task we can assign it an ID early so that
2905 // any other references can refer to the value we're about to write.
2906 if (keyPath !== null || implicitSlot) {
2907 // If we're in some kind of context we can't necessarily reuse this object depending
2908 // what parent components are used.
2909 } else {
2910 request.writtenObjects.set(model, serializeByValueID(id));
2911 }
2912 }
2913 const task: Task = {
2914 id,
2915 status: PENDING,
2916 model,
2917 keyPath,
2918 implicitSlot,
2919 formatContext: formatContext,
2920 ping: () => pingTask(request, task),
2921 thenableState: null,
2922 } as Omit<
2923 Task,
2924 | 'timed'
2925 | 'time'
2926 | 'environmentName'
2927 | 'debugOwner'
2928 | 'debugStack'
2929 | 'debugTask',
2930 > as any;
2931 if (
2932 enableProfilerTimer &&
2933 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
2934 ) {
2935 task.timed = false;
2936 task.time = lastTimestamp;
2937 }
2938 if (__DEV__) {
2939 task.environmentName = request.environmentName();
2940 task.debugOwner = debugOwner;
2941 task.debugStack = debugStack;
2942 task.debugTask = debugTask;
2943 }
2944 abortSet.add(task);
2945 return task;
2946 }
2947
2948 function resolveModel(
2949 request: Request,
2950 task: Task,
2951 parent:
2952 | {+[key: string | number]: ReactClientValue}
2953 | $ReadOnlyArray<ReactClientValue>,
2954 parentPropertyName: string,
2955 value: ReactClientValue,
2956 ): ReactJSONValue {
2957 // Replicate JSON.stringify's toJSON semantics: if the value has a toJSON
2958 // method, call it first. In practice this only matters for Date objects whose
2959 // toJSON calls toISOString. Custom toJSON objects are not supported and will
2960 // trigger a DEV warning below.
2961 let jsonValue: ReactClientValue = value;
2962 if (
2963 value !== null &&
2964 typeof value === 'object' &&
2965 // $FlowFixMe[method-unbinding]
2966 typeof value.toJSON === 'function'
2967 ) {
2968 // $FlowFixMe[incompatible-use]
2969 jsonValue = value.toJSON(parentPropertyName);
2970 }
2971
2972 if (__DEV__) {
2973 // $FlowFixMe[incompatible-use]
2974 const originalValue = parent[parentPropertyName];
2975 if (
2976 typeof originalValue === 'object' &&
2977 originalValue !== jsonValue &&
2978 !(originalValue instanceof Date)
2979 ) {
2980 // Call with the server component as the currently rendering component
2981 // for context.
2982 callWithDebugContextInDEV(request, task, () => {
2983 if (ArrayBuffer.isView(originalValue)) {
2984 // Binary data such as a Node.js Buffer carries a toJSON method, so it
2985 // is serialized through that method rather than as binary. A plain
2986 // Uint8Array or ArrayBuffer has no toJSON and is serialized as
2987 // binary.
2988 console.error(
2989 'Binary data with a toJSON method, such as a Node.js Buffer, is ' +
2990 'serialized through toJSON instead of as binary. Pass a ' +
2991 'Uint8Array or ArrayBuffer to send binary data.%s',
2992 describeObjectForErrorMessage(parent, parentPropertyName),
2993 );
2994 } else if (objectName(originalValue) !== 'Object') {
2995 const jsxParentType = jsxChildrenParents.get(parent);
2996 if (typeof jsxParentType === 'string') {
2997 console.error(
2998 '%s objects cannot be rendered as text children. Try formatting it using toString().%s',
2999 objectName(originalValue),
3000 describeObjectForErrorMessage(parent, parentPropertyName),
3001 );
3002 } else {
3003 console.error(
3004 'Only plain objects can be passed to Client Components from Server Components. ' +
3005 '%s objects are not supported.%s',
3006 objectName(originalValue),
3007 describeObjectForErrorMessage(parent, parentPropertyName),
3008 );
3009 }
3010 } else {
3011 console.error(
3012 'Only plain objects can be passed to Client Components from Server Components. ' +
3013 'Objects with toJSON methods are not supported. Convert it manually ' +
3014 'to a simple value before passing it to props.%s',
3015 describeObjectForErrorMessage(parent, parentPropertyName),
3016 );
3017 }
3018 });
3019 }
3020 }
3021
3022 const rendered = renderModel(
3023 request,
3024 task,
3025 parent,
3026 parentPropertyName,
3027 jsonValue,
3028 );
3029
3030 if (rendered === null || typeof rendered !== 'object') {
3031 return rendered;
3032 }
3033
3034 if (isArray(rendered)) {
3035 const resolved: Array<ReactJSONValue> = [];
3036 for (let i = 0; i < rendered.length; i++) {
3037 resolved[i] = resolveModel(request, task, rendered, '' + i, rendered[i]);
3038 }
3039 return resolved;
3040 }
3041
3042 // Use `{}` for fast properties; `__proto__` is handled below because simple
3043 // assignment would hit Object.prototype's setter instead of creating a key.
3044 const resolved: {[key: string]: ReactJSONValue} = {} as any;
3045 for (const key in rendered) {
3046 if (hasOwnProperty.call(rendered, key)) {
3047 const resolvedValue = resolveModel(
3048 request,
3049 task,
3050 rendered,
3051 key,
3052 rendered[key],
3053 );
3054 if (key === __PROTO__) {
3055 // Match JSON's ordinary data-property semantics for this legacy key.
3056 Object.defineProperty(resolved, key, {
3057 value: resolvedValue,
3058 enumerable: true,
3059 writable: true,
3060 configurable: true,
3061 });
3062 } else {
3063 resolved[key] = resolvedValue;
3064 }
3065 }
3066 }
3067 return resolved;
3068 }
3069
3070 function serializeByValueID(id: number): string {
3071 return '$' + id.toString(16);
3072 }
3073
3074 function serializeLazyID(id: number): string {
3075 return '$L' + id.toString(16);
3076 }
3077
3078 function serializePromiseID(id: number): string {
3079 return '$@' + id.toString(16);
3080 }
3081
3082 function serializeWeakPromiseID(id: number): string {
3083 return '$w' + id.toString(16);
3084 }
3085
3086 function serializeServerReferenceID(id: number): string {
3087 return '$h' + id.toString(16);
3088 }
3089
3090 function serializeSymbolReference(name: string): string {
3091 return '$S' + name;
3092 }
3093
3094 function serializeDeferredObject(
3095 request: Request,
3096 value: ReactClientReference | string,
3097 ): string {
3098 const deferredDebugObjects = request.deferredDebugObjects;
3099 if (deferredDebugObjects !== null) {
3100 // This client supports a long lived connection. We can assign this object
3101 // an ID to be lazy loaded later.
3102 // This keeps the connection alive until we ask for it or release it.
3103 request.pendingDebugChunks++;
3104 const id = request.nextChunkId++;
3105 deferredDebugObjects.existing.set(value, id);
3106 deferredDebugObjects.retained.set(id, value);
3107 return '$Y' + id.toString(16);
3108 }
3109 return '$Y';
3110 }
3111
3112 function serializeNumber(number: number): string | number {
3113 if (Number.isFinite(number)) {
3114 if (number === 0 && 1 / number === -Infinity) {
3115 return '$-0';
3116 } else {
3117 return number;
3118 }
3119 } else {
3120 if (number === Infinity) {
3121 return '$Infinity';
3122 } else if (number === -Infinity) {
3123 return '$-Infinity';
3124 } else {
3125 return '$NaN';
3126 }
3127 }
3128 }
3129
3130 function serializeUndefined(): string {
3131 return '$undefined';
3132 }
3133
3134 function serializeDate(date: Date): string {
3135 // JSON.stringify automatically calls Date.prototype.toJSON which calls toISOString.
3136 // We need only tack on a $D prefix.
3137 return '$D' + date.toJSON();
3138 }
3139
3140 function serializeDateFromDateJSON(dateJSON: string): string {
3141 // JSON.stringify automatically calls Date.prototype.toJSON which calls toISOString.
3142 // We need only tack on a $D prefix.
3143 return '$D' + dateJSON;
3144 }
3145
3146 function serializeBigInt(n: bigint): string {
3147 return '$n' + n.toString(10);
3148 }
3149
3150 function serializeRowHeader(tag: string, id: number) {
3151 return id.toString(16) + ':' + tag;
3152 }
3153
3154 function encodeReferenceChunk(
3155 request: Request,
3156 id: number,
3157 reference: string,
3158 ): Chunk {
3159 const json = stringify(reference);
3160 const row = id.toString(16) + ':' + json + '\n';
3161 return stringToChunk(row);
3162 }
3163
3164 function serializeClientReference(
3165 request: Request,
3166 parent:
3167 | {+[propertyName: string | number]: ReactClientValue}
3168 | $ReadOnlyArray<ReactClientValue>,
3169 parentPropertyName: string,
3170 clientReference: ClientReference<any>,
3171 ): string {
3172 const clientReferenceKey: ClientReferenceKey =
3173 getClientReferenceKey(clientReference);
3174 const writtenClientReferences = request.writtenClientReferences;
3175 const existingId = writtenClientReferences.get(clientReferenceKey);
3176 if (existingId !== undefined) {
3177 if (parent[0] === REACT_ELEMENT_TYPE && parentPropertyName === '1') {
3178 // If we're encoding the "type" of an element, we can refer
3179 // to that by a lazy reference instead of directly since React
3180 // knows how to deal with lazy values. This lets us suspend
3181 // on this component rather than its parent until the code has
3182 // loaded.
3183 return serializeLazyID(existingId);
3184 }
3185 return serializeByValueID(existingId);
3186 }
3187 try {
3188 const clientReferenceMetadata: ClientReferenceMetadata =
3189 resolveClientReferenceMetadata(request.bundlerConfig, clientReference);
3190 // Stringify before claiming a chunk id so a throw can't leave it pending.
3191 const json = stringifyImportMetadata(
3192 request,
3193 clientReferenceMetadata,
3194 false,
3195 );
3196 request.pendingChunks++;
3197 const importId = request.nextChunkId++;
3198 emitImportChunk(request, importId, json, false);
3199 writtenClientReferences.set(clientReferenceKey, importId);
3200 if (parent[0] === REACT_ELEMENT_TYPE && parentPropertyName === '1') {
3201 // If we're encoding the "type" of an element, we can refer
3202 // to that by a lazy reference instead of directly since React
3203 // knows how to deal with lazy values. This lets us suspend
3204 // on this component rather than its parent until the code has
3205 // loaded.
3206 return serializeLazyID(importId);
3207 }
3208 return serializeByValueID(importId);
3209 } catch (x) {
3210 request.pendingChunks++;
3211 const errorId = request.nextChunkId++;
3212 const digest = logRecoverableError(request, x, null);
3213 emitErrorChunk(request, errorId, digest, x, false, null);
3214 return serializeByValueID(errorId);
3215 }
3216 }
3217
3218 function serializeDebugClientReference(
3219 request: Request,
3220 parent:
3221 | {+[propertyName: string | number]: ReactClientValue}
3222 | $ReadOnlyArray<ReactClientValue>,
3223 parentPropertyName: string,
3224 clientReference: ClientReference<any>,
3225 ): string {
3226 // Like serializeDebugClientReference but it doesn't dedupe in the regular set
3227 // and it writes to completedDebugChunk instead of imports.
3228 const clientReferenceKey: ClientReferenceKey =
3229 getClientReferenceKey(clientReference);
3230 const writtenClientReferences = request.writtenClientReferences;
3231 const existingId = writtenClientReferences.get(clientReferenceKey);
3232 if (existingId !== undefined) {
3233 if (parent[0] === REACT_ELEMENT_TYPE && parentPropertyName === '1') {
3234 // If we're encoding the "type" of an element, we can refer
3235 // to that by a lazy reference instead of directly since React
3236 // knows how to deal with lazy values. This lets us suspend
3237 // on this component rather than its parent until the code has
3238 // loaded.
3239 return serializeLazyID(existingId);
3240 }
3241 return serializeByValueID(existingId);
3242 }
3243 try {
3244 const clientReferenceMetadata: ClientReferenceMetadata =
3245 resolveClientReferenceMetadata(request.bundlerConfig, clientReference);
3246 const json = stringifyImportMetadata(
3247 request,
3248 clientReferenceMetadata,
3249 true,
3250 );
3251 request.pendingDebugChunks++;
3252 const importId = request.nextChunkId++;
3253 emitImportChunk(request, importId, json, true);
3254 if (parent[0] === REACT_ELEMENT_TYPE && parentPropertyName === '1') {
3255 // If we're encoding the "type" of an element, we can refer
3256 // to that by a lazy reference instead of directly since React
3257 // knows how to deal with lazy values. This lets us suspend
3258 // on this component rather than its parent until the code has
3259 // loaded.
3260 return serializeLazyID(importId);
3261 }
3262 return serializeByValueID(importId);
3263 } catch (x) {
3264 request.pendingDebugChunks++;
3265 const errorId = request.nextChunkId++;
3266 const digest = logRecoverableError(request, x, null);
3267 emitErrorChunk(request, errorId, digest, x, true, null);
3268 return serializeByValueID(errorId);
3269 }
3270 }
3271
3272 function outlineModel(request: Request, value: ReactClientValue): number {
3273 return outlineModelWithFormatContext(
3274 request,
3275 value,
3276 // For deduped values we don't know which context it will be reused in
3277 // so we have to assume that it's the root context.
3278 createRootFormatContext(),
3279 );
3280 }
3281
3282 function outlineModelWithFormatContext(
3283 request: Request,
3284 value: ReactClientValue,
3285 formatContext: FormatContext,
3286 ): number {
3287 const newTask = createTask(
3288 request,
3289 value,
3290 null, // The way we use outlining is for reusing an object.
3291 false, // It makes no sense for that use case to be contextual.
3292 formatContext, // Except for FormatContext we optimistically use it.
3293 request.abortableTasks,
3294 enableProfilerTimer &&
3295 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
3296 ? performance.now() // TODO: This should really inherit the time from the task.
3297 : 0,
3298 null, // TODO: Currently we don't associate any debug information with
3299 null, // this object on the server. If it ends up erroring, it won't
3300 null, // have any context on the server but can on the client.
3301 );
3302 retryTask(request, newTask);
3303 return newTask.id;
3304 }
3305
3306 function serializeServerReference(
3307 request: Request,
3308 serverReference: ServerReference<any>,
3309 ): string {
3310 const writtenServerReferences = request.writtenServerReferences;
3311 const existingId = writtenServerReferences.get(serverReference);
3312 if (existingId !== undefined) {
3313 return serializeServerReferenceID(existingId);
3314 }
3315
3316 const boundArgs: null | Array<any> = getServerReferenceBoundArguments(
3317 request.bundlerConfig,
3318 serverReference,
3319 );
3320 const bound = boundArgs === null ? null : Promise.resolve(boundArgs);
3321 const id = getServerReferenceId(request.bundlerConfig, serverReference);
3322
3323 let location: null | ReactFunctionLocation = null;
3324 if (__DEV__) {
3325 const error = getServerReferenceLocation(
3326 request.bundlerConfig,
3327 serverReference,
3328 );
3329 // $FlowFixMe[constant-condition]
3330 if (error) {
3331 const frames = parseStackTrace(error, 1);
3332 if (frames.length > 0) {
3333 const firstFrame = frames[0];
3334 location = [
3335 firstFrame[0],
3336 firstFrame[1],
3337 firstFrame[2], // The line and col of the callsite represents the
3338 firstFrame[3], // enclosing line and col of the function.
3339 ];
3340 }
3341 }
3342 }
3343
3344 const serverReferenceMetadata: {
3345 id: ServerReferenceId,
3346 bound: null | Promise<Array<any>>,
3347 name?: string, // DEV-only
3348 env?: string, // DEV-only
3349 location?: ReactFunctionLocation, // DEV-only
3350 } =
3351 __DEV__ && location !== null
3352 ? {
3353 id,
3354 bound,
3355 name:
3356 typeof serverReference === 'function' ? serverReference.name : '',
3357 env: (0, request.environmentName)(),
3358 location,
3359 }
3360 : {
3361 id,
3362 bound,
3363 };
3364 const metadataId = outlineModel(request, serverReferenceMetadata);
3365 writtenServerReferences.set(serverReference, metadataId);
3366 return serializeServerReferenceID(metadataId);
3367 }
3368
3369 function serializeTemporaryReference(
3370 request: Request,
3371 reference: string,
3372 ): string {
3373 return '$T' + reference;
3374 }
3375
3376 function serializeLargeTextString(request: Request, text: string): string {
3377 request.pendingChunks++;
3378 const textId = request.nextChunkId++;
3379 emitTextChunk(request, textId, text, false);
3380 return serializeByValueID(textId);
3381 }
3382
3383 function serializeDebugLargeTextString(request: Request, text: string): string {
3384 request.pendingDebugChunks++;
3385 const textId = request.nextChunkId++;
3386 emitTextChunk(request, textId, text, true);
3387 return serializeByValueID(textId);
3388 }
3389
3390 function serializeMap(
3391 request: Request,
3392 map: Map<ReactClientValue, ReactClientValue>,
3393 ): string {
3394 const entries = Array.from(map);
3395 const id = outlineModel(request, entries);
3396 return '$Q' + id.toString(16);
3397 }
3398
3399 function serializeFormData(request: Request, formData: FormData): string {
3400 const entries = Array.from(formData.entries());
3401 const id = outlineModel(request, entries as any);
3402 return '$K' + id.toString(16);
3403 }
3404
3405 function serializeDebugFormData(request: Request, formData: FormData): string {
3406 const entries = Array.from(formData.entries());
3407 const id = outlineDebugModel(
3408 request,
3409 {objectLimit: entries.length * 2 + 1},
3410 entries as any,
3411 );
3412 return '$K' + id.toString(16);
3413 }
3414
3415 function serializeSet(request: Request, set: Set<ReactClientValue>): string {
3416 const entries = Array.from(set);
3417 const id = outlineModel(request, entries);
3418 return '$W' + id.toString(16);
3419 }
3420
3421 function serializeDebugMap(
3422 request: Request,
3423 counter: {objectLimit: number},
3424 map: Map<ReactClientValue, ReactClientValue>,
3425 ): string {
3426 // Like serializeMap but for renderDebugModel.
3427 const entries = Array.from(map);
3428 // The Map itself doesn't take up any space but the outlined object does.
3429 counter.objectLimit++;
3430 for (let i = 0; i < entries.length; i++) {
3431 // Outline every object entry in case we run out of space to serialize them.
3432 // Because we can't mark these values as limited.
3433 const entry = entries[i];
3434 doNotLimit.add(entry);
3435 const key = entry[0];
3436 const value = entry[1];
3437 if (typeof key === 'object' && key !== null) {
3438 doNotLimit.add(key);
3439 }
3440 if (typeof value === 'object' && value !== null) {
3441 doNotLimit.add(value);
3442 }
3443 }
3444 const id = outlineDebugModel(request, counter, entries);
3445 return '$Q' + id.toString(16);
3446 }
3447
3448 function serializeDebugSet(
3449 request: Request,
3450 counter: {objectLimit: number},
3451 set: Set<ReactClientValue>,
3452 ): string {
3453 // Like serializeMap but for renderDebugModel.
3454 const entries = Array.from(set);
3455 // The Set itself doesn't take up any space but the outlined object does.
3456 counter.objectLimit++;
3457 for (let i = 0; i < entries.length; i++) {
3458 // Outline every object entry in case we run out of space to serialize them.
3459 // Because we can't mark these values as limited.
3460 const entry = entries[i];
3461 if (typeof entry === 'object' && entry !== null) {
3462 doNotLimit.add(entry);
3463 }
3464 }
3465 const id = outlineDebugModel(request, counter, entries);
3466 return '$W' + id.toString(16);
3467 }
3468
3469 function serializeIterator(
3470 request: Request,
3471 iterator: Iterator<ReactClientValue>,
3472 ): string {
3473 const id = outlineModel(request, Array.from(iterator));
3474 return '$i' + id.toString(16);
3475 }
3476
3477 function serializeTypedArray(
3478 request: Request,
3479 tag: string,
3480 typedArray: $ArrayBufferView,
3481 ): string {
3482 request.pendingChunks++;
3483 const bufferId = request.nextChunkId++;
3484 emitTypedArrayChunk(request, bufferId, tag, typedArray, false);
3485 return serializeByValueID(bufferId);
3486 }
3487
3488 function serializeDebugTypedArray(
3489 request: Request,
3490 tag: string,
3491 typedArray: $ArrayBufferView,
3492 ): string {
3493 if (typedArray.byteLength > 1000 && !doNotLimit.has(typedArray)) {
3494 // Defer large typed arrays.
3495 return serializeDeferredObject(request, typedArray);
3496 }
3497 request.pendingDebugChunks++;
3498 const bufferId = request.nextChunkId++;
3499 emitTypedArrayChunk(request, bufferId, tag, typedArray, true);
3500 return serializeByValueID(bufferId);
3501 }
3502
3503 function serializeDebugBlob(request: Request, blob: Blob): string {
3504 const model: Array<string | Uint8Array> = [blob.type];
3505 const reader = blob.stream().getReader();
3506 request.pendingDebugChunks++;
3507 const id = request.nextChunkId++;
3508 function progress(
3509 entry: {done: false, value: Uint8Array} | {done: true, value: void},
3510 ): Promise<void> | void {
3511 if (entry.done) {
3512 emitOutlinedDebugModelChunk(
3513 request,
3514 id,
3515 {objectLimit: model.length + 2},
3516 model,
3517 );
3518 enqueueFlush(request);
3519 return;
3520 }
3521 // TODO: Emit the chunk early and refer to it later by dedupe.
3522 model.push(entry.value);
3523 // $FlowFixMe[incompatible-type]
3524 return reader.read().then(progress).catch(error);
3525 }
3526 function error(reason: mixed) {
3527 const digest = '';
3528 emitErrorChunk(request, id, digest, reason, true, null);
3529 enqueueFlush(request);
3530 // $FlowFixMe[incompatible-type] should be able to pass mixed
3531 reader.cancel(reason).then(noop, noop);
3532 }
3533 // $FlowFixMe[incompatible-type]
3534 reader.read().then(progress).catch(error);
3535 return '$B' + id.toString(16);
3536 }
3537
3538 function serializeBlob(request: Request, blob: Blob): string {
3539 const model: Array<string | Uint8Array> = [blob.type];
3540 const newTask = createTask(
3541 request,
3542 model,
3543 null,
3544 false,
3545 createRootFormatContext(),
3546 request.abortableTasks,
3547 enableProfilerTimer &&
3548 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
3549 ? performance.now() // TODO: This should really inherit the time from the task.
3550 : 0,
3551 null, // TODO: Currently we don't associate any debug information with
3552 null, // this object on the server. If it ends up erroring, it won't
3553 null, // have any context on the server but can on the client.
3554 );
3555
3556 const reader = blob.stream().getReader();
3557
3558 function progress(
3559 entry: {done: false, value: Uint8Array} | {done: true, value: void},
3560 ): Promise<void> | void {
3561 if (newTask.status !== PENDING) {
3562 return;
3563 }
3564 if (entry.done) {
3565 request.cacheController.signal.removeEventListener('abort', abortBlob);
3566 pingTask(request, newTask);
3567 return;
3568 }
3569 // TODO: Emit the chunk early and refer to it later by dedupe.
3570 model.push(entry.value);
3571 // $FlowFixMe[incompatible-type]
3572 return reader.read().then(progress).catch(error);
3573 }
3574 function error(reason: mixed) {
3575 if (newTask.status !== PENDING) {
3576 return;
3577 }
3578 request.cacheController.signal.removeEventListener('abort', abortBlob);
3579 erroredTask(request, newTask, reason);
3580 enqueueFlush(request);
3581 // $FlowFixMe[incompatible-type] should be able to pass mixed
3582 // $FlowFixMe[incompatible-use]
3583 reader.cancel(reason).then(error, error);
3584 }
3585 function abortBlob() {
3586 if (newTask.status !== PENDING) {
3587 return;
3588 }
3589 const signal = request.cacheController.signal;
3590 signal.removeEventListener('abort', abortBlob);
3591 const reason = signal.reason;
3592 if (request.type === PRERENDER) {
3593 request.abortableTasks.delete(newTask);
3594 haltTask(newTask, request);
3595 finishHaltedTask(newTask, request);
3596 } else {
3597 // TODO: Make this use abortTask() instead.
3598 erroredTask(request, newTask, reason);
3599 enqueueFlush(request);
3600 }
3601 // $FlowFixMe[incompatible-use] should be able to pass mixed
3602 reader.cancel(reason).then(error, error);
3603 }
3604
3605 request.cacheController.signal.addEventListener('abort', abortBlob);
3606
3607 // $FlowFixMe[incompatible-type]
3608 reader.read().then(progress).catch(error);
3609
3610 return '$B' + newTask.id.toString(16);
3611 }
3612
3613 function escapeStringValue(value: string): string {
3614 if (value[0] === '$') {
3615 // We need to escape $ prefixed strings since we use those to encode
3616 // references to IDs and as special symbol values.
3617 return '$' + value;
3618 } else {
3619 return value;
3620 }
3621 }
3622
3623 function serializeImportString(request: Request, value: string): string {
3624 // No maximum length because import strings are short and repeat often.
3625 // Deduping model strings too would need one to skip very long strings.
3626 if (value.length < MIN_DEDUPLICATED_IMPORT_STRING_LENGTH) {
3627 return escapeStringValue(value);
3628 }
3629 const writtenStrings = request.writtenImportStrings;
3630 const existing = writtenStrings.get(value);
3631 if (existing !== undefined) {
3632 return existing;
3633 }
3634 const size = request.writtenImportStringsSize + value.length;
3635 if (size > MAX_DEDUPLICATED_IMPORT_STRINGS_SIZE) {
3636 // The map is full. Strings already outlined keep deduping; new ones are
3637 // written out every time.
3638 return escapeStringValue(value);
3639 }
3640 request.writtenImportStringsSize = size;
3641 // Chunk names are almost always shared, so the first occurrence is outlined
3642 // right away instead of waiting for a repeat.
3643 request.pendingChunks++;
3644 const outlinedId = request.nextChunkId++;
3645 // $FlowFixMe[incompatible-type] stringify can return null
3646 const json: string = stringify(escapeStringValue(value));
3647 // The client reads import metadata synchronously, so this row has to have
3648 // been written by the time the referencing row arrives. Import chunks are
3649 // flushed ahead of regular ones, which regular chunks can't guarantee.
3650 request.completedImportChunks.push(
3651 stringToChunk(outlinedId.toString(16) + ':' + json + '\n'),
3652 );
3653 const ref = serializeByValueID(outlinedId);
3654 writtenStrings.set(value, ref);
3655 return ref;
3656 }
3657
3658 let modelRoot: null | ReactClientValue = false;
3659
3660 function renderModel(
3661 request: Request,
3662 task: Task,
3663 parent:
3664 | {+[key: string | number]: ReactClientValue}
3665 | $ReadOnlyArray<ReactClientValue>,
3666 key: string,
3667 value: ReactClientValue,
3668 ): ReactJSONValue {
3669 // First time we're serializing the key, we should add it to the size.
3670 serializedSize += key.length;
3671
3672 const prevKeyPath = task.keyPath;
3673 const prevImplicitSlot = task.implicitSlot;
3674 try {
3675 return renderModelDestructive(request, task, parent, key, value);
3676 } catch (thrownValue) {
3677 // If the suspended/errored value was an element or lazy it can be reduced
3678 // to a lazy reference, so that it doesn't error the parent.
3679 const model = task.model;
3680 const wasReactNode =
3681 typeof model === 'object' &&
3682 model !== null &&
3683 ((model as any).$$typeof === REACT_ELEMENT_TYPE ||
3684 (model as any).$$typeof === REACT_LAZY_TYPE);
3685
3686 if (request.status === ABORTING) {
3687 task.status = ABORTED;
3688 if (request.type === PRERENDER) {
3689 // This will create a new task and refer to it in this slot
3690 // the new task won't be retried because we are aborting
3691 return outlineHaltedTask(request, task, wasReactNode);
3692 }
3693 const errorId = request.fatalError as any;
3694 if (wasReactNode) {
3695 return serializeLazyID(errorId);
3696 }
3697 return serializeByValueID(errorId);
3698 }
3699
3700 const x =
3701 thrownValue === SuspenseException
3702 ? // This is a special type of exception used for Suspense. For historical
3703 // reasons, the rest of the Suspense implementation expects the thrown
3704 // value to be a thenable, because before `use` existed that was the
3705 // (unstable) API for suspending. This implementation detail can change
3706 // later, once we deprecate the old API in favor of `use`.
3707 getSuspendedThenable()
3708 : thrownValue;
3709
3710 // $FlowFixMe[invalid-compare]
3711 if (typeof x === 'object' && x !== null) {
3712 // $FlowFixMe[method-unbinding]
3713 if (typeof x.then === 'function') {
3714 // Something suspended, we'll need to create a new task and resolve it later.
3715 const newTask = createTask(
3716 request,
3717 task.model,
3718 task.keyPath,
3719 task.implicitSlot,
3720 task.formatContext,
3721 request.abortableTasks,
3722 enableProfilerTimer &&
3723 (enableComponentPerformanceTrack || enableAsyncDebugInfo)
3724 ? task.time
3725 : 0,
3726 __DEV__ ? task.debugOwner : null,
3727 __DEV__ ? task.debugStack : null,
3728 __DEV__ ? task.debugTask : null,
3729 );
3730 const ping = newTask.ping;
3731 (x as any).then(ping, ping);
3732 newTask.thenableState = getThenableStateAfterSuspending();
3733
3734 // Restore the context. We assume that this will be restored by the inner
3735 // functions in case nothing throws so we don't use "finally" here.
3736 task.keyPath = prevKeyPath;
3737 task.implicitSlot = prevImplicitSlot;
3738
3739 if (wasReactNode) {
3740 return serializeLazyID(newTask.id);
3741 }
3742 return serializeByValueID(newTask.id);
3743 }
3744 }
3745
3746 // Restore the context. We assume that this will be restored by the inner
3747 // functions in case nothing throws so we don't use "finally" here.
3748 task.keyPath = prevKeyPath;
3749 task.implicitSlot = prevImplicitSlot;
3750
3751 // Something errored. We'll still send everything we have up until this point.
3752 request.pendingChunks++;
3753 const errorId = request.nextChunkId++;
3754 const digest = logRecoverableError(request, x, task);
3755 emitErrorChunk(
3756 request,
3757 errorId,
3758 digest,
3759 x,
3760 false,
3761 __DEV__ ? task.debugOwner : null,
3762 );
3763 if (wasReactNode) {
3764 // We'll replace this element with a lazy reference that throws on the client
3765 // once it gets rendered.
3766 return serializeLazyID(errorId);
3767 }
3768 // If we don't know if it was a React Node we render a direct reference and let
3769 // the client deal with it.
3770 return serializeByValueID(errorId);
3771 }
3772 }
3773
3774 function renderModelDestructive(
3775 request: Request,
3776 task: Task,
3777 parent:
3778 | {+[propertyName: string | number]: ReactClientValue}
3779 | $ReadOnlyArray<ReactClientValue>,
3780 parentPropertyName: string,
3781 value: ReactClientValue,
3782 ): ReactJSONValue {
3783 // Set the currently rendering model
3784 task.model = value;
3785
3786 if (__DEV__) {
3787 if (parentPropertyName === __PROTO__) {
3788 callWithDebugContextInDEV(request, task, () => {
3789 console.error(
3790 'Expected not to serialize an object with own property `__proto__`. When parsed this property will be omitted.%s',
3791 describeObjectForErrorMessage(parent, parentPropertyName),
3792 );
3793 });
3794 }
3795 }
3796
3797 // Special Symbol, that's very common.
3798 if (value === REACT_ELEMENT_TYPE) {
3799 return '$';
3800 }
3801
3802 if (value === null) {
3803 return null;
3804 }
3805
3806 if (typeof value === 'object') {
3807 switch ((value as any).$$typeof) {
3808 case REACT_ELEMENT_TYPE: {
3809 let elementReference = null;
3810 const writtenObjects = request.writtenObjects;
3811 if (task.keyPath !== null || task.implicitSlot) {
3812 // If we're in some kind of context we can't reuse the result of this render or
3813 // previous renders of this element. We only reuse elements if they're not wrapped
3814 // by another Server Component.
3815 } else {
3816 const existingReference = writtenObjects.get(value);
3817 if (existingReference !== undefined) {
3818 if (modelRoot === value) {
3819 // This is the ID we're currently emitting so we need to write it
3820 // once but if we discover it again, we refer to it by id.
3821 modelRoot = null;
3822 } else {
3823 // We've already emitted this as an outlined object, so we can refer to that by its
3824 // existing ID. TODO: We should use a lazy reference since, unlike plain objects,
3825 // elements might suspend so it might not have emitted yet even if we have the ID for
3826 // it. However, this creates an extra wrapper when it's not needed. We should really
3827 // detect whether this already was emitted and synchronously available. In that
3828 // case we can refer to it synchronously and only make it lazy otherwise.
3829 // We currently don't have a data structure that lets us see that though.
3830 return existingReference;
3831 }
3832 } else if (parentPropertyName.indexOf(':') === -1) {
3833 // TODO: If the property name contains a colon, we don't dedupe. Escape instead.
3834 const parentReference = writtenObjects.get(parent);
3835 if (parentReference !== undefined) {
3836 // If the parent has a reference, we can refer to this object indirectly
3837 // through the property name inside that parent.
3838 elementReference = parentReference + ':' + parentPropertyName;
3839 writtenObjects.set(value, elementReference);
3840 }
3841 }
3842 }
3843
3844 const element: ReactElement = value as any;
3845
3846 if (serializedSize > MAX_ROW_SIZE) {
3847 return deferTask(request, task);
3848 }
3849
3850 if (__DEV__) {
3851 const debugInfo: ?ReactDebugInfo = (value as any)._debugInfo;
3852 if (debugInfo) {
3853 // If this came from Flight, forward any debug info into this new row.
3854 if (!canEmitDebugInfo) {
3855 // We don't have a chunk to assign debug info. We need to outline this
3856 // component to assign it an ID.
3857 return outlineTask(request, task);
3858 } else {
3859 // Forward any debug info we have the first time we see it.
3860 forwardDebugInfo(request, task, debugInfo);
3861 }
3862 }
3863 }
3864
3865 const props = element.props;
3866 // TODO: We should get the ref off the props object right before using
3867 // it.
3868 const refProp = props.ref;
3869 const ref = refProp !== undefined ? refProp : null;
3870
3871 // Attempt to render the Server Component.
3872
3873 if (__DEV__) {
3874 task.debugOwner = element._owner;
3875 task.debugStack = element._debugStack;
3876 task.debugTask = element._debugTask;
3877 if (
3878 element._owner === undefined ||
3879 element._debugStack === undefined ||
3880 element._debugTask === undefined
3881 ) {
3882 let key = '';
3883 if (element.key !== null && element.key !== REACT_OPTIMISTIC_KEY) {
3884 key = ' key="' + element.key + '"';
3885 }
3886
3887 console.error(
3888 'Attempted to render <%s%s> without development properties. ' +
3889 'This is not supported. It can happen if:' +
3890 '\n- The element is created with a production version of React but rendered in development.' +
3891 '\n- The element was cloned with a custom function instead of `React.cloneElement`.\n' +
3892 'The props of this element may help locate this element: %o',
3893 element.type,
3894 key,
3895 element.props,
3896 );
3897 }
3898 // TODO: Pop this. Since we currently don't have a point where we can pop the stack
3899 // this debug information will be used for errors inside sibling properties that
3900 // are not elements. Leading to the wrong attribution on the server. We could fix
3901 // that if we switch to a proper stack instead of resolveModel's recursive walk.
3902 // Attribution on the client is still correct since it has a pop.
3903 }
3904
3905 const newChild = renderElement(
3906 request,
3907 task,
3908 element.type,
3909 // $FlowFixMe[incompatible-call] the key of an element is null | string | ReactOptimisticKey
3910 element.key,
3911 ref,
3912 props,
3913 __DEV__ ? element._store.validated : 0,
3914 );
3915 if (
3916 typeof newChild === 'object' &&
3917 newChild !== null &&
3918 elementReference !== null
3919 ) {
3920 // If this element renders another object, we can now refer to that object through
3921 // the same location as this element.
3922 if (!writtenObjects.has(newChild)) {
3923 writtenObjects.set(newChild, elementReference);
3924 }
3925 }
3926 return newChild;
3927 }
3928 case REACT_LAZY_TYPE: {
3929 if (serializedSize > MAX_ROW_SIZE) {
3930 return deferTask(request, task);
3931 }
3932
3933 // Reset the task's thenable state before continuing. If there was one, it was
3934 // from suspending the lazy before.
3935 task.thenableState = null;
3936
3937 const lazy: LazyComponent<any, any> = value as any;
3938 let resolvedModel;
3939 if (__DEV__) {
3940 resolvedModel = callLazyInitInDEV(lazy);
3941 } else {
3942 const payload = lazy._payload;
3943 const init = lazy._init;
3944 resolvedModel = init(payload);
3945 }
3946 if (request.status === ABORTING) {
3947 // lazy initializers are user code and could abort during render
3948 // we don't wan to return any value resolved from the lazy initializer
3949 // if it aborts so we interrupt rendering here
3950 // eslint-disable-next-line no-throw-literal
3951 throw null;
3952 }
3953 if (__DEV__) {
3954 const debugInfo: ?ReactDebugInfo = lazy._debugInfo;
3955 if (debugInfo) {
3956 // If this came from Flight, forward any debug info into this new row.
3957 if (!canEmitDebugInfo) {
3958 // We don't have a chunk to assign debug info. We need to outline this
3959 // component to assign it an ID.
3960 return outlineTask(request, task);
3961 } else {
3962 // Forward any debug info we have the first time we see it.
3963 // We do this after init so that we have received all the debug info
3964 // from the server by the time we emit it.
3965 forwardDebugInfo(request, task, debugInfo);
3966 }
3967 }
3968 }
3969 return renderModelDestructive(
3970 request,
3971 task,
3972 parent,
3973 parentPropertyName,
3974 resolvedModel,
3975 );
3976 }
3977 case REACT_LEGACY_ELEMENT_TYPE: {
3978 throw new Error(
3979 'A React Element from an older version of React was rendered. ' +
3980 'This is not supported. It can happen if:\n' +
3981 '- Multiple copies of the "react" package is used.\n' +
3982 '- A library pre-bundled an old copy of "react" or "react/jsx-runtime".\n' +
3983 '- A compiler tries to "inline" JSX instead of using the runtime.',
3984 );
3985 }
3986 }
3987
3988 if (isClientReference(value)) {
3989 return serializeClientReference(
3990 request,
3991 parent,
3992 parentPropertyName,
3993 value as any,
3994 );
3995 }
3996
3997 if (request.temporaryReferences !== undefined) {
3998 const tempRef = resolveTemporaryReference(
3999 request.temporaryReferences,
4000 value,
4001 );
4002 if (tempRef !== undefined) {
4003 return serializeTemporaryReference(request, tempRef);
4004 }
4005 }
4006
4007 if (enableTaint) {
4008 const tainted = TaintRegistryObjects.get(value);
4009 if (tainted !== undefined) {
4010 throwTaintViolation(tainted);
4011 }
4012 }
4013
4014 const writtenObjects = request.writtenObjects;
4015 const existingReference = writtenObjects.get(value);
4016 // $FlowFixMe[method-unbinding]
4017 if (typeof value.then === 'function') {
4018 // A weak-pending thenable may never emit, so its reference is marked
4019 // on the wire ($w instead of $@). That way the client knows to leave
4020 // it forever pending, instead of erroring it, if the stream closes
4021 // first.
4022 if (existingReference !== undefined) {
4023 if (task.keyPath !== null || task.implicitSlot) {
4024 // If we're in some kind of context we can't reuse the result of this render or
4025 // previous renders of this element. We only reuse Promises if they're not wrapped
4026 // by another Server Component.
4027 const promiseId = serializeThenable(request, task, value as any);
4028 return enableFlightWeakThenables &&
4029 (value as any).status === 'pending_weak'
4030 ? serializeWeakPromiseID(promiseId)
4031 : serializePromiseID(promiseId);
4032 } else if (modelRoot === value) {
4033 // This is the ID we're currently emitting so we need to write it
4034 // once but if we discover it again, we refer to it by id.
4035 modelRoot = null;
4036 } else {
4037 // We've seen this promise before, so we can just refer to the same result.
4038 return existingReference;
4039 }
4040 }
4041 // We assume that any object with a .then property is a "Thenable" type,
4042 // or a Promise type. Either of which can be represented by a Promise.
4043 const promiseId = serializeThenable(request, task, value as any);
4044 const promiseReference =
4045 enableFlightWeakThenables && (value as any).status === 'pending_weak'
4046 ? serializeWeakPromiseID(promiseId)
4047 : serializePromiseID(promiseId);
4048 writtenObjects.set(value, promiseReference);
4049 return promiseReference;
4050 }
4051
4052 if (existingReference !== undefined) {
4053 if (modelRoot === value) {
4054 if (existingReference !== serializeByValueID(task.id)) {
4055 // Turns out that we already have this root at a different reference.
4056 // Use that after all.
4057 return existingReference;
4058 }
4059 // This is the ID we're currently emitting so we need to write it
4060 // once but if we discover it again, we refer to it by id.
4061 modelRoot = null;
4062 } else {
4063 // We've already emitted this as an outlined object, so we can
4064 // just refer to that by its existing ID.
4065 return existingReference;
4066 }
4067 } else if (parentPropertyName.indexOf(':') === -1) {
4068 // TODO: If the property name contains a colon, we don't dedupe. Escape instead.
4069 const parentReference = writtenObjects.get(parent);
4070 if (parentReference !== undefined) {
4071 // If the parent has a reference, we can refer to this object indirectly
4072 // through the property name inside that parent.
4073 let propertyName = parentPropertyName;
4074 if (isArray(parent) && parent[0] === REACT_ELEMENT_TYPE) {
4075 // For elements, we've converted it to an array but we'll have converted
4076 // it back to an element before we read the references so the property
4077 // needs to be aliased.
4078 switch (parentPropertyName) {
4079 case '1':
4080 propertyName = 'type';
4081 break;
4082 case '2':
4083 propertyName = 'key';
4084 break;
4085 case '3':
4086 propertyName = 'props';
4087 break;
4088 case '4':
4089 propertyName = '_owner';
4090 break;
4091 }
4092 }
4093 writtenObjects.set(value, parentReference + ':' + propertyName);
4094 }
4095 }
4096
4097 if (isArray(value)) {
4098 return renderFragment(request, task, value);
4099 }
4100
4101 if (value instanceof Map) {
4102 return serializeMap(request, value);
4103 }
4104 if (value instanceof Set) {
4105 return serializeSet(request, value);
4106 }
4107 // TODO: FormData is not available in old Node. Remove the typeof later.
4108 if (typeof FormData === 'function' && value instanceof FormData) {
4109 return serializeFormData(request, value);
4110 }
4111 if (value instanceof Error) {
4112 return serializeErrorValue(request, value);
4113 }
4114 if (value instanceof ArrayBuffer) {
4115 return serializeTypedArray(request, 'A', new Uint8Array(value));
4116 }
4117 if (value instanceof Int8Array) {
4118 // char
4119 return serializeTypedArray(request, 'O', value);
4120 }
4121 if (value instanceof Uint8Array) {
4122 // unsigned char
4123 return serializeTypedArray(request, 'o', value);
4124 }
4125 if (value instanceof Uint8ClampedArray) {
4126 // unsigned clamped char
4127 return serializeTypedArray(request, 'U', value);
4128 }
4129 if (value instanceof Int16Array) {
4130 // sort
4131 return serializeTypedArray(request, 'S', value);
4132 }
4133 if (value instanceof Uint16Array) {
4134 // unsigned short
4135 return serializeTypedArray(request, 's', value);
4136 }
4137 if (value instanceof Int32Array) {
4138 // long
4139 return serializeTypedArray(request, 'L', value);
4140 }
4141 if (value instanceof Uint32Array) {
4142 // unsigned long
4143 return serializeTypedArray(request, 'l', value);
4144 }
4145 if (value instanceof Float32Array) {
4146 // float
4147 return serializeTypedArray(request, 'G', value);
4148 }
4149 if (value instanceof Float64Array) {
4150 // double
4151 return serializeTypedArray(request, 'g', value);
4152 }
4153 if (value instanceof BigInt64Array) {
4154 // number
4155 return serializeTypedArray(request, 'M', value);
4156 }
4157 if (value instanceof BigUint64Array) {
4158 // unsigned number
4159 // We use "m" instead of "n" since JSON can start with "null"
4160 return serializeTypedArray(request, 'm', value);
4161 }
4162 if (value instanceof DataView) {
4163 return serializeTypedArray(request, 'V', value);
4164 }
4165 // TODO: Blob is not available in old Node. Remove the typeof check later.
4166 if (typeof Blob === 'function' && value instanceof Blob) {
4167 return serializeBlob(request, value);
4168 }
4169
4170 const iteratorFn = getIteratorFn(value);
4171 if (iteratorFn) {
4172 // TODO: Should we serialize the return value as well like we do for AsyncIterables?
4173 const iterator = iteratorFn.call(value);
4174 if (iterator === value) {
4175 // Iterator, not Iterable
4176 return serializeIterator(request, iterator as any);
4177 }
4178 return renderFragment(request, task, Array.from(iterator as any));
4179 }
4180
4181 // TODO: Blob is not available in old Node. Remove the typeof check later.
4182 if (
4183 typeof ReadableStream === 'function' &&
4184 value instanceof ReadableStream
4185 ) {
4186 return serializeReadableStream(request, task, value);
4187 }
4188 const getAsyncIterator: void | (() => $AsyncIterator<any, any, any>) = (
4189 value as any
4190 )[ASYNC_ITERATOR];
4191 if (typeof getAsyncIterator === 'function') {
4192 // We treat AsyncIterables as a Fragment and as such we might need to key them.
4193 return renderAsyncFragment(request, task, value as any, getAsyncIterator);
4194 }
4195
4196 // We put the Date check low b/c most of the time Date's will already have been serialized
4197 // before we process it in this function but when rendering a Date() as a top level it can
4198 // end up being a Date instance here. This is rare so we deprioritize it by putting it deep
4199 // in this function
4200 if (value instanceof Date) {
4201 return serializeDate(value);
4202 }
4203
4204 // Verify that this is a simple plain object.
4205 const proto = getPrototypeOf(value);
4206 if (
4207 proto !== ObjectPrototype &&
4208 (proto === null || getPrototypeOf(proto) !== null)
4209 ) {
4210 throw new Error(
4211 'Only plain objects, and a few built-ins, can be passed to Client Components ' +
4212 'from Server Components. Classes or null prototypes are not supported.' +
4213 describeObjectForErrorMessage(parent, parentPropertyName),
4214 );
4215 }
4216 if (__DEV__) {
4217 if (objectName(value) !== 'Object') {
4218 callWithDebugContextInDEV(request, task, () => {
4219 console.error(
4220 'Only plain objects can be passed to Client Components from Server Components. ' +
4221 '%s objects are not supported.%s',
4222 objectName(value),
4223 describeObjectForErrorMessage(parent, parentPropertyName),
4224 );
4225 });
4226 } else if (!isSimpleObject(value)) {
4227 callWithDebugContextInDEV(request, task, () => {
4228 console.error(
4229 'Only plain objects can be passed to Client Components from Server Components. ' +
4230 'Classes or other objects with methods are not supported.%s',
4231 describeObjectForErrorMessage(parent, parentPropertyName),
4232 );
4233 });
4234 } else if (Object.getOwnPropertySymbols) {
4235 const symbols = Object.getOwnPropertySymbols(value);
4236 if (symbols.length > 0) {
4237 callWithDebugContextInDEV(request, task, () => {
4238 console.error(
4239 'Only plain objects can be passed to Client Components from Server Components. ' +
4240 'Objects with symbol properties like %s are not supported.%s',
4241 symbols[0].description,
4242 describeObjectForErrorMessage(parent, parentPropertyName),
4243 );
4244 });
4245 }
4246 }
4247 }
4248
4249 // $FlowFixMe[incompatible-type]
4250 return value;
4251 }
4252
4253 if (typeof value === 'string') {
4254 if (enableTaint) {
4255 const tainted = TaintRegistryValues.get(value);
4256 if (tainted !== undefined) {
4257 throwTaintViolation(tainted.message);
4258 }
4259 }
4260 serializedSize += value.length;
4261 // TODO: Maybe too clever. If we support URL there's no similar trick.
4262 if (value[value.length - 1] === 'Z') {
4263 // Possibly a Date, whose toJSON automatically calls toISOString
4264 // $FlowFixMe[incompatible-use]
4265 const originalValue = parent[parentPropertyName];
4266 if (originalValue instanceof Date) {
4267 return serializeDateFromDateJSON(value);
4268 }
4269 }
4270 // $FlowFixMe[invalid-compare]
4271 if (value.length >= 1024 && byteLengthOfChunk !== null) {
4272 // For large strings, we encode them outside the JSON payload so that we
4273 // don't have to double encode and double parse the strings. This can also
4274 // be more compact in case the string has a lot of escaped characters.
4275 return serializeLargeTextString(request, value);
4276 }
4277 return escapeStringValue(value);
4278 }
4279
4280 if (typeof value === 'boolean') {
4281 return value;
4282 }
4283
4284 if (typeof value === 'number') {
4285 return serializeNumber(value);
4286 }
4287
4288 if (typeof value === 'undefined') {
4289 return serializeUndefined();
4290 }
4291
4292 if (typeof value === 'function') {
4293 if (isClientReference(value)) {
4294 return serializeClientReference(
4295 request,
4296 parent,
4297 parentPropertyName,
4298 value as any,
4299 );
4300 }
4301 if (isServerReference(value)) {
4302 return serializeServerReference(request, value as any);
4303 }
4304 if (request.temporaryReferences !== undefined) {
4305 const tempRef = resolveTemporaryReference(
4306 request.temporaryReferences,
4307 value,
4308 );
4309 if (tempRef !== undefined) {
4310 return serializeTemporaryReference(request, tempRef);
4311 }
4312 }
4313
4314 if (enableTaint) {
4315 const tainted = TaintRegistryObjects.get(value);
4316 if (tainted !== undefined) {
4317 throwTaintViolation(tainted);
4318 }
4319 }
4320
4321 if (isOpaqueTemporaryReference(value)) {
4322 throw new Error(
4323 'Could not reference an opaque temporary reference. ' +
4324 'This is likely due to misconfiguring the temporaryReferences options ' +
4325 'on the server.',
4326 );
4327 } else if (/^on[A-Z]/.test(parentPropertyName)) {
4328 throw new Error(
4329 'Event handlers cannot be passed to Client Component props.' +
4330 describeObjectForErrorMessage(parent, parentPropertyName) +
4331 '\nIf you need interactivity, consider converting part of this to a Client Component.',
4332 );
4333 } else if (
4334 __DEV__ &&
4335 (jsxChildrenParents.has(parent) ||
4336 (jsxPropsParents.has(parent) && parentPropertyName === 'children'))
4337 ) {
4338 const componentName = value.displayName || value.name || 'Component';
4339 throw new Error(
4340 'Functions are not valid as a child of Client Components. This may happen if ' +
4341 'you return ' +
4342 componentName +
4343 ' instead of <' +
4344 componentName +
4345 ' /> from render. ' +
4346 'Or maybe you meant to call this function rather than return it.' +
4347 describeObjectForErrorMessage(parent, parentPropertyName),
4348 );
4349 } else {
4350 throw new Error(
4351 'Functions cannot be passed directly to Client Components ' +
4352 'unless you explicitly expose it by marking it with "use server". ' +
4353 'Or maybe you meant to call this function rather than return it.' +
4354 describeObjectForErrorMessage(parent, parentPropertyName),
4355 );
4356 }
4357 }
4358
4359 if (typeof value === 'symbol') {
4360 const writtenSymbols = request.writtenSymbols;
4361 const existingId = writtenSymbols.get(value);
4362 if (existingId !== undefined) {
4363 return serializeByValueID(existingId);
4364 }
4365 // $FlowFixMe[incompatible-type] `description` might be undefined
4366 const name: string = value.description;
4367
4368 if (Symbol.for(name) !== value) {
4369 throw new Error(
4370 'Only global symbols received from Symbol.for(...) can be passed to Client Components. ' +
4371 `The symbol Symbol.for(${
4372 // $FlowFixMe[incompatible-type] `description` might be undefined
4373 value.description
4374 }) cannot be found among global symbols.` +
4375 describeObjectForErrorMessage(parent, parentPropertyName),
4376 );
4377 }
4378
4379 request.pendingChunks++;
4380 const symbolId = request.nextChunkId++;
4381 emitSymbolChunk(request, symbolId, name);
4382 writtenSymbols.set(value, symbolId);
4383 return serializeByValueID(symbolId);
4384 }
4385
4386 if (typeof value === 'bigint') {
4387 if (enableTaint) {
4388 const tainted = TaintRegistryValues.get(value);
4389 if (tainted !== undefined) {
4390 throwTaintViolation(tainted.message);
4391 }
4392 }
4393 return serializeBigInt(value);
4394 }
4395
4396 throw new Error(
4397 `Type ${typeof value} is not supported in Client Component props.` +
4398 describeObjectForErrorMessage(parent, parentPropertyName),
4399 );
4400 }
4401
4402 function logRecoverableError(
4403 request: Request,
4404 error: mixed,
4405 task: Task | null, // DEV-only
4406 ): string {
4407 const prevRequest = currentRequest;
4408 // We clear the request context so that console.logs inside the callback doesn't
4409 // get forwarded to the client.
4410 currentRequest = null;
4411 let errorDigest;
4412 try {
4413 const onError = request.onError;
4414 if (__DEV__ && task !== null) {
4415 // $FlowFixMe[constant-condition]
4416 if (supportsRequestStorage) {
4417 errorDigest = requestStorage.run(
4418 undefined,
4419 callWithDebugContextInDEV,
4420 request,
4421 task,
4422 onError,
4423 error,
4424 );
4425 } else {
4426 errorDigest = callWithDebugContextInDEV(request, task, onError, error);
4427 }
4428 // $FlowFixMe[constant-condition]
4429 } else if (supportsRequestStorage) {
4430 // Exit the request context while running callbacks.
4431 errorDigest = requestStorage.run(undefined, onError, error);
4432 } else {
4433 errorDigest = onError(error);
4434 }
4435 } finally {
4436 currentRequest = prevRequest;
4437 }
4438 if (errorDigest != null && typeof errorDigest !== 'string') {
4439 // eslint-disable-next-line react-internal/prod-error-codes
4440 throw new Error(
4441 `onError returned something with a type other than "string". onError should return a string and may return null or undefined but must not return anything else. It received something of type "${typeof errorDigest}" instead`,
4442 );
4443 }
4444 return errorDigest || '';
4445 }
4446
4447 function fatalError(request: Request, error: mixed): void {
4448 const onFatalError = request.onFatalError;
4449 onFatalError(error);
4450 if (enableTaint) {
4451 cleanupTaintQueue(request);
4452 }
4453 // This is called outside error handling code such as if an error happens in React internals.
4454 if (request.destination !== null) {
4455 request.status = CLOSED;
4456 closeWithError(request.destination, error);
4457 } else {
4458 request.status = CLOSING;
4459 request.fatalError = error;
4460 }
4461 const abortReason = new Error(
4462 'The render was aborted due to a fatal error.',
4463 {
4464 cause: error,
4465 },
4466 );
4467 request.cacheController.abort(abortReason);
4468 }
4469
4470 function serializeErrorValue(request: Request, error: Error): string {
4471 if (__DEV__) {
4472 let name: string = 'Error';
4473 let message: string;
4474 let stack: ReactStackTrace;
4475 let env = (0, request.environmentName)();
4476 try {
4477 name = error.name;
4478 // eslint-disable-next-line react-internal/safe-string-coercion
4479 message = String(error.message);
4480 stack = filterStackTrace(request, parseStackTrace(error, 0));
4481 const errorEnv = (error as any).environmentName;
4482 if (typeof errorEnv === 'string') {
4483 // This probably came from another FlightClient as a pass through.
4484 // Keep the environment name.
4485 env = errorEnv;
4486 }
4487 } catch (x) {
4488 message = 'An error occurred but serializing the error message failed.';
4489 stack = [];
4490 }
4491 const errorInfo: ReactErrorInfoDev = {name, message, stack, env};
4492 if ('cause' in error) {
4493 const cause: ReactClientValue = error.cause as any;
4494 const causeId = outlineModel(request, cause);
4495 errorInfo.cause = serializeByValueID(causeId);
4496 }
4497 if (
4498 typeof AggregateError !== 'undefined' &&
4499 error instanceof AggregateError
4500 ) {
4501 const errors: ReactClientValue = error.errors as any;
4502 const errorsId = outlineModel(request, errors);
4503 errorInfo.errors = serializeByValueID(errorsId);
4504 }
4505 const id = outlineModel(request, errorInfo);
4506 return '$Z' + id.toString(16);
4507 } else {
4508 // In prod we don't emit any information about this Error object to avoid
4509 // unintentional leaks. Since this doesn't actually throw on the server
4510 // we don't go through onError and so don't register any digest neither.
4511 return '$Z';
4512 }
4513 }
4514
4515 function serializeDebugErrorValue(
4516 request: Request,
4517 counter: {objectLimit: number},
4518 error: Error,
4519 ): string {
4520 if (__DEV__) {
4521 let name: string = 'Error';
4522 let message: string;
4523 let stack: ReactStackTrace;
4524 let env = (0, request.environmentName)();
4525 try {
4526 name = error.name;
4527 // eslint-disable-next-line react-internal/safe-string-coercion
4528 message = String(error.message);
4529 stack = filterStackTrace(request, parseStackTrace(error, 0));
4530 const errorEnv = (error as any).environmentName;
4531 if (typeof errorEnv === 'string') {
4532 // This probably came from another FlightClient as a pass through.
4533 // Keep the environment name.
4534 env = errorEnv;
4535 }
4536 } catch (x) {
4537 message = 'An error occurred but serializing the error message failed.';
4538 stack = [];
4539 }
4540 const errorInfo: ReactErrorInfoDev = {name, message, stack, env};
4541 if ('cause' in error) {
4542 counter.objectLimit--;
4543 const cause: ReactClientValue = error.cause as any;
4544 const causeId = outlineDebugModel(request, counter, cause);
4545 errorInfo.cause = serializeByValueID(causeId);
4546 }
4547 if (
4548 typeof AggregateError !== 'undefined' &&
4549 error instanceof AggregateError
4550 ) {
4551 counter.objectLimit--;
4552 const errors: ReactClientValue = error.errors as any;
4553 const errorsId = outlineDebugModel(request, counter, errors);
4554 errorInfo.errors = serializeByValueID(errorsId);
4555 }
4556 const id = outlineDebugModel(
4557 request,
4558 {objectLimit: stack.length * 2 + 1},
4559 errorInfo,
4560 );
4561 return '$Z' + id.toString(16);
4562 } else {
4563 // In prod we don't emit any information about this Error object to avoid
4564 // unintentional leaks. Since this doesn't actually throw on the server
4565 // we don't go through onError and so don't register any digest neither.
4566 return '$Z';
4567 }
4568 }
4569
4570 function emitErrorChunk(
4571 request: Request,
4572 id: number,
4573 digest: string,
4574 error: mixed,
4575 debug: boolean, // DEV-only
4576 owner: ?ReactComponentInfo, // DEV-only
4577 ): void {
4578 let errorInfo: ReactErrorInfo;
4579 if (__DEV__) {
4580 let name: string = 'Error';
4581 let message: string;
4582 let stack: ReactStackTrace;
4583 let env = (0, request.environmentName)();
4584 let causeReference: null | string = null;
4585 let errorsReference: null | string = null;
4586 try {
4587 if (error instanceof Error) {
4588 name = error.name;
4589 // eslint-disable-next-line react-internal/safe-string-coercion
4590 message = String(error.message);
4591 stack = filterStackTrace(request, parseStackTrace(error, 0));
4592 const errorEnv = (error as any).environmentName;
4593 if (typeof errorEnv === 'string') {
4594 // This probably came from another FlightClient as a pass through.
4595 // Keep the environment name.
4596 env = errorEnv;
4597 }
4598 if ('cause' in error) {
4599 const cause: ReactClientValue = error.cause as any;
4600 const causeId = debug
4601 ? outlineDebugModel(request, {objectLimit: 5}, cause)
4602 : outlineModel(request, cause);
4603 causeReference = serializeByValueID(causeId);
4604 }
4605 if (
4606 typeof AggregateError !== 'undefined' &&
4607 error instanceof AggregateError
4608 ) {
4609 const errors: ReactClientValue = error.errors as any;
4610 const errorsId = debug
4611 ? outlineDebugModel(request, {objectLimit: 5}, errors)
4612 : outlineModel(request, errors);
4613 errorsReference = serializeByValueID(errorsId);
4614 }
4615 } else if (typeof error === 'object' && error !== null) {
4616 message = describeObjectForErrorMessage(error);
4617 stack = [];
4618 } else {
4619 // eslint-disable-next-line react-internal/safe-string-coercion
4620 message = String(error);
4621 stack = [];
4622 }
4623 } catch (x) {
4624 message = 'An error occurred but serializing the error message failed.';
4625 stack = [];
4626 }
4627 const ownerRef =
4628 owner == null ? null : outlineComponentInfo(request, owner);
4629 errorInfo = {digest, name, message, stack, env, owner: ownerRef};
4630 if (causeReference !== null) {
4631 (errorInfo as ReactErrorInfoDev).cause = causeReference;
4632 }
4633 if (errorsReference !== null) {
4634 (errorInfo as ReactErrorInfoDev).errors = errorsReference;
4635 }
4636 } else {
4637 errorInfo = {digest};
4638 }
4639 const row = serializeRowHeader('E', id) + stringify(errorInfo) + '\n';
4640 const processedChunk = stringToChunk(row);
4641 if (__DEV__ && debug) {
4642 request.completedDebugChunks.push(processedChunk);
4643 } else {
4644 request.completedErrorChunks.push(processedChunk);
4645 }
4646 }
4647
4648 // Null on the debug channel, which can't reference rows in the main stream.
4649 let importStringRequest: null | Request = null;
4650
4651 function importMetadataReplacer(key: string, value: mixed): mixed {
4652 if (typeof value === 'string') {
4653 const request = importStringRequest;
4654 if (request === null) {
4655 return escapeStringValue(value);
4656 }
4657 return serializeImportString(request, value);
4658 }
4659 return value;
4660 }
4661
4662 function stringifyImportMetadataWithReplacer(
4663 request: Request,
4664 clientReferenceMetadata: ClientReferenceMetadata,
4665 debug: boolean,
4666 ): string {
4667 const prevRequest = importStringRequest;
4668 importStringRequest = __DEV__ && debug ? null : request;
4669 try {
4670 // $FlowFixMe[incompatible-type] stringify can return null
4671 return stringify(clientReferenceMetadata, importMetadataReplacer);
4672 } finally {
4673 importStringRequest = prevRequest;
4674 }
4675 }
4676
4677 // Bundler metadata is two or three levels deep. The bound is only there so a
4678 // cycle ends up in stringify itself, which throws its own error for it.
4679 const MAX_IMPORT_METADATA_DEPTH = 16;
4680
4681 const NOT_PLAIN_IMPORT_METADATA = {};
4682
4683 // Copies the metadata with every string replaced by its serialized form, so
4684 // that stringify can run without a replacer. Anything stringify would treat
4685 // specially (toJSON, boxed primitives, class instances) makes this give up
4686 // instead, because the copy would not reproduce that treatment.
4687 function transformImportMetadata(
4688 request: Request,
4689 value: mixed,
4690 depth: number,
4691 ): mixed {
4692 switch (typeof value) {
4693 case 'string':
4694 return serializeImportString(request, value);
4695 case 'number':
4696 case 'boolean':
4697 case 'undefined':
4698 return value;
4699 case 'object': {
4700 if (value === null) {
4701 return null;
4702 }
4703 if (depth > MAX_IMPORT_METADATA_DEPTH) {
4704 return NOT_PLAIN_IMPORT_METADATA;
4705 }
4706 if (typeof (value as any).toJSON === 'function') {
4707 return NOT_PLAIN_IMPORT_METADATA;
4708 }
4709 if (isArray(value)) {
4710 const length = value.length;
4711 const copy: Array<mixed> = new Array(length);
4712 for (let i = 0; i < length; i++) {
4713 const element = value[i];
4714 if (typeof element === 'string') {
4715 copy[i] = serializeImportString(request, element);
4716 continue;
4717 }
4718 const child = transformImportMetadata(request, element, depth + 1);
4719 if (child === NOT_PLAIN_IMPORT_METADATA) {
4720 return NOT_PLAIN_IMPORT_METADATA;
4721 }
4722 copy[i] = child;
4723 }
4724 return copy;
4725 }
4726 const proto = getPrototypeOf(value);
4727 if (proto !== ObjectPrototype && proto !== null) {
4728 return NOT_PLAIN_IMPORT_METADATA;
4729 }
4730 const keys = Object.keys(value);
4731 const copy: {[string]: mixed} = {};
4732 for (let i = 0; i < keys.length; i++) {
4733 const key = keys[i];
4734 if (key in ObjectPrototype) {
4735 // The copy inherits from Object.prototype, so assigning this key would
4736 // hit an accessor like __proto__ or, if the prototype is frozen, throw.
4737 return NOT_PLAIN_IMPORT_METADATA;
4738 }
4739 const element = (value as any)[key];
4740 if (typeof element === 'string') {
4741 copy[key] = serializeImportString(request, element);
4742 continue;
4743 }
4744 const child = transformImportMetadata(request, element, depth + 1);
4745 if (child === NOT_PLAIN_IMPORT_METADATA) {
4746 return NOT_PLAIN_IMPORT_METADATA;
4747 }
4748 copy[key] = child;
4749 }
4750 return copy;
4751 }
4752 default:
4753 return NOT_PLAIN_IMPORT_METADATA;
4754 }
4755 }
4756
4757 function stringifyImportMetadata(
4758 request: Request,
4759 clientReferenceMetadata: ClientReferenceMetadata,
4760 debug: boolean,
4761 ): string {
4762 if (!(__DEV__ && debug)) {
4763 const copy = transformImportMetadata(request, clientReferenceMetadata, 0);
4764 if (copy !== NOT_PLAIN_IMPORT_METADATA) {
4765 // $FlowFixMe[incompatible-type] stringify can return null
4766 return stringify(copy);
4767 }
4768 }
4769 return stringifyImportMetadataWithReplacer(
4770 request,
4771 clientReferenceMetadata,
4772 debug,
4773 );
4774 }
4775
4776 function emitImportChunk(
4777 request: Request,
4778 id: number,
4779 json: string,
4780 debug: boolean,
4781 ): void {
4782 const row = serializeRowHeader('I', id) + json + '\n';
4783 const processedChunk = stringToChunk(row);
4784 if (__DEV__ && debug) {
4785 request.completedDebugChunks.push(processedChunk);
4786 } else {
4787 request.completedImportChunks.push(processedChunk);
4788 }
4789 }
4790
4791 function emitHintChunk<Code: HintCode>(
4792 request: Request,
4793 code: Code,
4794 model: HintModel<Code>,
4795 ): void {
4796 const json: string = stringify(model);
4797 const row = ':H' + code + json + '\n';
4798 const processedChunk = stringToChunk(row);
4799 request.completedHintChunks.push(processedChunk);
4800 }
4801
4802 function emitSymbolChunk(request: Request, id: number, name: string): void {
4803 const symbolReference = serializeSymbolReference(name);
4804 const processedChunk = encodeReferenceChunk(request, id, symbolReference);
4805 request.completedImportChunks.push(processedChunk);
4806 }
4807
4808 function emitModelChunk(request: Request, id: number, json: string): void {
4809 const row = id.toString(16) + ':' + json + '\n';
4810 const processedChunk = stringToChunk(row);
4811 request.completedRegularChunks.push(processedChunk);
4812 }
4813
4814 function emitDebugHaltChunk(request: Request, id: number): void {
4815 if (!__DEV__) {
4816 // These errors should never make it into a build so we don't need to encode them in codes.json
4817 // eslint-disable-next-line react-internal/prod-error-codes
4818 throw new Error(
4819 'emitDebugHaltChunk should never be called in production mode. This is a bug in React.',
4820 );
4821 }
4822 // This emits a marker that this row will never complete and should intentionally never resolve
4823 // even when the client stream is closed. We use just the lack of data to indicate this.
4824 const row = id.toString(16) + ':\n';
4825 const processedChunk = stringToChunk(row);
4826 request.completedDebugChunks.push(processedChunk);
4827 }
4828
4829 function emitDebugChunk(
4830 request: Request,
4831 id: number,
4832 debugInfo: ReactDebugInfoEntry,
4833 ): void {
4834 if (!__DEV__) {
4835 // These errors should never make it into a build so we don't need to encode them in codes.json
4836 // eslint-disable-next-line react-internal/prod-error-codes
4837 throw new Error(
4838 'emitDebugChunk should never be called in production mode. This is a bug in React.',
4839 );
4840 }
4841
4842 const json: string = serializeDebugModel(request, 500, debugInfo);
4843 if (request.debugDestination !== null) {
4844 if (json[0] === '"' && json[1] === '$') {
4845 // This is already an outlined reference so we can just emit it directly,
4846 // without an unnecessary indirection.
4847 const row = serializeRowHeader('D', id) + json + '\n';
4848 request.completedRegularChunks.push(stringToChunk(row));
4849 } else {
4850 // Outline the debug information to the debug channel.
4851 const outlinedId = request.nextChunkId++;
4852 const debugRow = outlinedId.toString(16) + ':' + json + '\n';
4853 request.pendingDebugChunks++;
4854 request.completedDebugChunks.push(stringToChunk(debugRow));
4855 const row =
4856 serializeRowHeader('D', id) + '"$' + outlinedId.toString(16) + '"\n';
4857 request.completedRegularChunks.push(stringToChunk(row));
4858 }
4859 } else {
4860 const row = serializeRowHeader('D', id) + json + '\n';
4861 request.completedRegularChunks.push(stringToChunk(row));
4862 }
4863 }
4864
4865 function outlineComponentInfo(
4866 request: Request,
4867 componentInfo: ReactComponentInfo,
4868 ): string {
4869 if (!__DEV__) {
4870 // These errors should never make it into a build so we don't need to encode them in codes.json
4871 // eslint-disable-next-line react-internal/prod-error-codes
4872 throw new Error(
4873 'outlineComponentInfo should never be called in production mode. This is a bug in React.',
4874 );
4875 }
4876
4877 const existingRef = request.writtenDebugObjects.get(componentInfo);
4878 if (existingRef !== undefined) {
4879 // Already written
4880 return existingRef;
4881 }
4882
4883 if (componentInfo.owner != null) {
4884 // Ensure the owner is already outlined.
4885 outlineComponentInfo(request, componentInfo.owner);
4886 }
4887
4888 // Limit the number of objects we write to prevent emitting giant props objects.
4889 let objectLimit = 10;
4890 if (componentInfo.stack != null) {
4891 // Ensure we have enough object limit to encode the stack trace.
4892 objectLimit += componentInfo.stack.length;
4893 }
4894
4895 // We use the console encoding so that we can dedupe objects but don't necessarily
4896 // use the full serialization that requires a task.
4897 const counter = {objectLimit};
4898
4899 // We can't serialize the ConsoleTask/Error objects so we need to omit them before serializing.
4900 const componentDebugInfo: Omit<
4901 ReactComponentInfo,
4902 'debugTask' | 'debugStack',
4903 > = {
4904 name: componentInfo.name,
4905 key: componentInfo.key,
4906 };
4907 if (componentInfo.env != null) {
4908 // $FlowFixMe[cannot-write]
4909 componentDebugInfo.env = componentInfo.env;
4910 }
4911 if (componentInfo.owner != null) {
4912 // $FlowFixMe[cannot-write]
4913 componentDebugInfo.owner = componentInfo.owner;
4914 }
4915 if (componentInfo.stack == null && componentInfo.debugStack != null) {
4916 // If we have a debugStack but no parsed stack we should parse it.
4917 // $FlowFixMe[cannot-write]
4918 componentDebugInfo.stack = filterStackTrace(
4919 request,
4920 parseStackTrace(componentInfo.debugStack, 1),
4921 );
4922 } else if (componentInfo.stack != null) {
4923 // $FlowFixMe[cannot-write]
4924 componentDebugInfo.stack = componentInfo.stack;
4925 }
4926 // Ensure we serialize props after the stack to favor the stack being complete.
4927 // $FlowFixMe[cannot-write]
4928 componentDebugInfo.props = componentInfo.props;
4929
4930 const id = outlineDebugModel(request, counter, componentDebugInfo);
4931 const ref = serializeByValueID(id);
4932 request.writtenDebugObjects.set(componentInfo, ref);
4933 // We also store this in the main dedupe set so that it can be referenced by inline React Elements.
4934 request.writtenObjects.set(componentInfo, ref);
4935 return ref;
4936 }
4937
4938 function emitIOInfoChunk(
4939 request: Request,
4940 id: number,
4941 name: string,
4942 start: number,
4943 end: number,
4944 value: ?Promise<mixed>,
4945 env: ?string,
4946 owner: ?ReactComponentInfo,
4947 stack: ?ReactStackTrace,
4948 ): void {
4949 if (!__DEV__) {
4950 // These errors should never make it into a build so we don't need to encode them in codes.json
4951 // eslint-disable-next-line react-internal/prod-error-codes
4952 throw new Error(
4953 'emitIOInfoChunk should never be called in production mode. This is a bug in React.',
4954 );
4955 }
4956
4957 let objectLimit = 10;
4958 if (stack) {
4959 objectLimit += stack.length;
4960 }
4961
4962 const relativeStartTimestamp = start - request.timeOrigin;
4963 const relativeEndTimestamp = end - request.timeOrigin;
4964 const debugIOInfo: Omit<ReactIOInfo, 'debugTask' | 'debugStack'> = {
4965 name: name,
4966 start: relativeStartTimestamp,
4967 end: relativeEndTimestamp,
4968 };
4969 if (env != null) {
4970 // $FlowFixMe[cannot-write]
4971 debugIOInfo.env = env;
4972 }
4973 if (stack != null) {
4974 // $FlowFixMe[cannot-write]
4975 debugIOInfo.stack = stack;
4976 }
4977 if (owner != null) {
4978 // $FlowFixMe[cannot-write]
4979 debugIOInfo.owner = owner;
4980 }
4981 if (value !== undefined) {
4982 // $FlowFixMe[cannot-write]
4983 debugIOInfo.value = value;
4984 }
4985 const json: string = serializeDebugModel(request, objectLimit, debugIOInfo);
4986 const row = id.toString(16) + ':J' + json + '\n';
4987 const processedChunk = stringToChunk(row);
4988 request.completedDebugChunks.push(processedChunk);
4989 }
4990
4991 function outlineIOInfo(request: Request, ioInfo: ReactIOInfo): void {
4992 if (request.writtenObjects.has(ioInfo)) {
4993 // Already written
4994 return;
4995 }
4996 // We can't serialize the ConsoleTask/Error objects so we need to omit them before serializing.
4997 request.pendingDebugChunks++;
4998 const id = request.nextChunkId++;
4999 const owner = ioInfo.owner;
5000 // Ensure the owner is already outlined.
Showing first 5,000 of 7,047 lines. View raw