Skip to content

Commit 7ece221

Browse files
committed
Expose registerServerReference from the client builds
This is used to register Server References that exist in the current environment but also exists in the server it might call into. Such as a remote server. If the value comes from the remote server in the first place then this is called automatically to ensure that you can pass a reference back to where it came from - even if the serverModuleMap option is used .
1 parent e9252bc commit 7ece221

16 files changed

+143
-38
lines changed

packages/react-client/src/ReactFlightClient.js

+19-2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ import {
6464
rendererPackageName,
6565
} from './ReactFlightClientConfig';
6666

67-
import {createBoundServerReference} from './ReactFlightReplyClient';
67+
import {
68+
createBoundServerReference,
69+
registerBoundServerReference,
70+
} from './ReactFlightReplyClient';
6871

6972
import {readTemporaryReference} from './ReactFlightTemporaryReferences';
7073

@@ -1096,7 +1099,14 @@ function loadServerReference<A: Iterable<any>, T>(
10961099
let promise: null | Thenable<any> = preloadModule(serverReference);
10971100
if (!promise) {
10981101
if (!metaData.bound) {
1099-
return (requireModule(serverReference): any);
1102+
const resolvedValue = (requireModule(serverReference): any);
1103+
registerBoundServerReference(
1104+
resolvedValue,
1105+
metaData.id,
1106+
metaData.bound,
1107+
response._encodeFormAction,
1108+
);
1109+
return resolvedValue;
11001110
} else {
11011111
promise = Promise.resolve(metaData.bound);
11021112
}
@@ -1128,6 +1138,13 @@ function loadServerReference<A: Iterable<any>, T>(
11281138
resolvedValue = resolvedValue.bind.apply(resolvedValue, boundArgs);
11291139
}
11301140

1141+
registerBoundServerReference(
1142+
resolvedValue,
1143+
metaData.id,
1144+
metaData.bound,
1145+
response._encodeFormAction,
1146+
);
1147+
11311148
parentObject[key] = resolvedValue;
11321149

11331150
// If this is the root object for a model reference, where `handler.value`

packages/react-client/src/ReactFlightReplyClient.js

+18-8
Original file line numberDiff line numberDiff line change
@@ -1125,11 +1125,12 @@ function createFakeServerFunction<A: Iterable<any>, T>(
11251125
}
11261126
}
11271127

1128-
function registerServerReference(
1129-
proxy: any,
1130-
reference: {id: ServerReferenceId, bound: null | Thenable<Array<any>>},
1128+
export function registerBoundServerReference<T: Function>(
1129+
reference: T,
1130+
id: ServerReferenceId,
1131+
bound: null | Thenable<Array<any>>,
11311132
encodeFormAction: void | EncodeFormActionCallback,
1132-
) {
1133+
): void {
11331134
// Expose encoder for use by SSR, as well as a special bind that can be used to
11341135
// keep server capabilities.
11351136
if (usedWithSSR) {
@@ -1147,13 +1148,22 @@ function registerServerReference(
11471148
encodeFormAction,
11481149
);
11491150
};
1150-
Object.defineProperties((proxy: any), {
1151+
Object.defineProperties((reference: any), {
11511152
$$FORM_ACTION: {value: $$FORM_ACTION},
11521153
$$IS_SIGNATURE_EQUAL: {value: isSignatureEqual},
11531154
bind: {value: bind},
11541155
});
11551156
}
1156-
knownServerReferences.set(proxy, reference);
1157+
knownServerReferences.set(reference, {id, bound});
1158+
}
1159+
1160+
export function registerServerReference<T: Function>(
1161+
reference: T,
1162+
id: ServerReferenceId,
1163+
encodeFormAction?: EncodeFormActionCallback,
1164+
): ServerReference<T> {
1165+
registerBoundServerReference(reference, id, null, encodeFormAction);
1166+
return reference;
11571167
}
11581168

11591169
// $FlowFixMe[method-unbinding]
@@ -1258,7 +1268,7 @@ export function createBoundServerReference<A: Iterable<any>, T>(
12581268
);
12591269
}
12601270
}
1261-
registerServerReference(action, {id, bound}, encodeFormAction);
1271+
registerBoundServerReference(action, id, bound, encodeFormAction);
12621272
return action;
12631273
}
12641274

@@ -1358,6 +1368,6 @@ export function createServerReference<A: Iterable<any>, T>(
13581368
);
13591369
}
13601370
}
1361-
registerServerReference(action, {id, bound: null}, encodeFormAction);
1371+
registerBoundServerReference(action, id, null, encodeFormAction);
13621372
return action;
13631373
}

packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js

+7-8
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,15 @@ import {
2525
injectIntoDevTools,
2626
} from 'react-client/src/ReactFlightClient';
2727

28-
import {
29-
processReply,
28+
import {processReply} from 'react-client/src/ReactFlightReplyClient';
29+
30+
export {
3031
createServerReference,
32+
registerServerReference,
3133
} from 'react-client/src/ReactFlightReplyClient';
3234

35+
export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
36+
3337
import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
3438

3539
export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
@@ -151,12 +155,7 @@ function encodeReply(
151155
});
152156
}
153157

154-
export {
155-
createFromFetch,
156-
createFromReadableStream,
157-
encodeReply,
158-
createServerReference,
159-
};
158+
export {createFromFetch, createFromReadableStream, encodeReply};
160159

161160
if (__DEV__) {
162161
injectIntoDevTools();

packages/react-server-dom-esm/src/client/ReactFlightDOMClientNode.js

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626

2727
import {createServerReference as createServerReferenceImpl} from 'react-client/src/ReactFlightReplyClient';
2828

29+
export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
30+
2931
function noServerCall() {
3032
throw new Error(
3133
'Server Functions cannot be called during initial render. ' +

packages/react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
createServerReference as createServerReferenceImpl,
2727
} from 'react-client/src/ReactFlightReplyClient';
2828

29+
export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
30+
2931
import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
3032

3133
export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';

packages/react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import {
2525
createServerReference as createServerReferenceImpl,
2626
} from 'react-client/src/ReactFlightReplyClient';
2727

28+
export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
29+
2830
import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
2931

3032
export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';

packages/react-server-dom-parcel/src/client/ReactFlightDOMClientNode.js

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121

2222
import {createServerReference as createServerReferenceImpl} from 'react-client/src/ReactFlightReplyClient';
2323

24+
export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
25+
2426
function findSourceMapURL(filename: string, environmentName: string) {
2527
const devServer = parcelRequire.meta.devServer;
2628
if (devServer != null) {

packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js

+5-8
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ import {
2525
injectIntoDevTools,
2626
} from 'react-client/src/ReactFlightClient';
2727

28-
import {
29-
processReply,
28+
import {processReply} from 'react-client/src/ReactFlightReplyClient';
29+
30+
export {
3031
createServerReference,
32+
registerServerReference,
3133
} from 'react-client/src/ReactFlightReplyClient';
3234

3335
import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
@@ -150,12 +152,7 @@ function encodeReply(
150152
});
151153
}
152154

153-
export {
154-
createFromFetch,
155-
createFromReadableStream,
156-
encodeReply,
157-
createServerReference,
158-
};
155+
export {createFromFetch, createFromReadableStream, encodeReply};
159156

160157
if (__DEV__) {
161158
injectIntoDevTools();

packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientEdge.js

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ import {
4141
createServerReference as createServerReferenceImpl,
4242
} from 'react-client/src/ReactFlightReplyClient';
4343

44+
export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
45+
4446
import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
4547

4648
export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';

packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientNode.js

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import {
3838

3939
import {createServerReference as createServerReferenceImpl} from 'react-client/src/ReactFlightReplyClient';
4040

41+
export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
42+
4143
function noServerCall() {
4244
throw new Error(
4345
'Server Functions cannot be called during initial render. ' +

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js

+42
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,48 @@ describe('ReactFlightDOMEdge', () => {
301301
expect(result.boundMethod()).toBe('hi, there');
302302
});
303303

304+
it('should load a server reference on a consuming server and pass it back', async () => {
305+
function greet(name) {
306+
return 'hi, ' + name;
307+
}
308+
const ServerModule = serverExports({
309+
greet,
310+
});
311+
312+
const stream = await serverAct(() =>
313+
ReactServerDOMServer.renderToReadableStream(
314+
{
315+
method: ServerModule.greet,
316+
boundMethod: ServerModule.greet.bind(null, 'there'),
317+
},
318+
webpackMap,
319+
),
320+
);
321+
const response = ReactServerDOMClient.createFromReadableStream(stream, {
322+
serverConsumerManifest: {
323+
moduleMap: webpackMap,
324+
serverModuleMap: webpackServerMap,
325+
moduleLoading: webpackModuleLoading,
326+
},
327+
});
328+
329+
const result = await response;
330+
331+
expect(result.method).toBe(greet);
332+
expect(result.boundMethod()).toBe('hi, there');
333+
334+
const body = await ReactServerDOMClient.encodeReply({
335+
method: result.method,
336+
boundMethod: result.boundMethod,
337+
});
338+
const replyResult = await ReactServerDOMServer.decodeReply(
339+
body,
340+
webpackServerMap,
341+
);
342+
expect(replyResult.method).toBe(greet);
343+
expect(replyResult.boundMethod()).toBe('hi, there');
344+
});
345+
304346
it('should encode long string in a compact format', async () => {
305347
const testString = '"\n\t'.repeat(500) + '🙃';
306348
const testString2 = 'hello'.repeat(400);

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js

+27-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ if (typeof File === 'undefined' || typeof FormData === 'undefined') {
2222
global.FormData = require('undici').FormData;
2323
}
2424

25-
// let serverExports;
25+
let serverExports;
2626
let webpackServerMap;
2727
let ReactServerDOMServer;
2828
let ReactServerDOMClient;
@@ -36,7 +36,7 @@ describe('ReactFlightDOMReplyEdge', () => {
3636
require('react-server-dom-webpack/server.edge'),
3737
);
3838
const WebpackMock = require('./utils/WebpackMock');
39-
// serverExports = WebpackMock.serverExports;
39+
serverExports = WebpackMock.serverExports;
4040
webpackServerMap = WebpackMock.webpackServerMap;
4141
ReactServerDOMServer = require('react-server-dom-webpack/server.edge');
4242
jest.resetModules();
@@ -308,4 +308,29 @@ describe('ReactFlightDOMReplyEdge', () => {
308308
expect(await decoded.a).toBe('hello');
309309
expect(Array.from(await decoded.b)).toEqual(Array.from(buffer));
310310
});
311+
312+
it('can pass a registered server reference', async () => {
313+
function greet(name) {
314+
return 'hi, ' + name;
315+
}
316+
const ServerModule = serverExports({
317+
greet,
318+
});
319+
320+
ReactServerDOMClient.registerServerReference(
321+
ServerModule.greet,
322+
ServerModule.greet.$$id,
323+
);
324+
325+
const body = await ReactServerDOMClient.encodeReply({
326+
method: ServerModule.greet,
327+
boundMethod: ServerModule.greet.bind(null, 'there'),
328+
});
329+
const replyResult = await ReactServerDOMServer.decodeReply(
330+
body,
331+
webpackServerMap,
332+
);
333+
expect(replyResult.method).toBe(greet);
334+
expect(replyResult.boundMethod()).toBe('hi, there');
335+
});
311336
});

packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js

+5-8
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ import {
2525
injectIntoDevTools,
2626
} from 'react-client/src/ReactFlightClient';
2727

28-
import {
29-
processReply,
28+
import {processReply} from 'react-client/src/ReactFlightReplyClient';
29+
30+
export {
3031
createServerReference,
32+
registerServerReference,
3133
} from 'react-client/src/ReactFlightReplyClient';
3234

3335
import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
@@ -150,12 +152,7 @@ function encodeReply(
150152
});
151153
}
152154

153-
export {
154-
createFromFetch,
155-
createFromReadableStream,
156-
encodeReply,
157-
createServerReference,
158-
};
155+
export {createFromFetch, createFromReadableStream, encodeReply};
159156

160157
if (__DEV__) {
161158
injectIntoDevTools();

packages/react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ import {
4141
createServerReference as createServerReferenceImpl,
4242
} from 'react-client/src/ReactFlightReplyClient';
4343

44+
export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
45+
4446
import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
4547

4648
export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';

packages/react-server-dom-webpack/src/client/ReactFlightDOMClientNode.js

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import {
3939

4040
import {createServerReference as createServerReferenceImpl} from 'react-client/src/ReactFlightReplyClient';
4141

42+
export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
43+
4244
function noServerCall() {
4345
throw new Error(
4446
'Server Functions cannot be called during initial render. ' +

packages/react-server/src/ReactFlightReplyServer.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -936,8 +936,10 @@ function parseModelString(
936936
// Server Reference
937937
const ref = value.slice(2);
938938
// TODO: Just encode this in the reference inline instead of as a model.
939-
const metaData: {id: ServerReferenceId, bound: Thenable<Array<any>>} =
940-
getOutlinedModel(response, ref, obj, key, createModel);
939+
const metaData: {
940+
id: ServerReferenceId,
941+
bound: null | Thenable<Array<any>>,
942+
} = getOutlinedModel(response, ref, obj, key, createModel);
941943
return loadServerReference(
942944
response,
943945
metaData.id,

0 commit comments

Comments
 (0)