diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 0eaf513a67..3234814952 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -64,7 +64,10 @@ import { rendererPackageName, } from './ReactFlightClientConfig'; -import {createBoundServerReference} from './ReactFlightReplyClient'; +import { + createBoundServerReference, + registerBoundServerReference, +} from './ReactFlightReplyClient'; import {readTemporaryReference} from './ReactFlightTemporaryReferences'; @@ -1096,7 +1099,14 @@ function loadServerReference, T>( let promise: null | Thenable = preloadModule(serverReference); if (!promise) { if (!metaData.bound) { - return (requireModule(serverReference): any); + const resolvedValue = (requireModule(serverReference): any); + registerBoundServerReference( + resolvedValue, + metaData.id, + metaData.bound, + response._encodeFormAction, + ); + return resolvedValue; } else { promise = Promise.resolve(metaData.bound); } @@ -1128,6 +1138,13 @@ function loadServerReference, T>( resolvedValue = resolvedValue.bind.apply(resolvedValue, boundArgs); } + registerBoundServerReference( + resolvedValue, + metaData.id, + metaData.bound, + response._encodeFormAction, + ); + parentObject[key] = resolvedValue; // If this is the root object for a model reference, where `handler.value` diff --git a/packages/react-client/src/ReactFlightReplyClient.js b/packages/react-client/src/ReactFlightReplyClient.js index 65d1129b53..3fa37cd00c 100644 --- a/packages/react-client/src/ReactFlightReplyClient.js +++ b/packages/react-client/src/ReactFlightReplyClient.js @@ -1125,11 +1125,12 @@ function createFakeServerFunction, T>( } } -function registerServerReference( - proxy: any, - reference: {id: ServerReferenceId, bound: null | Thenable>}, +export function registerBoundServerReference( + reference: T, + id: ServerReferenceId, + bound: null | Thenable>, encodeFormAction: void | EncodeFormActionCallback, -) { +): void { // Expose encoder for use by SSR, as well as a special bind that can be used to // keep server capabilities. if (usedWithSSR) { @@ -1147,13 +1148,22 @@ function registerServerReference( encodeFormAction, ); }; - Object.defineProperties((proxy: any), { + Object.defineProperties((reference: any), { $$FORM_ACTION: {value: $$FORM_ACTION}, $$IS_SIGNATURE_EQUAL: {value: isSignatureEqual}, bind: {value: bind}, }); } - knownServerReferences.set(proxy, reference); + knownServerReferences.set(reference, {id, bound}); +} + +export function registerServerReference( + reference: T, + id: ServerReferenceId, + encodeFormAction?: EncodeFormActionCallback, +): ServerReference { + registerBoundServerReference(reference, id, null, encodeFormAction); + return reference; } // $FlowFixMe[method-unbinding] @@ -1258,7 +1268,7 @@ export function createBoundServerReference, T>( ); } } - registerServerReference(action, {id, bound}, encodeFormAction); + registerBoundServerReference(action, id, bound, encodeFormAction); return action; } @@ -1358,6 +1368,6 @@ export function createServerReference, T>( ); } } - registerServerReference(action, {id, bound: null}, encodeFormAction); + registerBoundServerReference(action, id, null, encodeFormAction); return action; } diff --git a/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js index afabc29104..9ae47e3b55 100644 --- a/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js +++ b/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js @@ -25,9 +25,11 @@ import { injectIntoDevTools, } from 'react-client/src/ReactFlightClient'; -import { - processReply, +import {processReply} from 'react-client/src/ReactFlightReplyClient'; + +export { createServerReference, + registerServerReference, } from 'react-client/src/ReactFlightReplyClient'; import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences'; @@ -151,12 +153,7 @@ function encodeReply( }); } -export { - createFromFetch, - createFromReadableStream, - encodeReply, - createServerReference, -}; +export {createFromFetch, createFromReadableStream, encodeReply}; if (__DEV__) { injectIntoDevTools(); diff --git a/packages/react-server-dom-esm/src/client/ReactFlightDOMClientNode.js b/packages/react-server-dom-esm/src/client/ReactFlightDOMClientNode.js index 2e1c556642..75c569e5ac 100644 --- a/packages/react-server-dom-esm/src/client/ReactFlightDOMClientNode.js +++ b/packages/react-server-dom-esm/src/client/ReactFlightDOMClientNode.js @@ -26,6 +26,8 @@ import { import {createServerReference as createServerReferenceImpl} from 'react-client/src/ReactFlightReplyClient'; +export {registerServerReference} from 'react-client/src/ReactFlightReplyClient'; + function noServerCall() { throw new Error( 'Server Functions cannot be called during initial render. ' + diff --git a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js index 7ea840b140..3aca4a355d 100644 --- a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js +++ b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js @@ -26,6 +26,8 @@ import { createServerReference as createServerReferenceImpl, } from 'react-client/src/ReactFlightReplyClient'; +export {registerServerReference} from 'react-client/src/ReactFlightReplyClient'; + import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences'; export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences'; diff --git a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js index 8783cbfc6a..b1fbfed08f 100644 --- a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js +++ b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js @@ -25,6 +25,8 @@ import { createServerReference as createServerReferenceImpl, } from 'react-client/src/ReactFlightReplyClient'; +export {registerServerReference} from 'react-client/src/ReactFlightReplyClient'; + import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences'; export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences'; diff --git a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientNode.js b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientNode.js index 8be06af9f2..b12a3a3ff4 100644 --- a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientNode.js +++ b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientNode.js @@ -21,6 +21,8 @@ import { import {createServerReference as createServerReferenceImpl} from 'react-client/src/ReactFlightReplyClient'; +export {registerServerReference} from 'react-client/src/ReactFlightReplyClient'; + function findSourceMapURL(filename: string, environmentName: string) { const devServer = parcelRequire.meta.devServer; if (devServer != null) { diff --git a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js index b6b55e4586..ee319beca1 100644 --- a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js +++ b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js @@ -25,9 +25,11 @@ import { injectIntoDevTools, } from 'react-client/src/ReactFlightClient'; -import { - processReply, +import {processReply} from 'react-client/src/ReactFlightReplyClient'; + +export { createServerReference, + registerServerReference, } from 'react-client/src/ReactFlightReplyClient'; import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences'; @@ -150,12 +152,7 @@ function encodeReply( }); } -export { - createFromFetch, - createFromReadableStream, - encodeReply, - createServerReference, -}; +export {createFromFetch, createFromReadableStream, encodeReply}; if (__DEV__) { injectIntoDevTools(); diff --git a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientEdge.js b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientEdge.js index 509950bc65..48cb0dd4db 100644 --- a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientEdge.js +++ b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientEdge.js @@ -41,6 +41,8 @@ import { createServerReference as createServerReferenceImpl, } from 'react-client/src/ReactFlightReplyClient'; +export {registerServerReference} from 'react-client/src/ReactFlightReplyClient'; + import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences'; export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences'; diff --git a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientNode.js b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientNode.js index 2ee76fa3b4..919be523f8 100644 --- a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientNode.js +++ b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientNode.js @@ -38,6 +38,8 @@ import { import {createServerReference as createServerReferenceImpl} from 'react-client/src/ReactFlightReplyClient'; +export {registerServerReference} from 'react-client/src/ReactFlightReplyClient'; + function noServerCall() { throw new Error( 'Server Functions cannot be called during initial render. ' + diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js index ba1ae3b64a..5d3af9d411 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js @@ -301,6 +301,48 @@ describe('ReactFlightDOMEdge', () => { expect(result.boundMethod()).toBe('hi, there'); }); + it('should load a server reference on a consuming server and pass it back', async () => { + function greet(name) { + return 'hi, ' + name; + } + const ServerModule = serverExports({ + greet, + }); + + const stream = await serverAct(() => + ReactServerDOMServer.renderToReadableStream( + { + method: ServerModule.greet, + boundMethod: ServerModule.greet.bind(null, 'there'), + }, + webpackMap, + ), + ); + const response = ReactServerDOMClient.createFromReadableStream(stream, { + serverConsumerManifest: { + moduleMap: webpackMap, + serverModuleMap: webpackServerMap, + moduleLoading: webpackModuleLoading, + }, + }); + + const result = await response; + + expect(result.method).toBe(greet); + expect(result.boundMethod()).toBe('hi, there'); + + const body = await ReactServerDOMClient.encodeReply({ + method: result.method, + boundMethod: result.boundMethod, + }); + const replyResult = await ReactServerDOMServer.decodeReply( + body, + webpackServerMap, + ); + expect(replyResult.method).toBe(greet); + expect(replyResult.boundMethod()).toBe('hi, there'); + }); + it('should encode long string in a compact format', async () => { const testString = '"\n\t'.repeat(500) + '🙃'; const testString2 = 'hello'.repeat(400); diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js index 2effa9868e..7315e78c61 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReplyEdge-test.js @@ -22,7 +22,7 @@ if (typeof File === 'undefined' || typeof FormData === 'undefined') { global.FormData = require('undici').FormData; } -// let serverExports; +let serverExports; let webpackServerMap; let ReactServerDOMServer; let ReactServerDOMClient; @@ -36,7 +36,7 @@ describe('ReactFlightDOMReplyEdge', () => { require('react-server-dom-webpack/server.edge'), ); const WebpackMock = require('./utils/WebpackMock'); - // serverExports = WebpackMock.serverExports; + serverExports = WebpackMock.serverExports; webpackServerMap = WebpackMock.webpackServerMap; ReactServerDOMServer = require('react-server-dom-webpack/server.edge'); jest.resetModules(); @@ -308,4 +308,29 @@ describe('ReactFlightDOMReplyEdge', () => { expect(await decoded.a).toBe('hello'); expect(Array.from(await decoded.b)).toEqual(Array.from(buffer)); }); + + it('can pass a registered server reference', async () => { + function greet(name) { + return 'hi, ' + name; + } + const ServerModule = serverExports({ + greet, + }); + + ReactServerDOMClient.registerServerReference( + ServerModule.greet, + ServerModule.greet.$$id, + ); + + const body = await ReactServerDOMClient.encodeReply({ + method: ServerModule.greet, + boundMethod: ServerModule.greet.bind(null, 'there'), + }); + const replyResult = await ReactServerDOMServer.decodeReply( + body, + webpackServerMap, + ); + expect(replyResult.method).toBe(greet); + expect(replyResult.boundMethod()).toBe('hi, there'); + }); }); diff --git a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js index b6b55e4586..ee319beca1 100644 --- a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js +++ b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js @@ -25,9 +25,11 @@ import { injectIntoDevTools, } from 'react-client/src/ReactFlightClient'; -import { - processReply, +import {processReply} from 'react-client/src/ReactFlightReplyClient'; + +export { createServerReference, + registerServerReference, } from 'react-client/src/ReactFlightReplyClient'; import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences'; @@ -150,12 +152,7 @@ function encodeReply( }); } -export { - createFromFetch, - createFromReadableStream, - encodeReply, - createServerReference, -}; +export {createFromFetch, createFromReadableStream, encodeReply}; if (__DEV__) { injectIntoDevTools(); diff --git a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js index 509950bc65..48cb0dd4db 100644 --- a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js +++ b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js @@ -41,6 +41,8 @@ import { createServerReference as createServerReferenceImpl, } from 'react-client/src/ReactFlightReplyClient'; +export {registerServerReference} from 'react-client/src/ReactFlightReplyClient'; + import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences'; export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences'; diff --git a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientNode.js b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientNode.js index 22c8928432..4118ad046d 100644 --- a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientNode.js +++ b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientNode.js @@ -39,6 +39,8 @@ import { import {createServerReference as createServerReferenceImpl} from 'react-client/src/ReactFlightReplyClient'; +export {registerServerReference} from 'react-client/src/ReactFlightReplyClient'; + function noServerCall() { throw new Error( 'Server Functions cannot be called during initial render. ' + diff --git a/packages/react-server/src/ReactFlightReplyServer.js b/packages/react-server/src/ReactFlightReplyServer.js index 4db7571bb6..7c94352f28 100644 --- a/packages/react-server/src/ReactFlightReplyServer.js +++ b/packages/react-server/src/ReactFlightReplyServer.js @@ -936,8 +936,10 @@ function parseModelString( // Server Reference const ref = value.slice(2); // TODO: Just encode this in the reference inline instead of as a model. - const metaData: {id: ServerReferenceId, bound: Thenable>} = - getOutlinedModel(response, ref, obj, key, createModel); + const metaData: { + id: ServerReferenceId, + bound: null | Thenable>, + } = getOutlinedModel(response, ref, obj, key, createModel); return loadServerReference( response, metaData.id,