diff --git a/packages/react-noop-renderer/src/ReactNoopFlightServer.js b/packages/react-noop-renderer/src/ReactNoopFlightServer.js index f6e8845c96811..f080f27addfdc 100644 --- a/packages/react-noop-renderer/src/ReactNoopFlightServer.js +++ b/packages/react-noop-renderer/src/ReactNoopFlightServer.js @@ -45,6 +45,9 @@ const ReactNoopFlightServer = ReactFlightServer({ isModuleReference(reference: Object): boolean { return reference.$$typeof === Symbol.for('react.module.reference'); }, + getModuleKey(reference: Object): Object { + return reference; + }, resolveModuleMetaData( config: void, reference: {$$typeof: Symbol, value: any}, diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 34404544efbdc..374ce95397f35 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -14,6 +14,7 @@ import type { BundlerConfig, ModuleMetaData, ModuleReference, + ModuleKey, } from './ReactFlightServerConfig'; import { @@ -28,6 +29,7 @@ import { processSymbolChunk, processErrorChunk, resolveModuleMetaData, + getModuleKey, isModuleReference, } from './ReactFlightServerConfig'; @@ -79,6 +81,7 @@ export type Request = { completedJSONChunks: Array, completedErrorChunks: Array, writtenSymbols: Map, + writtenModules: Map, flowing: boolean, toJSON: (key: string, value: ReactModel) => ReactJSONValue, }; @@ -101,6 +104,7 @@ export function createRequest( completedJSONChunks: [], completedErrorChunks: [], writtenSymbols: new Map(), + writtenModules: new Map(), flowing: false, toJSON: function(key: string, value: ReactModel): ReactJSONValue { return resolveModelToJSON(request, this, key, value); @@ -425,6 +429,11 @@ export function resolveModelToJSON( if (typeof value === 'object') { if (isModuleReference(value)) { const moduleReference: ModuleReference = (value: any); + const moduleKey: ModuleKey = getModuleKey(moduleReference); + const existingId = request.writtenModules.get(moduleKey); + if (existingId !== undefined) { + return serializeByValueID(existingId); + } try { const moduleMetaData: ModuleMetaData = resolveModuleMetaData( request.bundlerConfig, diff --git a/packages/react-server/src/ReactFlightServerBundlerConfigCustom.js b/packages/react-server/src/ReactFlightServerBundlerConfigCustom.js index c0db2f6b2892a..eba05dab334fb 100644 --- a/packages/react-server/src/ReactFlightServerBundlerConfigCustom.js +++ b/packages/react-server/src/ReactFlightServerBundlerConfigCustom.js @@ -12,5 +12,7 @@ declare var $$$hostConfig: any; export opaque type BundlerConfig = mixed; // eslint-disable-line no-undef export opaque type ModuleReference = mixed; // eslint-disable-line no-undef export opaque type ModuleMetaData: any = mixed; // eslint-disable-line no-undef +export opaque type ModuleKey: any = mixed; // eslint-disable-line no-undef export const isModuleReference = $$$hostConfig.isModuleReference; +export const getModuleKey = $$$hostConfig.getModuleKey; export const resolveModuleMetaData = $$$hostConfig.resolveModuleMetaData; diff --git a/packages/react-transport-dom-relay/src/ReactFlightDOMRelayServerHostConfig.js b/packages/react-transport-dom-relay/src/ReactFlightDOMRelayServerHostConfig.js index 70c2e957596cf..73c75872c506c 100644 --- a/packages/react-transport-dom-relay/src/ReactFlightDOMRelayServerHostConfig.js +++ b/packages/react-transport-dom-relay/src/ReactFlightDOMRelayServerHostConfig.js @@ -38,6 +38,14 @@ export function isModuleReference(reference: Object): boolean { return reference instanceof JSResourceReference; } +export type ModuleKey = ModuleReference; + +export function getModuleKey(reference: ModuleReference): ModuleKey { + // We use the reference object itself as the key because we assume the + // object will be cached by the bundler runtime. + return reference; +} + export function resolveModuleMetaData( config: BundlerConfig, resource: ModuleReference, diff --git a/packages/react-transport-dom-webpack/src/ReactFlightServerWebpackBundlerConfig.js b/packages/react-transport-dom-webpack/src/ReactFlightServerWebpackBundlerConfig.js index b8bb0d5cbee7d..f691809522ca1 100644 --- a/packages/react-transport-dom-webpack/src/ReactFlightServerWebpackBundlerConfig.js +++ b/packages/react-transport-dom-webpack/src/ReactFlightServerWebpackBundlerConfig.js @@ -25,8 +25,14 @@ export type ModuleMetaData = { name: string, }; +export type ModuleKey = string; + const MODULE_TAG = Symbol.for('react.module.reference'); +export function getModuleKey(reference: ModuleReference): ModuleKey { + return reference.name; +} + export function isModuleReference(reference: Object): boolean { return reference.$$typeof === MODULE_TAG; } diff --git a/packages/react-transport-native-relay/src/ReactFlightNativeRelayServerHostConfig.js b/packages/react-transport-native-relay/src/ReactFlightNativeRelayServerHostConfig.js index c8566c960f0de..7ba6ba34420e8 100644 --- a/packages/react-transport-native-relay/src/ReactFlightNativeRelayServerHostConfig.js +++ b/packages/react-transport-native-relay/src/ReactFlightNativeRelayServerHostConfig.js @@ -38,6 +38,14 @@ export function isModuleReference(reference: Object): boolean { return reference instanceof JSResourceReferenceImpl; } +export type ModuleKey = ModuleReference; + +export function getModuleKey(reference: ModuleReference): ModuleKey { + // We use the reference object itself as the key because we assume the + // object will be cached by the bundler runtime. + return reference; +} + export function resolveModuleMetaData( config: BundlerConfig, resource: ModuleReference, diff --git a/scripts/jest/setupHostConfigs.js b/scripts/jest/setupHostConfigs.js index 4f9cbf5d479d8..58f4d4adf1240 100644 --- a/scripts/jest/setupHostConfigs.js +++ b/scripts/jest/setupHostConfigs.js @@ -35,6 +35,7 @@ jest.mock('react-server/flight', () => { jest.mock(shimServerFormatConfigPath, () => config); jest.mock('react-server/src/ReactFlightServerBundlerConfigCustom', () => ({ isModuleReference: config.isModuleReference, + getModuleKey: config.getModuleKey, resolveModuleMetaData: config.resolveModuleMetaData, })); jest.mock(shimFlightServerConfigPath, () =>