7
* @flow
8
*/
9
10
-import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
10
import type {
11
Destination,
12
Chunk,
15
ModuleReference,
16
ModuleKey,
17
} from './ReactFlightServerConfig';
18
+import type {ContextSnapshot} from './ReactFlightNewContext';
19
+import type {
20
+ ReactProviderType,
21
+ ServerContextJSONValue,
22
+} from 'shared/ReactTypes';
23
24
import {
25
scheduleWork,
31
closeWithError,
32
processModelChunk,
33
processModuleChunk,
34
+ processProviderChunk,
35
processSymbolChunk,
36
processErrorChunk,
37
resolveModuleMetaData,
39
isModuleReference,
40
} from './ReactFlightServerConfig';
41
42
+import {Dispatcher, getCurrentCache, setCurrentCache} from './ReactFlightHooks';
43
+import {
44
+ pushProvider,
45
+ popProvider,
46
+ switchContext,
47
+ getActiveContext,
48
+ rootContextSnapshot,
49
+} from './ReactFlightNewContext';
50
+
51
import {
52
REACT_ELEMENT_TYPE,
53
REACT_FORWARD_REF_TYPE,
54
REACT_FRAGMENT_TYPE,
55
REACT_LAZY_TYPE,
56
REACT_MEMO_TYPE,
57
+ REACT_PROVIDER_TYPE,
58
} from 'shared/ReactSymbols';
59
60
+import {getOrCreateServerContext} from 'shared/ReactServerContextRegistry';
61
import ReactSharedInternals from 'shared/ReactSharedInternals';
62
import isArray from 'shared/isArray';
63
84
id: number,
85
model: ReactModel,
86
ping: () => void,
87
+ context: ContextSnapshot,
88
};
89
90
export type Request = {
101
completedErrorChunks: Array<Chunk>,
102
writtenSymbols: Map<Symbol, number>,
103
writtenModules: Map<ModuleKey, number>,
104
+ writtenProviders: Map<string, number>,
105
onError: (error: mixed) => void,
106
toJSON: (key: string, value: ReactModel) => ReactJSONValue,
107
};
108
109
+export type Options = {
110
+ onError?: (error: mixed) => void,
111
+};
112
+
113
const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
114
115
function defaultErrorHandler(error: mixed) {
125
model: ReactModel,
126
bundlerConfig: BundlerConfig,
127
onError: void | ((error: mixed) => void),
128
+ context?: Array<[string, ServerContextJSONValue]>,
129
): Request {
130
const pingedSegments = [];
131
const request = {
142
completedErrorChunks: [],
143
writtenSymbols: new Map(),
144
writtenModules: new Map(),
145
+ writtenProviders: new Map(),
146
onError: onError === undefined ? defaultErrorHandler : onError,
147
toJSON: function(key: string, value: ReactModel): ReactJSONValue {
148
return resolveModelToJSON(request, this, key, value);
149
},
150
};
151
request.pendingChunks++;
128
- const rootSegment = createSegment(request, model);
152
+ const rootContext = createRootContext(context);
153
+ const rootSegment = createSegment(request, model, rootContext);
154
pingedSegments.push(rootSegment);
155
return request;
156
}
157
158
+function createRootContext(
159
+ reqContext?: Array<[string, ServerContextJSONValue]>,
160
+) {
161
+ return importServerContexts(reqContext);
162
+}
163
+
164
+const POP = {};
165
+
166
function attemptResolveElement(
167
type: any,
168
key: null | React$Key,
207
case REACT_MEMO_TYPE: {
208
return attemptResolveElement(type.type, key, ref, props);
209
}
210
+ case REACT_PROVIDER_TYPE: {
211
+ pushProvider(type._context, props.value);
212
+ if (__DEV__) {
213
+ const extraKeys = Object.keys(props).filter(value => {
214
+ if (value === 'children' || value === 'value') {
215
+ return false;
216
+ }
217
+ return true;
218
+ });
219
+ if (extraKeys.length !== 0) {
220
+ console.error(
221
+ 'ServerContext can only have a value prop and children. Found: %s',
222
+ JSON.stringify(extraKeys),
223
+ );
224
+ }
225
+ }
226
+ return [
227
+ REACT_ELEMENT_TYPE,
228
+ type,
229
+ key,
230
+ // Rely on __popProvider being serialized last to pop the provider.
231
+ {value: props.value, children: props.children, __pop: POP},
232
+ ];
233
+ }
234
}
235
}
236
throw new Error(
246
}
247
}
248
192
-function createSegment(request: Request, model: ReactModel): Segment {
249
+function createSegment(
250
+ request: Request,
251
+ model: ReactModel,
252
+ context: ContextSnapshot,
253
+): Segment {
254
const id = request.nextChunkId++;
255
const segment = {
256
id,
257
model,
258
+ context,
259
ping: () => pingSegment(request, segment),
260
};
261
return segment;
283
if (!object) {
284
return false;
285
}
224
- // $FlowFixMe
286
const ObjectPrototype = Object.prototype;
287
if (object === ObjectPrototype) {
288
return true;
372
): string {
373
if (isArray(objectOrArray)) {
374
let str = '[';
314
- // $FlowFixMe: Should be refined by now.
375
const array: $ReadOnlyArray<ReactModel> = objectOrArray;
376
for (let i = 0; i < array.length; i++) {
377
if (i > 0) {
396
return str;
397
} else {
398
let str = '{';
339
- // $FlowFixMe: Should be refined by now.
399
const object: {+[key: string | number]: ReactModel} = objectOrArray;
400
const names = Object.keys(object);
401
for (let i = 0; i < names.length; i++) {
424
}
425
}
426
427
+let insideContextProps = null;
428
+let isInsideContextValue = false;
429
+
430
export function resolveModelToJSON(
431
request: Request,
432
parent: {+[key: string | number]: ReactModel} | $ReadOnlyArray<ReactModel>,
458
);
459
}
460
461
+ if (__DEV__) {
462
+ if (
463
+ parent[0] === REACT_ELEMENT_TYPE &&
464
+ parent[1] &&
465
+ parent[1].$$typeof === REACT_PROVIDER_TYPE &&
466
+ key === '3'
467
+ ) {
468
+ insideContextProps = value;
469
+ } else if (insideContextProps === parent && key === 'value') {
470
+ isInsideContextValue = true;
471
+ } else if (insideContextProps === parent && key === 'children') {
472
+ isInsideContextValue = false;
473
+ }
474
+ }
475
+
476
// Resolve server components.
477
while (
478
typeof value === 'object' &&
479
value !== null &&
403
- value.$$typeof === REACT_ELEMENT_TYPE
480
+ (value: any).$$typeof === REACT_ELEMENT_TYPE
481
) {
482
+ if (__DEV__) {
483
+ if (isInsideContextValue) {
484
+ console.error('React elements are not allowed in ServerContext');
485
+ }
486
+ }
487
// TODO: Concatenate keys of parents onto children.
488
const element: React$Element<any> = (value: any);
489
try {
498
if (typeof x === 'object' && x !== null && typeof x.then === 'function') {
499
// Something suspended, we'll need to create a new segment and resolve it later.
500
request.pendingChunks++;
419
- const newSegment = createSegment(request, value);
501
+ const newSegment = createSegment(request, value, getActiveContext());
502
const ping = newSegment.ping;
503
x.then(ping, ping);
504
return serializeByRefID(newSegment.id);
560
emitErrorChunk(request, errorId, x);
561
return serializeByValueID(errorId);
562
}
563
+ } else if ((value: any).$$typeof === REACT_PROVIDER_TYPE) {
564
+ const providerKey = ((value: any): ReactProviderType<any>)._context
565
+ ._globalName;
566
+ const writtenProviders = request.writtenProviders;
567
+ let providerId = writtenProviders.get(key);
568
+ if (providerId === undefined) {
569
+ request.pendingChunks++;
570
+ providerId = request.nextChunkId++;
571
+ writtenProviders.set(providerKey, providerId);
572
+ emitProviderChunk(request, providerId, providerKey);
573
+ }
574
+ return serializeByValueID(providerId);
575
+ } else if (value === POP) {
576
+ popProvider();
577
+ if (__DEV__) {
578
+ insideContextProps = null;
579
+ isInsideContextValue = false;
580
+ }
581
+ return (undefined: any);
582
}
583
584
if (__DEV__) {
616
}
617
}
618
}
619
+
620
return value;
621
}
622
759
request.completedModuleChunks.push(processedChunk);
760
}
761
762
+function emitProviderChunk(
763
+ request: Request,
764
+ id: number,
765
+ contextName: string,
766
+): void {
767
+ const processedChunk = processProviderChunk(request, id, contextName);
768
+ request.completedJSONChunks.push(processedChunk);
769
+}
770
+
771
function retrySegment(request: Request, segment: Segment): void {
772
+ switchContext(segment.context);
773
try {
774
let value = segment.model;
775
while (
776
typeof value === 'object' &&
777
value !== null &&
666
- value.$$typeof === REACT_ELEMENT_TYPE
778
+ (value: any).$$typeof === REACT_ELEMENT_TYPE
779
) {
780
// TODO: Concatenate keys of parents onto children.
781
const element: React$Element<any> = (value: any);
808
809
function performWork(request: Request): void {
810
const prevDispatcher = ReactCurrentDispatcher.current;
699
- const prevCache = currentCache;
811
+ const prevCache = getCurrentCache();
812
ReactCurrentDispatcher.current = Dispatcher;
701
- currentCache = request.cache;
813
+ setCurrentCache(request.cache);
814
815
try {
816
const pingedSegments = request.pingedSegments;
827
fatalError(request, error);
828
} finally {
829
ReactCurrentDispatcher.current = prevDispatcher;
718
- currentCache = prevCache;
830
+ setCurrentCache(prevCache);
831
}
832
}
833
918
}
919
}
920
809
-function unsupportedHook(): void {
810
- throw new Error('This Hook is not supported in Server Components.');
811
-}
812
-
813
-function unsupportedRefresh(): void {
814
- if (!currentCache) {
815
- throw new Error(
816
- 'Refreshing the cache is not supported in Server Components.',
817
- );
921
+function importServerContexts(
922
+ contexts?: Array<[string, ServerContextJSONValue]>,
923
+) {
924
+ if (contexts) {
925
+ const prevContext = getActiveContext();
926
+ switchContext(rootContextSnapshot);
927
+ for (let i = 0; i < contexts.length; i++) {
928
+ const [name, value] = contexts[i];
929
+ const context = getOrCreateServerContext(name);
930
+ pushProvider(context, value);
931
+ }
932
+ const importedContext = getActiveContext();
933
+ switchContext(prevContext);
934
+ return importedContext;
935
}
936
+ return rootContextSnapshot;
937
}
820
-
821
-let currentCache: Map<Function, mixed> | null = null;
822
-
823
-const Dispatcher: DispatcherType = {
824
- useMemo<T>(nextCreate: () => T): T {
825
- return nextCreate();
826
- },
827
- useCallback<T>(callback: T): T {
828
- return callback;
829
- },
830
- useDebugValue(): void {},
831
- useDeferredValue: (unsupportedHook: any),
832
- useTransition: (unsupportedHook: any),
833
- getCacheForType<T>(resourceType: () => T): T {
834
- if (!currentCache) {
835
- throw new Error('Reading the cache is only supported while rendering.');
836
- }
837
-
838
- let entry: T | void = (currentCache.get(resourceType): any);
839
- if (entry === undefined) {
840
- entry = resourceType();
841
- // TODO: Warn if undefined?
842
- currentCache.set(resourceType, entry);
843
- }
844
- return entry;
845
- },
846
- readContext: (unsupportedHook: any),
847
- useContext: (unsupportedHook: any),
848
- useReducer: (unsupportedHook: any),
849
- useRef: (unsupportedHook: any),
850
- useState: (unsupportedHook: any),
851
- useInsertionEffect: (unsupportedHook: any),
852
- useLayoutEffect: (unsupportedHook: any),
853
- useImperativeHandle: (unsupportedHook: any),
854
- useEffect: (unsupportedHook: any),
855
- useId: (unsupportedHook: any),
856
- useMutableSource: (unsupportedHook: any),
857
- useSyncExternalStore: (unsupportedHook: any),
858
- useCacheRefresh(): <T>(?() => T, ?T) => void {
859
- return unsupportedRefresh;
860
- },
861
-};