25
close,
26
processModelChunk,
27
processModuleChunk,
28
+ processSymbolChunk,
29
processErrorChunk,
30
resolveModuleMetaData,
31
isModuleReference,
33
34
import {
35
REACT_ELEMENT_TYPE,
35
- REACT_DEBUG_TRACING_MODE_TYPE,
36
REACT_FORWARD_REF_TYPE,
37
REACT_FRAGMENT_TYPE,
38
REACT_LAZY_TYPE,
39
- REACT_LEGACY_HIDDEN_TYPE,
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,
40
} from 'shared/ReactSymbols';
41
49
-import * as React from 'react';
42
import ReactSharedInternals from 'shared/ReactSharedInternals';
43
import invariant from 'shared/invariant';
44
78
completedModuleChunks: Array<Chunk>,
79
completedJSONChunks: Array<Chunk>,
80
completedErrorChunks: Array<Chunk>,
81
+ writtenSymbols: Map<Symbol, number>,
82
flowing: boolean,
83
toJSON: (key: string, value: ReactModel) => ReactJSONValue,
84
};
100
completedModuleChunks: [],
101
completedJSONChunks: [],
102
completedErrorChunks: [],
103
+ writtenSymbols: new Map(),
104
flowing: false,
105
toJSON: function(key: string, value: ReactModel): ReactJSONValue {
106
return resolveModelToJSON(request, this, key, value);
112
return request;
113
}
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) {
122
// When the ref moves to the regular props object this will implicitly
123
// throw for functions. We could probably relax it to a DEV warning for other
124
// cases.
132
return type(props);
133
} else if (typeof type === 'string') {
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];
147
} else if (type != null && typeof type === 'object') {
148
if (isModuleReference(type)) {
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];
151
}
152
switch (type.$$typeof) {
153
case REACT_FORWARD_REF_TYPE: {
155
return render(props, undefined);
156
}
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);
159
}
160
}
161
}
392
const element: React$Element<any> = (value: any);
393
try {
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
+ );
401
} catch (x) {
402
if (typeof x === 'object' && x !== null && typeof x.then === 'function') {
403
// Something suspended, we'll need to create a new segment and resolve it later.
524
}
525
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;
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. ' +
537
'Remove %s from this object, or avoid the entire object: %s',
538
value.description,
539
describeKeyForErrorMessage(key),
540
describeObjectForErrorMessage(parent),
541
);
542
+ request.pendingChunks++;
543
+ const symbolId = request.nextChunkId++;
544
+ emitSymbolChunk(request, symbolId, name);
545
+ writtenSymbols.set(value, symbolId);
546
+ return serializeByValueID(symbolId);
547
}
548
549
// $FlowFixMe: bigint isn't added to Flow yet.
598
request.completedModuleChunks.push(processedChunk);
599
}
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
+
606
function retrySegment(request: Request, segment: Segment): void {
607
const query = segment.query;
608
let value;
619
// Doing this here lets us reuse this same segment if the next component
620
// also suspends.
621
segment.query = () => value;
607
- value = attemptResolveElement(element);
622
+ value = attemptResolveElement(
623
+ element.type,
624
+ element.key,
625
+ element.ref,
626
+ element.props,
627
+ );
628
}
629
const processedChunk = processModelChunk(request, segment.id, value);
630
request.completedJSONChunks.push(processedChunk);