diff --git a/.eslintrc.js b/.eslintrc.js
index 4d53738e281ae..a00174fea7122 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -517,6 +517,7 @@ module.exports = {
__TEST__: 'readonly',
__UMD__: 'readonly',
__VARIANT__: 'readonly',
+ __unmockReact: 'readonly',
gate: 'readonly',
trustedTypes: 'readonly',
IS_REACT_ACT_ENVIRONMENT: 'readonly',
diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js
index 2ebbb9f2d52f5..30cfda19af6ec 100644
--- a/packages/react-client/src/__tests__/ReactFlight-test.js
+++ b/packages/react-client/src/__tests__/ReactFlight-test.js
@@ -14,6 +14,7 @@ let act;
let use;
let startTransition;
let React;
+let ReactServer;
let ReactNoop;
let ReactNoopFlightServer;
let ReactNoopFlightClient;
@@ -25,12 +26,18 @@ let assertLog;
describe('ReactFlight', () => {
beforeEach(() => {
jest.resetModules();
-
+ jest.mock('react', () => require('react/react.shared-subset'));
+ ReactServer = require('react');
+ ReactNoopFlightServer = require('react-noop-renderer/flight-server');
+ // This stores the state so we need to preserve it
+ const flightModules = require('react-noop-renderer/flight-modules');
+ __unmockReact();
+ jest.resetModules();
+ jest.mock('react-noop-renderer/flight-modules', () => flightModules);
React = require('react');
startTransition = React.startTransition;
use = React.use;
ReactNoop = require('react-noop-renderer');
- ReactNoopFlightServer = require('react-noop-renderer/flight-server');
ReactNoopFlightClient = require('react-noop-renderer/flight-client');
act = require('internal-test-utils').act;
Scheduler = require('scheduler');
@@ -111,6 +118,19 @@ describe('ReactFlight', () => {
return ctx;
}
+ function createServerServerContext(globalName, defaultValue, withStack) {
+ let ctx;
+ expect(() => {
+ ctx = ReactServer.createServerContext(globalName, defaultValue);
+ }).toErrorDev(
+ 'Server Context is deprecated and will soon be removed. ' +
+ 'It was never documented and we have found it not to be useful ' +
+ 'enough to warrant the downside it imposes on all apps.',
+ {withoutStack: !withStack},
+ );
+ return ctx;
+ }
+
function clientReference(value) {
return Object.defineProperties(
function () {
@@ -970,7 +990,7 @@ describe('ReactFlight', () => {
const Context = React.createContext();
const ClientContext = clientReference(Context);
function ServerComponent() {
- return React.useContext(ClientContext);
+ return ReactServer.useContext(ClientContext);
}
expect(() => {
const transport = ReactNoopFlightServer.render();
@@ -982,7 +1002,7 @@ describe('ReactFlight', () => {
describe('Hooks', () => {
function DivWithId({children}) {
- const id = React.useId();
+ const id = ReactServer.useId();
return
{children}
;
}
@@ -1039,7 +1059,7 @@ describe('ReactFlight', () => {
// so the output passed to the Client has no knowledge of the useId use. In the future we would like to add a DEV warning when this happens. For now
// we just accept that it is a nuance of useId in Flight
function App() {
- const id = React.useId();
+ const id = ReactServer.useId();
const div = {id}
;
return ;
}
@@ -1076,19 +1096,17 @@ describe('ReactFlight', () => {
describe('ServerContext', () => {
// @gate enableServerContext
it('supports basic createServerContext usage', async () => {
- const ServerContext = createServerContext(
+ const ServerContext = createServerServerContext(
'ServerContext',
'hello from server',
);
function Foo() {
- const context = React.useContext(ServerContext);
+ const context = ReactServer.useContext(ServerContext);
return {context}
;
}
const transport = ReactNoopFlightServer.render();
await act(async () => {
- ServerContext._currentRenderer = null;
- ServerContext._currentRenderer2 = null;
ReactNoop.render(await ReactNoopFlightClient.read(transport));
});
@@ -1097,7 +1115,10 @@ describe('ReactFlight', () => {
// @gate enableServerContext
it('propagates ServerContext providers in flight', async () => {
- const ServerContext = createServerContext('ServerContext', 'default');
+ const ServerContext = createServerServerContext(
+ 'ServerContext',
+ 'default',
+ );
function Foo() {
return (
@@ -1109,14 +1130,12 @@ describe('ReactFlight', () => {
);
}
function Bar() {
- const context = React.useContext(ServerContext);
+ const context = ReactServer.useContext(ServerContext);
return context;
}
const transport = ReactNoopFlightServer.render();
await act(async () => {
- ServerContext._currentRenderer = null;
- ServerContext._currentRenderer2 = null;
ReactNoop.render(await ReactNoopFlightClient.read(transport));
});
@@ -1125,7 +1144,7 @@ describe('ReactFlight', () => {
// @gate enableServerContext
it('errors if you try passing JSX through ServerContext value', () => {
- const ServerContext = createServerContext('ServerContext', {
+ const ServerContext = createServerServerContext('ServerContext', {
foo: {
bar: hi this is default,
},
@@ -1146,7 +1165,7 @@ describe('ReactFlight', () => {
);
}
function Bar() {
- const context = React.useContext(ServerContext);
+ const context = ReactServer.useContext(ServerContext);
return context.foo.bar;
}
@@ -1159,7 +1178,10 @@ describe('ReactFlight', () => {
// @gate enableServerContext
it('propagates ServerContext and cleans up the providers in flight', async () => {
- const ServerContext = createServerContext('ServerContext', 'default');
+ const ServerContext = createServerServerContext(
+ 'ServerContext',
+ 'default',
+ );
function Foo() {
return (
@@ -1181,7 +1203,7 @@ describe('ReactFlight', () => {
);
}
function Bar() {
- const context = React.useContext(ServerContext);
+ const context = ReactServer.useContext(ServerContext);
return {context};
}
@@ -1203,7 +1225,10 @@ describe('ReactFlight', () => {
// @gate enableServerContext
it('propagates ServerContext providers in flight after suspending', async () => {
- const ServerContext = createServerContext('ServerContext', 'default');
+ const ServerContext = createServerServerContext(
+ 'ServerContext',
+ 'default',
+ );
function Foo() {
return (
@@ -1231,7 +1256,7 @@ describe('ReactFlight', () => {
throw promise;
}
Scheduler.log('rendered');
- const context = React.useContext(ServerContext);
+ const context = ReactServer.useContext(ServerContext);
return context;
}
@@ -1248,8 +1273,6 @@ describe('ReactFlight', () => {
assertLog(['rendered']);
await act(async () => {
- ServerContext._currentRenderer = null;
- ServerContext._currentRenderer2 = null;
ReactNoop.render(await ReactNoopFlightClient.read(transport));
});
@@ -1258,11 +1281,15 @@ describe('ReactFlight', () => {
// @gate enableServerContext
it('serializes ServerContext to client', async () => {
- const ServerContext = createServerContext('ServerContext', 'default');
+ const ServerContext = createServerServerContext(
+ 'ServerContext',
+ 'default',
+ );
+ const ClientContext = createServerContext('ServerContext', 'default');
function ClientBar() {
Scheduler.log('ClientBar');
- const context = React.useContext(ServerContext);
+ const context = React.useContext(ClientContext);
return {context};
}
@@ -1285,8 +1312,6 @@ describe('ReactFlight', () => {
assertLog([]);
await act(async () => {
- ServerContext._currentRenderer = null;
- ServerContext._currentRenderer2 = null;
const flightModel = await ReactNoopFlightClient.read(transport);
ReactNoop.render(flightModel.foo);
});
@@ -1301,9 +1326,12 @@ describe('ReactFlight', () => {
// @gate enableServerContext
it('takes ServerContext from the client for refetching use cases', async () => {
- const ServerContext = createServerContext('ServerContext', 'default');
+ const ServerContext = createServerServerContext(
+ 'ServerContext',
+ 'default',
+ );
function Bar() {
- return {React.useContext(ServerContext)};
+ return {ReactServer.useContext(ServerContext)};
}
const transport = ReactNoopFlightServer.render(, {
context: [['ServerContext', 'Override']],
@@ -1321,7 +1349,7 @@ describe('ReactFlight', () => {
let ServerContext;
function inlineLazyServerContextInitialization() {
if (!ServerContext) {
- ServerContext = createServerContext('ServerContext', 'default');
+ ServerContext = createServerServerContext('ServerContext', 'default');
}
return ServerContext;
}
@@ -1346,7 +1374,7 @@ describe('ReactFlight', () => {
return (
- {React.useContext(inlineLazyServerContextInitialization())}
+ {ReactServer.useContext(inlineLazyServerContextInitialization())}
@@ -1381,11 +1409,17 @@ describe('ReactFlight', () => {
// Reset all modules, except flight-modules which keeps the registry of Client Components
const flightModules = require('react-noop-renderer/flight-modules');
jest.resetModules();
+ jest.mock('react', () => require('react/react.shared-subset'));
jest.mock('react-noop-renderer/flight-modules', () => flightModules);
+ ReactServer = require('react');
+ ReactNoopFlightServer = require('react-noop-renderer/flight-server');
+
+ __unmockReact();
+ jest.resetModules();
+ jest.mock('react-noop-renderer/flight-modules', () => flightModules);
React = require('react');
ReactNoop = require('react-noop-renderer');
- ReactNoopFlightServer = require('react-noop-renderer/flight-server');
ReactNoopFlightClient = require('react-noop-renderer/flight-client');
act = require('internal-test-utils').act;
Scheduler = require('scheduler');
diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json
index fbe4d1c51ee27..739e045447ddf 100644
--- a/packages/react-dom/package.json
+++ b/packages/react-dom/package.json
@@ -42,6 +42,7 @@
"test-utils.js",
"unstable_testing.js",
"unstable_server-external-runtime.js",
+ "react-dom.shared-subset.js",
"cjs/",
"umd/"
],
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOM-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOM-test.js
index dc57bd0e94255..0a3773fa6f3e4 100644
--- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOM-test.js
+++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOM-test.js
@@ -62,7 +62,7 @@ describe('ReactFlightDOM', () => {
// This reset is to load modules for the SSR/Browser scope.
jest.resetModules();
- jest.unmock('react');
+ __unmockReact();
act = require('internal-test-utils').act;
Stream = require('stream');
React = require('react');
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMBrowser-test.js
index b8e95f8b84ea1..3aa7f712a0025 100644
--- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMBrowser-test.js
+++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMBrowser-test.js
@@ -15,147 +15,27 @@ global.ReadableStream =
global.TextEncoder = require('util').TextEncoder;
global.TextDecoder = require('util').TextDecoder;
-let clientExports;
-let serverExports;
-let turbopackMap;
-let turbopackServerMap;
-let act;
let React;
-let ReactDOM;
-let ReactDOMClient;
-let ReactDOMFizzServer;
let ReactServerDOMServer;
let ReactServerDOMClient;
-let Suspense;
-let use;
describe('ReactFlightDOMBrowser', () => {
beforeEach(() => {
jest.resetModules();
// Simulate the condition resolution
+ jest.mock('react', () => require('react/react.shared-subset'));
jest.mock('react-server-dom-turbopack/server', () =>
require('react-server-dom-turbopack/server.browser'),
);
- act = require('internal-test-utils').act;
- const TurbopackMock = require('./utils/TurbopackMock');
- clientExports = TurbopackMock.clientExports;
- serverExports = TurbopackMock.serverExports;
- turbopackMap = TurbopackMock.turbopackMap;
- turbopackServerMap = TurbopackMock.turbopackServerMap;
- React = require('react');
- ReactDOM = require('react-dom');
- ReactDOMClient = require('react-dom/client');
- ReactDOMFizzServer = require('react-dom/server.browser');
ReactServerDOMServer = require('react-server-dom-turbopack/server.browser');
- ReactServerDOMClient = require('react-server-dom-turbopack/client');
- Suspense = React.Suspense;
- use = React.use;
- });
-
- function makeDelayedText(Model) {
- let error, _resolve, _reject;
- let promise = new Promise((resolve, reject) => {
- _resolve = () => {
- promise = null;
- resolve();
- };
- _reject = e => {
- error = e;
- promise = null;
- reject(e);
- };
- });
- function DelayedText({children}, data) {
- if (promise) {
- throw promise;
- }
- if (error) {
- throw error;
- }
- return {children};
- }
- return [DelayedText, _resolve, _reject];
- }
-
- const theInfinitePromise = new Promise(() => {});
- function InfiniteSuspend() {
- throw theInfinitePromise;
- }
-
- function requireServerRef(ref) {
- let name = '';
- let resolvedModuleData = turbopackServerMap[ref];
- if (resolvedModuleData) {
- // The potentially aliased name.
- name = resolvedModuleData.name;
- } else {
- // We didn't find this specific export name but we might have the * export
- // which contains this name as well.
- // TODO: It's unfortunate that we now have to parse this string. We should
- // probably go back to encoding path and name separately on the client reference.
- const idx = ref.lastIndexOf('#');
- if (idx !== -1) {
- name = ref.slice(idx + 1);
- resolvedModuleData = turbopackServerMap[ref.slice(0, idx)];
- }
- if (!resolvedModuleData) {
- throw new Error(
- 'Could not find the module "' +
- ref +
- '" in the React Client Manifest. ' +
- 'This is probably a bug in the React Server Components bundler.',
- );
- }
- }
- const mod = __turbopack_require__(resolvedModuleData.id);
- if (name === '*') {
- return mod;
- }
- return mod[name];
- }
-
- async function callServer(actionId, body) {
- const fn = requireServerRef(actionId);
- const args = await ReactServerDOMServer.decodeReply(
- body,
- turbopackServerMap,
- );
- return fn.apply(null, args);
- }
- it('should resolve HTML using W3C streams', async () => {
- function Text({children}) {
- return {children};
- }
- function HTML() {
- return (
-
- hello
- world
-
- );
- }
-
- function App() {
- const model = {
- html: {getResponse()}
-
- );
- }
-
- // pausing to let Flight runtime tick. This is a test only artifact of the fact that
- // we aren't operating separate module graphs for flight and fiber. In a real app
- // each would have their own dispatcher and there would be no cross dispatching.
- await 1;
-
- let fizzStream;
- await act(async () => {
- fizzStream = await ReactDOMFizzServer.renderToReadableStream();
- });
-
- const decoder = new TextDecoder();
- const reader = fizzStream.getReader();
- let content = '';
- while (true) {
- const {done, value} = await reader.read();
- if (done) {
- content += decoder.decode();
- break;
- }
- content += decoder.decode(value, {stream: true});
- }
-
- expect(content).toEqual(
- '' +
- 'hello world
',
- );
- });
-
- // @gate enablePostpone
- it('supports postpone in Server Components', async () => {
- function Server() {
- React.unstable_postpone('testing postpone');
- return 'Not shown';
- }
-
- let postponed = null;
-
- const stream = ReactServerDOMServer.renderToReadableStream(
-
-
- ,
- null,
- {
- onPostpone(reason) {
- postponed = reason;
- },
- },
- );
- const response = ReactServerDOMClient.createFromReadableStream(stream);
-
- function Client() {
- return use(response);
- }
-
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await act(async () => {
- root.render(
-
- Shell:
-
,
- );
- });
- // We should have reserved the shell already. Which means that the Server
- // Component should've been a lazy component.
- expect(container.innerHTML).toContain('Shell:');
- expect(container.innerHTML).toContain('Loading...');
- expect(container.innerHTML).not.toContain('Not shown');
-
- expect(postponed).toBe('testing postpone');
- });
});
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMEdge-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMEdge-test.js
index 2dfdf97841bd7..3538b4f7a60d9 100644
--- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMEdge-test.js
+++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMEdge-test.js
@@ -33,6 +33,7 @@ describe('ReactFlightDOMEdge', () => {
jest.resetModules();
// Simulate the condition resolution
+ jest.mock('react', () => require('react/react.shared-subset'));
jest.mock('react-server-dom-turbopack/server', () =>
require('react-server-dom-turbopack/server.edge'),
);
@@ -41,9 +42,14 @@ describe('ReactFlightDOMEdge', () => {
clientExports = TurbopackMock.clientExports;
turbopackMap = TurbopackMock.turbopackMap;
turbopackModules = TurbopackMock.turbopackModules;
+
+ ReactServerDOMServer = require('react-server-dom-turbopack/server.edge');
+
+ jest.resetModules();
+ __unmockReact();
+
React = require('react');
ReactDOMServer = require('react-dom/server.edge');
- ReactServerDOMServer = require('react-server-dom-turbopack/server.edge');
ReactServerDOMClient = require('react-server-dom-turbopack/client.edge');
use = React.use;
});
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMForm-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMForm-test.js
index 4c99860e0bb2e..176003eb4c822 100644
--- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMForm-test.js
+++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMForm-test.js
@@ -33,14 +33,17 @@ describe('ReactFlightDOMForm', () => {
beforeEach(() => {
jest.resetModules();
// Simulate the condition resolution
+ jest.mock('react', () => require('react/react.shared-subset'));
jest.mock('react-server-dom-turbopack/server', () =>
require('react-server-dom-turbopack/server.edge'),
);
+ ReactServerDOMServer = require('react-server-dom-turbopack/server.edge');
const TurbopackMock = require('./utils/TurbopackMock');
serverExports = TurbopackMock.serverExports;
turbopackServerMap = TurbopackMock.turbopackServerMap;
+ __unmockReact();
+ jest.resetModules();
React = require('react');
- ReactServerDOMServer = require('react-server-dom-turbopack/server.edge');
ReactServerDOMClient = require('react-server-dom-turbopack/client.edge');
ReactDOMServer = require('react-dom/server.edge');
container = document.createElement('div');
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMNode-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMNode-test.js
index 5a7b14b3bbd4c..eebabfce5d0de 100644
--- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMNode-test.js
+++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMNode-test.js
@@ -42,7 +42,7 @@ describe('ReactFlightDOMNode', () => {
turbopackModuleLoading = TurbopackMock.moduleLoading;
jest.resetModules();
- jest.unmock('react');
+ __unmockReact();
jest.unmock('react-server-dom-turbopack/server');
jest.mock('react-server-dom-turbopack/client', () =>
require('react-server-dom-turbopack/client.node'),
diff --git a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMReply-test.js b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMReply-test.js
index d6c4c318d7b38..d8475a8762f23 100644
--- a/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMReply-test.js
+++ b/packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMReply-test.js
@@ -24,6 +24,7 @@ describe('ReactFlightDOMReply', () => {
beforeEach(() => {
jest.resetModules();
// Simulate the condition resolution
+ jest.mock('react', () => require('react/react.shared-subset'));
jest.mock('react-server-dom-turbopack/server', () =>
require('react-server-dom-turbopack/server.browser'),
);
@@ -31,6 +32,7 @@ describe('ReactFlightDOMReply', () => {
// serverExports = TurbopackMock.serverExports;
turbopackServerMap = TurbopackMock.turbopackServerMap;
ReactServerDOMServer = require('react-server-dom-turbopack/server.browser');
+ jest.resetModules();
ReactServerDOMClient = require('react-server-dom-turbopack/client');
});
diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
index e26db0fdceb45..eebca4710e954 100644
--- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
@@ -62,7 +62,7 @@ describe('ReactFlightDOM', () => {
// This reset is to load modules for the SSR/Browser scope.
jest.unmock('react-server-dom-webpack/server');
- jest.unmock('react');
+ __unmockReact();
jest.resetModules();
act = require('internal-test-utils').act;
Stream = require('stream');
diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
index af474dc148bf7..7eef417035aca 100644
--- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
+++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
@@ -28,27 +28,37 @@ let ReactServerDOMServer;
let ReactServerDOMClient;
let Suspense;
let use;
+let ReactServer;
+let ReactServerDOM;
describe('ReactFlightDOMBrowser', () => {
beforeEach(() => {
jest.resetModules();
// Simulate the condition resolution
+ jest.mock('react', () => require('react/react.shared-subset'));
jest.mock('react-server-dom-webpack/server', () =>
require('react-server-dom-webpack/server.browser'),
);
- act = require('internal-test-utils').act;
const WebpackMock = require('./utils/WebpackMock');
clientExports = WebpackMock.clientExports;
serverExports = WebpackMock.serverExports;
webpackMap = WebpackMock.webpackMap;
webpackServerMap = WebpackMock.webpackServerMap;
+
+ ReactServer = require('react');
+ ReactServerDOM = require('react-dom');
+ ReactServerDOMServer = require('react-server-dom-webpack/server.browser');
+
+ __unmockReact();
+ jest.resetModules();
+
+ act = require('internal-test-utils').act;
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
ReactDOMFizzServer = require('react-dom/server.browser');
- ReactServerDOMServer = require('react-server-dom-webpack/server.browser');
ReactServerDOMClient = require('react-server-dom-webpack/client');
Suspense = React.Suspense;
use = React.use;
@@ -583,9 +593,9 @@ describe('ReactFlightDOMBrowser', () => {
it('basic use(promise)', async () => {
function Server() {
return (
- use(Promise.resolve('A')) +
- use(Promise.resolve('B')) +
- use(Promise.resolve('C'))
+ ReactServer.use(Promise.resolve('A')) +
+ ReactServer.use(Promise.resolve('B')) +
+ ReactServer.use(Promise.resolve('C'))
);
}
@@ -627,7 +637,7 @@ describe('ReactFlightDOMBrowser', () => {
);
function ServerComponent() {
- return use(ContextA) + use(ContextB);
+ return ReactServer.use(ContextA) + ReactServer.use(ContextB);
}
function Server() {
return (
@@ -657,12 +667,21 @@ describe('ReactFlightDOMBrowser', () => {
it('use(promise) in multiple components', async () => {
function Child({prefix}) {
- return prefix + use(Promise.resolve('C')) + use(Promise.resolve('D'));
+ return (
+ prefix +
+ ReactServer.use(Promise.resolve('C')) +
+ ReactServer.use(Promise.resolve('D'))
+ );
}
function Parent() {
return (
-
+
);
}
@@ -695,7 +714,11 @@ describe('ReactFlightDOMBrowser', () => {
await expect(promiseB).rejects.toThrow('Oops!');
function Server() {
- return use(promiseA) + use(promiseB) + use(promiseC);
+ return (
+ ReactServer.use(promiseA) +
+ ReactServer.use(promiseB) +
+ ReactServer.use(promiseC)
+ );
}
const reportedErrors = [];
@@ -755,7 +778,7 @@ describe('ReactFlightDOMBrowser', () => {
// This will never suspend because the thenable already resolved
function Server() {
- return use(thenable);
+ return ReactServer.use(thenable);
}
const stream = ReactServerDOMServer.renderToReadableStream();
@@ -783,7 +806,7 @@ describe('ReactFlightDOMBrowser', () => {
},
};
try {
- return use(thenable);
+ return ReactServer.use(thenable);
} catch {
throw new Error(
'`use` should not suspend because the thenable resolved synchronously.',
@@ -1087,9 +1110,9 @@ describe('ReactFlightDOMBrowser', () => {
const ClientComponent = clientExports(Component);
async function ServerComponent() {
- ReactDOM.preload('before', {as: 'style'});
+ ReactServerDOM.preload('before', {as: 'style'});
await 1;
- ReactDOM.preload('after', {as: 'style'});
+ ReactServerDOM.preload('after', {as: 'style'});
return ;
}
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 22ee0696027b1..a81ec52583fe6 100644
--- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js
+++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js
@@ -38,7 +38,6 @@ describe('ReactFlightDOMEdge', () => {
jest.mock('react-server-dom-webpack/server', () =>
require('react-server-dom-webpack/server.edge'),
);
- ReactServerDOMServer = require('react-server-dom-webpack/server');
const WebpackMock = require('./utils/WebpackMock');
@@ -47,8 +46,10 @@ describe('ReactFlightDOMEdge', () => {
webpackModules = WebpackMock.webpackModules;
webpackModuleLoading = WebpackMock.moduleLoading;
+ ReactServerDOMServer = require('react-server-dom-webpack/server');
+
jest.resetModules();
- jest.unmock('react');
+ __unmockReact();
jest.unmock('react-server-dom-webpack/server');
jest.mock('react-server-dom-webpack/client', () =>
require('react-server-dom-webpack/client.edge'),
diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js
index 4de3f5528dce8..1ba0e100740f3 100644
--- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js
+++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js
@@ -38,16 +38,19 @@ describe('ReactFlightDOMForm', () => {
beforeEach(() => {
jest.resetModules();
// Simulate the condition resolution
+ jest.mock('react', () => require('react/react.shared-subset'));
jest.mock('react-server-dom-webpack/server', () =>
require('react-server-dom-webpack/server.edge'),
);
+ ReactServerDOMServer = require('react-server-dom-webpack/server.edge');
const WebpackMock = require('./utils/WebpackMock');
clientExports = WebpackMock.clientExports;
serverExports = WebpackMock.serverExports;
webpackMap = WebpackMock.webpackMap;
webpackServerMap = WebpackMock.webpackServerMap;
+ __unmockReact();
+ jest.resetModules();
React = require('react');
- ReactServerDOMServer = require('react-server-dom-webpack/server.edge');
ReactServerDOMClient = require('react-server-dom-webpack/client.edge');
ReactDOMServer = require('react-dom/server.edge');
ReactDOMClient = require('react-dom/client');
diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js
index 490c321689d17..6fd51a5d1fb49 100644
--- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js
+++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js
@@ -42,7 +42,7 @@ describe('ReactFlightDOMNode', () => {
webpackModuleLoading = WebpackMock.moduleLoading;
jest.resetModules();
- jest.unmock('react');
+ __unmockReact();
jest.unmock('react-server-dom-webpack/server');
jest.mock('react-server-dom-webpack/client', () =>
require('react-server-dom-webpack/client.node'),
diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js
index 894f444640558..019d28a00ce98 100644
--- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js
+++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js
@@ -24,6 +24,7 @@ describe('ReactFlightDOMReply', () => {
beforeEach(() => {
jest.resetModules();
// Simulate the condition resolution
+ jest.mock('react', () => require('react/react.shared-subset'));
jest.mock('react-server-dom-webpack/server', () =>
require('react-server-dom-webpack/server.browser'),
);
@@ -31,6 +32,7 @@ describe('ReactFlightDOMReply', () => {
// serverExports = WebpackMock.serverExports;
webpackServerMap = WebpackMock.webpackServerMap;
ReactServerDOMServer = require('react-server-dom-webpack/server.browser');
+ jest.resetModules();
ReactServerDOMClient = require('react-server-dom-webpack/client');
});
diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js
index 3e73253e26ab0..230994011fb93 100644
--- a/packages/react-server/src/ReactFlightServer.js
+++ b/packages/react-server/src/ReactFlightServer.js
@@ -104,7 +104,7 @@ import {
} from 'shared/ReactSerializationErrors';
import {getOrCreateServerContext} from 'shared/ReactServerContextRegistry';
-import ReactSharedInternals from 'shared/ReactSharedInternals';
+import ReactServerSharedInternals from './ReactServerSharedInternals';
import isArray from 'shared/isArray';
import {SuspenseException, getSuspendedThenable} from './ReactFlightThenable';
@@ -197,8 +197,9 @@ export type Request = {
toJSON: (key: string, value: ReactClientValue) => ReactJSONValue,
};
-const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
-const ReactCurrentCache = ReactSharedInternals.ReactCurrentCache;
+const ReactCurrentDispatcher =
+ ReactServerSharedInternals.ReactCurrentDispatcher;
+const ReactCurrentCache = ReactServerSharedInternals.ReactCurrentCache;
function defaultErrorHandler(error: mixed) {
console['error'](error);
diff --git a/packages/react-server/src/ReactServerSharedInternals.js b/packages/react-server/src/ReactServerSharedInternals.js
new file mode 100644
index 0000000000000..6d1a5a7c6858a
--- /dev/null
+++ b/packages/react-server/src/ReactServerSharedInternals.js
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import * as React from 'react';
+
+const ReactSharedServerInternals =
+ // $FlowFixMe: It's defined in the one we resolve to.
+ React.__SECRET_SERVER_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
+
+if (!ReactSharedServerInternals) {
+ throw new Error(
+ 'The "react" package in this environment is not configured correctly. ' +
+ 'The "react-server" condition must be enabled in any environment that ' +
+ 'runs React Server Components.',
+ );
+}
+
+export default ReactSharedServerInternals;
diff --git a/packages/react/src/React.js b/packages/react/src/React.js
index b45a0cda05370..9c0cda47a8de7 100644
--- a/packages/react/src/React.js
+++ b/packages/react/src/React.js
@@ -67,7 +67,7 @@ import {
cloneElementWithValidation,
} from './ReactElementValidator';
import {createServerContext} from './ReactServerContext';
-import ReactSharedInternals from './ReactSharedInternals';
+import ReactSharedInternals from './ReactSharedInternalsClient';
import {startTransition} from './ReactStartTransition';
import {act} from './ReactAct';
diff --git a/packages/react/src/ReactServerContext.js b/packages/react/src/ReactServerContext.js
index 3748a05b1262d..e2ed9bea7d0d4 100644
--- a/packages/react/src/ReactServerContext.js
+++ b/packages/react/src/ReactServerContext.js
@@ -19,9 +19,7 @@ import type {
} from 'shared/ReactTypes';
import {enableServerContext} from 'shared/ReactFeatureFlags';
-import ReactSharedInternals from 'shared/ReactSharedInternals';
-
-const ContextRegistry = ReactSharedInternals.ContextRegistry;
+import {ContextRegistry} from './ReactServerContextRegistry';
export function createServerContext(
globalName: string,
diff --git a/packages/react/src/ReactServerSharedInternals.js b/packages/react/src/ReactServerSharedInternals.js
new file mode 100644
index 0000000000000..3e9b81f4ec149
--- /dev/null
+++ b/packages/react/src/ReactServerSharedInternals.js
@@ -0,0 +1,16 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import ReactCurrentDispatcher from './ReactCurrentDispatcher';
+import ReactCurrentCache from './ReactCurrentCache';
+
+const ReactServerSharedInternals = {
+ ReactCurrentDispatcher,
+ ReactCurrentCache,
+};
+
+export default ReactServerSharedInternals;
diff --git a/packages/react/src/ReactSharedInternals.js b/packages/react/src/ReactSharedInternalsClient.js
similarity index 100%
rename from packages/react/src/ReactSharedInternals.js
rename to packages/react/src/ReactSharedInternalsClient.js
diff --git a/packages/react/src/ReactSharedInternalsServer.js b/packages/react/src/ReactSharedInternalsServer.js
new file mode 100644
index 0000000000000..5e4aa64c3fd1a
--- /dev/null
+++ b/packages/react/src/ReactSharedInternalsServer.js
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import ReactCurrentOwner from './ReactCurrentOwner';
+import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';
+import {enableServerContext} from 'shared/ReactFeatureFlags';
+import {ContextRegistry} from './ReactServerContextRegistry';
+
+const ReactSharedInternals = {
+ ReactCurrentOwner,
+};
+
+if (__DEV__) {
+ ReactSharedInternals.ReactDebugCurrentFrame = ReactDebugCurrentFrame;
+}
+
+if (enableServerContext) {
+ ReactSharedInternals.ContextRegistry = ContextRegistry;
+}
+
+export default ReactSharedInternals;
diff --git a/packages/react/src/ReactSharedSubset.experimental.js b/packages/react/src/ReactSharedSubset.experimental.js
index ed5340df9de03..80d50805c23b4 100644
--- a/packages/react/src/ReactSharedSubset.experimental.js
+++ b/packages/react/src/ReactSharedSubset.experimental.js
@@ -10,8 +10,11 @@
// Patch fetch
import './ReactFetch';
+export {default as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './ReactSharedInternalsServer';
+
+export {default as __SECRET_SERVER_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './ReactServerSharedInternals';
+
export {
- __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
Children,
Fragment,
Profiler,
diff --git a/packages/react/src/ReactSharedSubset.js b/packages/react/src/ReactSharedSubset.js
index c880cc9ada182..1bc2b3036f455 100644
--- a/packages/react/src/ReactSharedSubset.js
+++ b/packages/react/src/ReactSharedSubset.js
@@ -10,8 +10,11 @@
// Patch fetch
import './ReactFetch';
+export {default as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './ReactSharedInternalsServer';
+
+export {default as __SECRET_SERVER_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './ReactServerSharedInternals';
+
export {
- __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
Children,
Fragment,
Profiler,
diff --git a/packages/react/src/__tests__/ReactFetch-test.js b/packages/react/src/__tests__/ReactFetch-test.js
index c778f7e6162d7..e91c6e09941d8 100644
--- a/packages/react/src/__tests__/ReactFetch-test.js
+++ b/packages/react/src/__tests__/ReactFetch-test.js
@@ -32,6 +32,7 @@ async function fetchMock(resource, options) {
}
let React;
+let ReactServer;
let ReactServerDOMServer;
let ReactServerDOMClient;
let use;
@@ -43,23 +44,21 @@ describe('ReactFetch', () => {
fetchCount = 0;
global.fetch = fetchMock;
- if (gate(flags => !flags.www)) {
- jest.mock('react', () => require('react/react.shared-subset'));
- }
+ jest.mock('react', () => require('react/react.shared-subset'));
jest.mock('react-server-dom-webpack/server', () =>
require('react-server-dom-webpack/server.browser'),
);
require('react-server-dom-webpack/src/__tests__/utils/WebpackMock');
-
- React = require('react');
ReactServerDOMServer = require('react-server-dom-webpack/server');
+ ReactServer = require('react');
jest.resetModules();
- jest.unmock('react');
+ __unmockReact();
jest.unmock('react-server-dom-webpack/server');
ReactServerDOMClient = require('react-server-dom-webpack/client');
- use = React.use;
- cache = React.cache;
+ React = require('react');
+ use = ReactServer.use;
+ cache = ReactServer.cache;
});
async function render(Component) {
diff --git a/packages/react/src/__tests__/ReactFetchEdge-test.js b/packages/react/src/__tests__/ReactFetchEdge-test.js
index 9f9555251823e..b4e313faa728f 100644
--- a/packages/react/src/__tests__/ReactFetchEdge-test.js
+++ b/packages/react/src/__tests__/ReactFetchEdge-test.js
@@ -48,9 +48,7 @@ describe('ReactFetch', () => {
fetchCount = 0;
global.fetch = fetchMock;
- if (gate(flags => !flags.www)) {
- jest.mock('react', () => require('react/react.shared-subset'));
- }
+ jest.mock('react', () => require('react/react.shared-subset'));
jest.mock('react-server-dom-webpack/server', () =>
require('react-server-dom-webpack/server.edge'),
);
@@ -60,7 +58,7 @@ describe('ReactFetch', () => {
ReactServerDOMServer = require('react-server-dom-webpack/server');
jest.resetModules();
- jest.unmock('react');
+ __unmockReact();
jest.unmock('react-server-dom-webpack/server');
ReactServerDOMClient = require('react-server-dom-webpack/client');
use = React.use;
diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json
index dae06b5c68079..43d448ca977b4 100644
--- a/scripts/error-codes/codes.json
+++ b/scripts/error-codes/codes.json
@@ -476,5 +476,6 @@
"488": "Couldn't find all resumable slots by key/index during replaying. The tree doesn't match so React will fallback to client rendering.",
"489": "Expected to see a component of type \"%s\" in this slot. The tree doesn't match so React will fallback to client rendering.",
"490": "Expected to see a Suspense boundary in this slot. The tree doesn't match so React will fallback to client rendering.",
- "491": "It should not be possible to postpone both at the root of an element as well as a slot below. This is a bug in React."
+ "491": "It should not be possible to postpone both at the root of an element as well as a slot below. This is a bug in React.",
+ "492": "The \"react\" package in this environment is not configured correctly. The \"react-server\" condition must be enabled in any environment that runs React Server Components."
}
\ No newline at end of file
diff --git a/scripts/jest/setupHostConfigs.js b/scripts/jest/setupHostConfigs.js
index 48eaf0fd5b731..b296f65224eb7 100644
--- a/scripts/jest/setupHostConfigs.js
+++ b/scripts/jest/setupHostConfigs.js
@@ -43,13 +43,20 @@ function resolveEntryFork(resolvedEntry, isFBBundle) {
return resolvedEntry;
}
-jest.mock('react', () => {
- const resolvedEntryPoint = resolveEntryFork(
- require.resolve('react'),
- global.__WWW__
- );
- return jest.requireActual(resolvedEntryPoint);
-});
+function mockReact() {
+ jest.mock('react', () => {
+ const resolvedEntryPoint = resolveEntryFork(
+ require.resolve('react'),
+ global.__WWW__
+ );
+ return jest.requireActual(resolvedEntryPoint);
+ });
+}
+
+// When we want to unmock React we really need to mock it again.
+global.__unmockReact = mockReact;
+
+mockReact();
jest.mock('react/react.shared-subset', () => {
const resolvedEntryPoint = resolveEntryFork(
@@ -162,7 +169,7 @@ inlinedHostConfigs.forEach(rendererInfo => {
// Make it possible to import this module inside
// the React package itself.
jest.mock('shared/ReactSharedInternals', () =>
- jest.requireActual('react/src/ReactSharedInternals')
+ jest.requireActual('react/src/ReactSharedInternalsClient')
);
// Make it possible to import this module inside
diff --git a/scripts/jest/setupTests.build.js b/scripts/jest/setupTests.build.js
index d7014c7720e42..2759ddae5404f 100644
--- a/scripts/jest/setupTests.build.js
+++ b/scripts/jest/setupTests.build.js
@@ -1,3 +1,5 @@
'use strict';
jest.mock('scheduler', () => jest.requireActual('scheduler/unstable_mock'));
+
+global.__unmockReact = () => jest.unmock('react');
diff --git a/scripts/rollup/forks.js b/scripts/rollup/forks.js
index d46dbb2b85c45..fa9a232bef40c 100644
--- a/scripts/rollup/forks.js
+++ b/scripts/rollup/forks.js
@@ -60,8 +60,11 @@ const forks = Object.freeze({
entry,
dependencies
) => {
- if (entry === 'react' || entry === 'react/src/ReactSharedSubset.js') {
- return './packages/react/src/ReactSharedInternals.js';
+ if (entry === 'react') {
+ return './packages/react/src/ReactSharedInternalsClient.js';
+ }
+ if (entry === 'react/src/ReactSharedSubset.js') {
+ return './packages/react/src/ReactSharedInternalsServer.js';
}
if (!entry.startsWith('react/') && dependencies.indexOf('react') === -1) {
// React internals are unavailable if we can't reference the package.