Files
react/scripts/jest/setupHostConfigs.js
Sebastian Markbåge ef9f6e77b8 Enable passing Server References from Server to Client (#26124)
This is the first of a series of PRs, that let you pass functions, by
reference, to the client and back. E.g. through Server Context. It's
like client references but they're opaque on the client and resolved on
the server.

To do this, for security, you must opt-in to exposing these functions to
the client using the `"use server"` directive. The `"use client"`
directive lets you enter the client from the server. The `"use server"`
directive lets you enter the server from the client.

This works by tagging those functions as Server References. We could
potentially expand this to other non-serializable or stateful objects
too like classes.

This only implements server->server CJS imports and server->server ESM
imports. We really should add a loader to the webpack plug-in for
client->server imports too. I'll leave closures as an exercise for
integrators.

You can't "call" a client reference on the server, however, you can
"call" a server reference on the client. This invokes a callback on the
Flight client options called `callServer`. This lets a router implement
calling back to the server. Effectively creating an RPC. This is using
JSON for serializing those arguments but more utils coming from
client->server serialization.
2023-02-09 19:45:05 -05:00

159 lines
5.2 KiB
JavaScript

'use strict';
const fs = require('fs');
const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
function resolveEntryFork(resolvedEntry, isFBBundle) {
// Pick which entry point fork to use:
// .modern.fb.js
// .classic.fb.js
// .fb.js
// .stable.js
// .experimental.js
// .js
if (isFBBundle) {
if (__EXPERIMENTAL__) {
// We can't currently use the true modern entry point because too many tests fail.
// TODO: Fix tests to not use ReactDOM.render or gate them. Then we can remove this.
return resolvedEntry;
}
const resolvedFBEntry = resolvedEntry.replace(
'.js',
__EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js'
);
if (fs.existsSync(resolvedFBEntry)) {
return resolvedFBEntry;
}
const resolvedGenericFBEntry = resolvedEntry.replace('.js', '.fb.js');
if (fs.existsSync(resolvedGenericFBEntry)) {
return resolvedGenericFBEntry;
}
// Even if it's a FB bundle we fallthrough to pick stable or experimental if we don't have an FB fork.
}
const resolvedForkedEntry = resolvedEntry.replace(
'.js',
__EXPERIMENTAL__ ? '.experimental.js' : '.stable.js'
);
if (fs.existsSync(resolvedForkedEntry)) {
return resolvedForkedEntry;
}
// Just use the plain .js one.
return resolvedEntry;
}
jest.mock('react', () => {
const resolvedEntryPoint = resolveEntryFork(
require.resolve('react'),
global.__WWW__
);
return jest.requireActual(resolvedEntryPoint);
});
jest.mock('react/react.shared-subset', () => {
const resolvedEntryPoint = resolveEntryFork(
require.resolve('react/src/ReactSharedSubset'),
global.__WWW__
);
return jest.requireActual(resolvedEntryPoint);
});
// When testing the custom renderer code path through `react-reconciler`,
// turn the export into a function, and use the argument as host config.
const shimHostConfigPath = 'react-reconciler/src/ReactFiberHostConfig';
jest.mock('react-reconciler', () => {
return config => {
jest.mock(shimHostConfigPath, () => config);
return jest.requireActual('react-reconciler');
};
});
const shimServerStreamConfigPath = 'react-server/src/ReactServerStreamConfig';
const shimServerFormatConfigPath = 'react-server/src/ReactServerFormatConfig';
const shimFlightServerConfigPath = 'react-server/src/ReactFlightServerConfig';
jest.mock('react-server', () => {
return config => {
jest.mock(shimServerStreamConfigPath, () => config);
jest.mock(shimServerFormatConfigPath, () => config);
return jest.requireActual('react-server');
};
});
jest.mock('react-server/flight', () => {
return config => {
jest.mock(shimServerStreamConfigPath, () => config);
jest.mock(shimServerFormatConfigPath, () => config);
jest.mock('react-server/src/ReactFlightServerBundlerConfigCustom', () => ({
isClientReference: config.isClientReference,
isServerReference: config.isServerReference,
getClientReferenceKey: config.getClientReferenceKey,
resolveClientReferenceMetadata: config.resolveClientReferenceMetadata,
}));
jest.mock(shimFlightServerConfigPath, () =>
jest.requireActual(
'react-server/src/forks/ReactFlightServerConfig.custom'
)
);
return jest.requireActual('react-server/flight');
};
});
const shimFlightClientHostConfigPath =
'react-client/src/ReactFlightClientHostConfig';
jest.mock('react-client/flight', () => {
return config => {
jest.mock(shimFlightClientHostConfigPath, () => config);
return jest.requireActual('react-client/flight');
};
});
const configPaths = [
'react-reconciler/src/ReactFiberHostConfig',
'react-client/src/ReactFlightClientHostConfig',
'react-server/src/ReactServerStreamConfig',
'react-server/src/ReactServerFormatConfig',
'react-server/src/ReactFlightServerConfig',
];
function mockAllConfigs(rendererInfo) {
configPaths.forEach(path => {
// We want the reconciler to pick up the host config for this renderer.
jest.mock(path, () => {
let idx = path.lastIndexOf('/');
let forkPath = path.substr(0, idx) + '/forks' + path.substr(idx);
return jest.requireActual(`${forkPath}.${rendererInfo.shortName}.js`);
});
});
}
// But for inlined host configs (such as React DOM, Native, etc), we
// mock their named entry points to establish a host config mapping.
inlinedHostConfigs.forEach(rendererInfo => {
if (rendererInfo.shortName === 'custom') {
// There is no inline entry point for the custom renderers.
// Instead, it's handled by the generic `react-reconciler` entry point above.
return;
}
rendererInfo.entryPoints.forEach(entryPoint => {
jest.mock(entryPoint, () => {
mockAllConfigs(rendererInfo);
const resolvedEntryPoint = resolveEntryFork(
require.resolve(entryPoint),
global.__WWW__
);
return jest.requireActual(resolvedEntryPoint);
});
});
});
// Make it possible to import this module inside
// the React package itself.
jest.mock('shared/ReactSharedInternals', () =>
jest.requireActual('react/src/ReactSharedInternals')
);
// Make it possible to import this module inside
// the ReactDOM package itself.
jest.mock('shared/ReactDOMSharedInternals', () =>
jest.requireActual('react-dom/src/ReactDOMSharedInternals')
);
jest.mock('scheduler', () => jest.requireActual('scheduler/unstable_mock'));