15
enableBinaryFlight,
16
enablePostpone,
17
enableTaint,
18
+ enableServerContext,
19
} from 'shared/ReactFeatureFlags';
20
21
import {
181
thenableState: ThenableState | null,
182
};
183
184
+interface Reference {}
185
+
186
export type Request = {
187
status: 0 | 1 | 2,
188
flushScheduled: boolean,
203
writtenClientReferences: Map<ClientReferenceKey, number>,
204
writtenServerReferences: Map<ServerReference<any>, number>,
205
writtenProviders: Map<string, number>,
206
+ writtenObjects: WeakMap<Reference, number>, // -1 means "seen" but not outlined.
207
identifierPrefix: string,
208
identifierCount: number,
209
taintCleanupQueue: Array<string | bigint>,
302
writtenClientReferences: new Map(),
303
writtenServerReferences: new Map(),
304
writtenProviders: new Map(),
305
+ writtenObjects: new WeakMap(),
306
identifierPrefix: identifierPrefix || '',
307
identifierCount: 1,
308
taintCleanupQueue: cleanupQueue,
586
);
587
}
588
case REACT_PROVIDER_TYPE: {
584
- pushProvider(type._context, props.value);
585
- if (__DEV__) {
586
- const extraKeys = Object.keys(props).filter(value => {
587
- if (value === 'children' || value === 'value') {
588
- return false;
589
+ if (enableServerContext) {
590
+ pushProvider(type._context, props.value);
591
+ if (__DEV__) {
592
+ const extraKeys = Object.keys(props).filter(value => {
593
+ if (value === 'children' || value === 'value') {
594
+ return false;
595
+ }
596
+ return true;
597
+ });
598
+ if (extraKeys.length !== 0) {
599
+ console.error(
600
+ 'ServerContext can only have a value prop and children. Found: %s',
601
+ JSON.stringify(extraKeys),
602
+ );
603
}
590
- return true;
591
- });
592
- if (extraKeys.length !== 0) {
593
- console.error(
594
- 'ServerContext can only have a value prop and children. Found: %s',
595
- JSON.stringify(extraKeys),
596
- );
604
}
605
+ return [
606
+ REACT_ELEMENT_TYPE,
607
+ type,
608
+ key,
609
+ // Rely on __popProvider being serialized last to pop the provider.
610
+ {value: props.value, children: props.children, __pop: POP},
611
+ ];
612
}
599
- return [
600
- REACT_ELEMENT_TYPE,
601
- type,
602
- key,
603
- // Rely on __popProvider being serialized last to pop the provider.
604
- {value: props.value, children: props.children, __pop: POP},
605
- ];
613
+ // Fallthrough
614
}
615
}
616
}
767
768
function outlineModel(request: Request, value: any): number {
769
request.pendingChunks++;
762
- const outlinedId = request.nextChunkId++;
763
- // We assume that this object doesn't suspend, but a child might.
764
- emitModelChunk(request, outlinedId, value);
765
- return outlinedId;
770
+ const newTask = createTask(
771
+ request,
772
+ value,
773
+ getActiveContext(),
774
+ request.abortableTasks,
775
+ );
776
+ retryTask(request, newTask);
777
+ return newTask.id;
778
}
779
780
function serializeServerReference(
822
request: Request,
823
map: Map<ReactClientValue, ReactClientValue>,
824
): string {
813
- const id = outlineModel(request, Array.from(map));
825
+ const entries = Array.from(map);
826
+ for (let i = 0; i < entries.length; i++) {
827
+ const key = entries[i][0];
828
+ if (typeof key === 'object' && key !== null) {
829
+ const writtenObjects = request.writtenObjects;
830
+ const existingId = writtenObjects.get(key);
831
+ if (existingId === undefined) {
832
+ // Mark all object keys as seen so that they're always outlined.
833
+ writtenObjects.set(key, -1);
834
+ }
835
+ }
836
+ }
837
+ const id = outlineModel(request, entries);
838
return '$Q' + id.toString(16);
839
}
840
841
function serializeSet(request: Request, set: Set<ReactClientValue>): string {
818
- const id = outlineModel(request, Array.from(set));
842
+ const entries = Array.from(set);
843
+ for (let i = 0; i < entries.length; i++) {
844
+ const key = entries[i];
845
+ if (typeof key === 'object' && key !== null) {
846
+ const writtenObjects = request.writtenObjects;
847
+ const existingId = writtenObjects.get(key);
848
+ if (existingId === undefined) {
849
+ // Mark all object keys as seen so that they're always outlined.
850
+ writtenObjects.set(key, -1);
851
+ }
852
+ }
853
+ }
854
+ const id = outlineModel(request, entries);
855
return '$W' + id.toString(16);
856
}
857
896
897
let insideContextProps = null;
898
let isInsideContextValue = false;
899
+let modelRoot: null | ReactClientValue = false;
900
901
function resolveModelToJSON(
902
request: Request,
950
951
if (__DEV__) {
952
if (
953
+ enableServerContext &&
954
parent[0] === REACT_ELEMENT_TYPE &&
955
parent[1] &&
956
(parent[1]: any).$$typeof === REACT_PROVIDER_TYPE &&
972
(value: any).$$typeof === REACT_LAZY_TYPE)
973
) {
974
if (__DEV__) {
937
- if (isInsideContextValue) {
975
+ if (enableServerContext && isInsideContextValue) {
976
console.error('React elements are not allowed in ServerContext');
977
}
978
}
980
try {
981
switch ((value: any).$$typeof) {
982
case REACT_ELEMENT_TYPE: {
983
+ const writtenObjects = request.writtenObjects;
984
+ const existingId = writtenObjects.get(value);
985
+ if (existingId !== undefined) {
986
+ if (existingId === -1) {
987
+ // Seen but not yet outlined.
988
+ const newId = outlineModel(request, value);
989
+ return serializeByValueID(newId);
990
+ } else if (modelRoot === value) {
991
+ // This is the ID we're currently emitting so we need to write it
992
+ // once but if we discover it again, we refer to it by id.
993
+ modelRoot = null;
994
+ } else {
995
+ // We've already emitted this as an outlined object, so we can
996
+ // just refer to that by its existing ID.
997
+ return serializeByValueID(existingId);
998
+ }
999
+ } else {
1000
+ // This is the first time we've seen this object. We may never see it again
1001
+ // so we'll inline it. Mark it as seen. If we see it again, we'll outline.
1002
+ writtenObjects.set(value, -1);
1003
+ }
1004
+
1005
// TODO: Concatenate keys of parents onto children.
1006
const element: React$Element<any> = (value: any);
1007
// Attempt to render the Server Component.
1082
}
1083
if (isClientReference(value)) {
1084
return serializeClientReference(request, parent, key, (value: any));
1025
- // $FlowFixMe[method-unbinding]
1026
- } else if (typeof value.then === 'function') {
1085
+ }
1086
+
1087
+ const writtenObjects = request.writtenObjects;
1088
+ const existingId = writtenObjects.get(value);
1089
+ // $FlowFixMe[method-unbinding]
1090
+ if (typeof value.then === 'function') {
1091
+ if (existingId !== undefined) {
1092
+ if (modelRoot === value) {
1093
+ // This is the ID we're currently emitting so we need to write it
1094
+ // once but if we discover it again, we refer to it by id.
1095
+ modelRoot = null;
1096
+ } else {
1097
+ // We've seen this promise before, so we can just refer to the same result.
1098
+ return serializePromiseID(existingId);
1099
+ }
1100
+ }
1101
// We assume that any object with a .then property is a "Thenable" type,
1102
// or a Promise type. Either of which can be represented by a Promise.
1103
const promiseId = serializeThenable(request, (value: any));
1104
+ writtenObjects.set(value, promiseId);
1105
return serializePromiseID(promiseId);
1031
- } else if ((value: any).$$typeof === REACT_PROVIDER_TYPE) {
1032
- const providerKey = ((value: any): ReactProviderType<any>)._context
1033
- ._globalName;
1034
- const writtenProviders = request.writtenProviders;
1035
- let providerId = writtenProviders.get(key);
1036
- if (providerId === undefined) {
1037
- request.pendingChunks++;
1038
- providerId = request.nextChunkId++;
1039
- writtenProviders.set(providerKey, providerId);
1040
- emitProviderChunk(request, providerId, providerKey);
1106
+ }
1107
+
1108
+ if (enableServerContext) {
1109
+ if ((value: any).$$typeof === REACT_PROVIDER_TYPE) {
1110
+ const providerKey = ((value: any): ReactProviderType<any>)._context
1111
+ ._globalName;
1112
+ const writtenProviders = request.writtenProviders;
1113
+ let providerId = writtenProviders.get(key);
1114
+ if (providerId === undefined) {
1115
+ request.pendingChunks++;
1116
+ providerId = request.nextChunkId++;
1117
+ writtenProviders.set(providerKey, providerId);
1118
+ emitProviderChunk(request, providerId, providerKey);
1119
+ }
1120
+ return serializeByValueID(providerId);
1121
+ } else if (value === POP) {
1122
+ popProvider();
1123
+ if (__DEV__) {
1124
+ insideContextProps = null;
1125
+ isInsideContextValue = false;
1126
+ }
1127
+ return (undefined: any);
1128
}
1042
- return serializeByValueID(providerId);
1043
- } else if (value === POP) {
1044
- popProvider();
1045
- if (__DEV__) {
1046
- insideContextProps = null;
1047
- isInsideContextValue = false;
1129
+ }
1130
+
1131
+ if (existingId !== undefined) {
1132
+ if (existingId === -1) {
1133
+ // Seen but not yet outlined.
1134
+ const newId = outlineModel(request, value);
1135
+ return serializeByValueID(newId);
1136
+ } else if (modelRoot === value) {
1137
+ // This is the ID we're currently emitting so we need to write it
1138
+ // once but if we discover it again, we refer to it by id.
1139
+ modelRoot = null;
1140
+ } else {
1141
+ // We've already emitted this as an outlined object, so we can
1142
+ // just refer to that by its existing ID.
1143
+ return serializeByValueID(existingId);
1144
}
1049
- return (undefined: any);
1145
+ } else {
1146
+ // This is the first time we've seen this object. We may never see it again
1147
+ // so we'll inline it. Mark it as seen. If we see it again, we'll outline.
1148
+ writtenObjects.set(value, -1);
1149
}
1150
1151
if (isArray(value)) {
1500
id: number,
1501
model: ReactClientValue,
1502
): void {
1503
+ // Track the root so we know that we have to emit this object even though it
1504
+ // already has an ID. This is needed because we might see this object twice
1505
+ // in the same toJSON if it is cyclic.
1506
+ modelRoot = model;
1507
// $FlowFixMe[incompatible-type] stringify can return null
1508
const json: string = stringify(model, request.toJSON);
1509
const row = id.toString(16) + ':' + json + '\n';
1525
value !== null &&
1526
(value: any).$$typeof === REACT_ELEMENT_TYPE
1527
) {
1528
+ request.writtenObjects.set(value, task.id);
1529
+
1530
// TODO: Concatenate keys of parents onto children.
1531
const element: React$Element<any> = (value: any);
1532
1559
value !== null &&
1560
(value: any).$$typeof === REACT_ELEMENT_TYPE
1561
) {
1562
+ request.writtenObjects.set(value, task.id);
1563
// TODO: Concatenate keys of parents onto children.
1564
const nextElement: React$Element<any> = (value: any);
1565
task.model = value;
1574
}
1575
}
1576
1577
+ // Track that this object is outlined and has an id.
1578
+ if (typeof value === 'object' && value !== null) {
1579
+ request.writtenObjects.set(value, task.id);
1580
+ }
1581
+
1582
emitModelChunk(request, task.id, value);
1583
request.abortableTasks.delete(task);
1584
task.status = COMPLETED;
1814
function importServerContexts(
1815
contexts?: Array<[string, ServerContextJSONValue]>,
1816
) {
1706
- if (contexts) {
1817
+ if (enableServerContext && contexts) {
1818
const prevContext = getActiveContext();
1819
switchContext(rootContextSnapshot);
1820
for (let i = 0; i < contexts.length; i++) {