Allow functions to be used as module references (#25137)
Sebastian Markbåge committed
Aug 25, 2022 at 07:47 UTC
d0f396651bd654a9c2058d9c779aab843651ca9a
1 file changed
+54
-39
packages/react-server/src/ReactFlightServer.js
+54
-39
@@ -195,6 +195,10 @@ function attemptResolveElement(
195
);
196
}
197
if (typeof type === 'function') {
198
+ if (isModuleReference(type)) {
199
+ // This is a reference to a client component.
200
+ return [REACT_ELEMENT_TYPE, type, key, props];
201
+ }
202
// This is a server-side component.
203
return type(props);
204
} else if (typeof type === 'string') {
@@ -295,6 +299,52 @@ function serializeByRefID(id: number): string {
299
return '@' + id.toString(16);
300
}
301
302
+function serializeModuleReference(
303
+ request: Request,
304
+ parent: {+[key: string | number]: ReactModel} | $ReadOnlyArray<ReactModel>,
305
+ key: string,
306
+ moduleReference: ModuleReference<any>,
307
+): string {
308
+ const moduleKey: ModuleKey = getModuleKey(moduleReference);
309
+ const writtenModules = request.writtenModules;
310
+ const existingId = writtenModules.get(moduleKey);
311
+ if (existingId !== undefined) {
312
+ if (parent[0] === REACT_ELEMENT_TYPE && key === '1') {
313
+ // If we're encoding the "type" of an element, we can refer
314
+ // to that by a lazy reference instead of directly since React
315
+ // knows how to deal with lazy values. This lets us suspend
316
+ // on this component rather than its parent until the code has
317
+ // loaded.
318
+ return serializeByRefID(existingId);
319
+ }
320
+ return serializeByValueID(existingId);
321
+ }
322
+ try {
323
+ const moduleMetaData: ModuleMetaData = resolveModuleMetaData(
324
+ request.bundlerConfig,
325
+ moduleReference,
326
+ );
327
+ request.pendingChunks++;
328
+ const moduleId = request.nextChunkId++;
329
+ emitModuleChunk(request, moduleId, moduleMetaData);
330
+ writtenModules.set(moduleKey, moduleId);
331
+ if (parent[0] === REACT_ELEMENT_TYPE && key === '1') {
332
+ // If we're encoding the "type" of an element, we can refer
333
+ // to that by a lazy reference instead of directly since React
334
+ // knows how to deal with lazy values. This lets us suspend
335
+ // on this component rather than its parent until the code has
336
+ // loaded.
337
+ return serializeByRefID(moduleId);
338
+ }
339
+ return serializeByValueID(moduleId);
340
+ } catch (x) {
341
+ request.pendingChunks++;
342
+ const errorId = request.nextChunkId++;
343
+ emitErrorChunk(request, errorId, x);
344
+ return serializeByValueID(errorId);
345
+ }
346
+}
347
+
348
function escapeStringValue(value: string): string {
349
if (value[0] === '$' || value[0] === '@') {
350
// We need to escape $ or @ prefixed strings since we use those to encode
@@ -561,45 +611,7 @@ export function resolveModelToJSON(
611
612
if (typeof value === 'object') {
613
if (isModuleReference(value)) {
564
- const moduleReference: ModuleReference<any> = (value: any);
565
- const moduleKey: ModuleKey = getModuleKey(moduleReference);
566
- const writtenModules = request.writtenModules;
567
- const existingId = writtenModules.get(moduleKey);
568
- if (existingId !== undefined) {
569
- if (parent[0] === REACT_ELEMENT_TYPE && key === '1') {
570
- // If we're encoding the "type" of an element, we can refer
571
- // to that by a lazy reference instead of directly since React
572
- // knows how to deal with lazy values. This lets us suspend
573
- // on this component rather than its parent until the code has
574
- // loaded.
575
- return serializeByRefID(existingId);
576
- }
577
- return serializeByValueID(existingId);
578
- }
579
- try {
580
- const moduleMetaData: ModuleMetaData = resolveModuleMetaData(
581
- request.bundlerConfig,
582
- moduleReference,
583
- );
584
- request.pendingChunks++;
585
- const moduleId = request.nextChunkId++;
586
- emitModuleChunk(request, moduleId, moduleMetaData);
587
- writtenModules.set(moduleKey, moduleId);
588
- if (parent[0] === REACT_ELEMENT_TYPE && key === '1') {
589
- // If we're encoding the "type" of an element, we can refer
590
- // to that by a lazy reference instead of directly since React
591
- // knows how to deal with lazy values. This lets us suspend
592
- // on this component rather than its parent until the code has
593
- // loaded.
594
- return serializeByRefID(moduleId);
595
- }
596
- return serializeByValueID(moduleId);
597
- } catch (x) {
598
- request.pendingChunks++;
599
- const errorId = request.nextChunkId++;
600
- emitErrorChunk(request, errorId, x);
601
- return serializeByValueID(errorId);
602
- }
614
+ return serializeModuleReference(request, parent, key, (value: any));
615
} else if ((value: any).$$typeof === REACT_PROVIDER_TYPE) {
616
const providerKey = ((value: any): ReactProviderType<any>)._context
617
._globalName;
@@ -673,6 +685,9 @@ export function resolveModelToJSON(
685
}
686
687
if (typeof value === 'function') {
688
+ if (isModuleReference(value)) {
689
+ return serializeModuleReference(request, parent, key, (value: any));
690
+ }
691
if (/^on[A-Z]/.test(key)) {
692
throw new Error(
693
'Event handlers cannot be passed to client component props. ' +