@@ -25,28 +25,20 @@ import {
25
25
close ,
26
26
processModelChunk ,
27
27
processModuleChunk ,
28
+ processSymbolChunk ,
28
29
processErrorChunk ,
29
30
resolveModuleMetaData ,
30
31
isModuleReference ,
31
32
} from './ReactFlightServerConfig' ;
32
33
33
34
import {
34
35
REACT_ELEMENT_TYPE ,
35
- REACT_DEBUG_TRACING_MODE_TYPE ,
36
36
REACT_FORWARD_REF_TYPE ,
37
37
REACT_FRAGMENT_TYPE ,
38
38
REACT_LAZY_TYPE ,
39
- REACT_LEGACY_HIDDEN_TYPE ,
40
39
REACT_MEMO_TYPE ,
41
- REACT_OFFSCREEN_TYPE ,
42
- REACT_PROFILER_TYPE ,
43
- REACT_SCOPE_TYPE ,
44
- REACT_STRICT_MODE_TYPE ,
45
- REACT_SUSPENSE_TYPE ,
46
- REACT_SUSPENSE_LIST_TYPE ,
47
40
} from 'shared/ReactSymbols' ;
48
41
49
- import * as React from 'react' ;
50
42
import ReactSharedInternals from 'shared/ReactSharedInternals' ;
51
43
import invariant from 'shared/invariant' ;
52
44
@@ -86,6 +78,7 @@ export type Request = {
86
78
completedModuleChunks : Array < Chunk > ,
87
79
completedJSONChunks : Array < Chunk > ,
88
80
completedErrorChunks : Array < Chunk > ,
81
+ writtenSymbols : Map < Symbol , number> ,
89
82
flowing : boolean ,
90
83
toJSON : ( key : string , value : ReactModel ) => ReactJSONValue ,
91
84
} ;
@@ -107,6 +100,7 @@ export function createRequest(
107
100
completedModuleChunks : [ ] ,
108
101
completedJSONChunks : [ ] ,
109
102
completedErrorChunks : [ ] ,
103
+ writtenSymbols : new Map ( ) ,
110
104
flowing : false ,
111
105
toJSON : function ( key : string , value : ReactModel ) : ReactJSONValue {
112
106
return resolveModelToJSON ( request , this , key , value ) ;
@@ -118,10 +112,13 @@ export function createRequest(
118
112
return request ;
119
113
}
120
114
121
- function attemptResolveElement ( element : React$Element < any > ) : ReactModel {
122
- const type = element . type ;
123
- const props = element . props ;
124
- if ( element . ref !== null && element . ref !== undefined ) {
115
+ function attemptResolveElement (
116
+ type : any ,
117
+ key : null | React$Key ,
118
+ ref : mixed ,
119
+ props : any ,
120
+ ) : ReactModel {
121
+ if ( ref !== null && ref !== undefined ) {
125
122
// When the ref moves to the regular props object this will implicitly
126
123
// throw for functions. We could probably relax it to a DEV warning for other
127
124
// cases.
@@ -135,34 +132,30 @@ function attemptResolveElement(element: React$Element<any>): ReactModel {
135
132
return type ( props ) ;
136
133
} else if ( typeof type === 'string' ) {
137
134
// This is a host element. E.g. HTML.
138
- return [ REACT_ELEMENT_TYPE , type , element . key , element . props ] ;
139
- } else if (
140
- type === REACT_FRAGMENT_TYPE ||
141
- type === REACT_STRICT_MODE_TYPE ||
142
- type === REACT_PROFILER_TYPE ||
143
- type === REACT_SCOPE_TYPE ||
144
- type === REACT_DEBUG_TRACING_MODE_TYPE ||
145
- type === REACT_LEGACY_HIDDEN_TYPE ||
146
- type === REACT_OFFSCREEN_TYPE ||
147
- // TODO: These are temporary shims
148
- // and we'll want a different behavior.
149
- type === REACT_SUSPENSE_TYPE ||
150
- type === REACT_SUSPENSE_LIST_TYPE
151
- ) {
152
- return element . props . children ;
135
+ return [ REACT_ELEMENT_TYPE , type , key , props ] ;
136
+ } else if ( typeof type === 'symbol' ) {
137
+ if ( type === REACT_FRAGMENT_TYPE ) {
138
+ // For key-less fragments, we add a small optimization to avoid serializing
139
+ // it as a wrapper.
140
+ // TODO: If a key is specified, we should propagate its key to any children.
141
+ // Same as if a server component has a key.
142
+ return props . children ;
143
+ }
144
+ // This might be a built-in React component. We'll let the client decide.
145
+ // Any built-in works as long as its props are serializable.
146
+ return [ REACT_ELEMENT_TYPE , type , key , props ] ;
153
147
} else if ( type != null && typeof type === 'object' ) {
154
148
if ( isModuleReference ( type ) ) {
155
149
// This is a reference to a client component.
156
- return [ REACT_ELEMENT_TYPE , type , element . key , element . props ] ;
150
+ return [ REACT_ELEMENT_TYPE , type , key , props ] ;
157
151
}
158
152
switch ( type . $$typeof ) {
159
153
case REACT_FORWARD_REF_TYPE : {
160
154
const render = type . render ;
161
155
return render ( props , undefined ) ;
162
156
}
163
157
case REACT_MEMO_TYPE : {
164
- const nextChildren = React . createElement ( type . type , element . props ) ;
165
- return attemptResolveElement ( nextChildren ) ;
158
+ return attemptResolveElement ( type . type , key , ref , props ) ;
166
159
}
167
160
}
168
161
}
@@ -399,7 +392,12 @@ export function resolveModelToJSON(
399
392
const element : React$Element < any > = (value: any);
400
393
try {
401
394
// Attempt to render the server component.
402
- value = attemptResolveElement ( element ) ;
395
+ value = attemptResolveElement (
396
+ element . type ,
397
+ element . key ,
398
+ element . ref ,
399
+ element . props ,
400
+ ) ;
403
401
} catch (x) {
404
402
if ( typeof x === 'object' && x !== null && typeof x . then === 'function' ) {
405
403
// Something suspended, we'll need to create a new segment and resolve it later.
@@ -526,14 +524,26 @@ export function resolveModelToJSON(
526
524
}
527
525
528
526
if ( typeof value === 'symbol ') {
527
+ const writtenSymbols = request . writtenSymbols ;
528
+ const existingId = writtenSymbols . get ( value ) ;
529
+ if ( existingId !== undefined ) {
530
+ return serializeByValueID ( existingId ) ;
531
+ }
532
+ const name = value . description ;
529
533
invariant (
530
- false ,
531
- 'Symbol values (%s) cannot be passed to client components. ' +
534
+ Symbol . for ( name ) === value ,
535
+ 'Only global symbols received from Symbol.for(...) can be passed to client components. ' +
536
+ 'The symbol Symbol.for(%s) cannot be found among global symbols. ' +
532
537
'Remove %s from this object, or avoid the entire object: %s' ,
533
538
value . description ,
534
539
describeKeyForErrorMessage ( key ) ,
535
540
describeObjectForErrorMessage ( parent ) ,
536
541
) ;
542
+ request . pendingChunks ++ ;
543
+ const symbolId = request . nextChunkId ++ ;
544
+ emitSymbolChunk ( request , symbolId , name ) ;
545
+ writtenSymbols . set ( value , symbolId ) ;
546
+ return serializeByValueID ( symbolId ) ;
537
547
}
538
548
539
549
// $FlowFixMe: bigint isn't added to Flow yet.
@@ -588,6 +598,11 @@ function emitModuleChunk(
588
598
request . completedModuleChunks . push ( processedChunk ) ;
589
599
}
590
600
601
+ function emitSymbolChunk(request: Request, id: number, name: string): void {
602
+ const processedChunk = processSymbolChunk ( request , id , name ) ;
603
+ request . completedModuleChunks . push ( processedChunk ) ;
604
+ }
605
+
591
606
function retrySegment(request: Request, segment: Segment): void {
592
607
const query = segment . query ;
593
608
let value ;
@@ -604,7 +619,12 @@ function retrySegment(request: Request, segment: Segment): void {
604
619
// Doing this here lets us reuse this same segment if the next component
605
620
// also suspends.
606
621
segment . query = ( ) => value ;
607
- value = attemptResolveElement ( element ) ;
622
+ value = attemptResolveElement (
623
+ element . type ,
624
+ element . key ,
625
+ element . ref ,
626
+ element . props ,
627
+ ) ;
608
628
}
609
629
const processedChunk = processModelChunk ( request , segment . id , value ) ;
610
630
request . completedJSONChunks . push ( processedChunk ) ;
0 commit comments