@samitouri / QOS-React-1 / commits / c7ba8c0988

Enforce that the "react-server" build of "react" is used (#27436)

I do this by simply renaming the secret export name in the "subset" bundle and this renamed version is what the FlightServer uses. This requires us to be more diligent about always using the correct instance of "react" in our tests so there's a bunch of clean up for that.

Sebastian Markbåge committed Sep 29, 2023 at 18:24 UTC c7ba8c098889b6dc47fa9c807bbba3975a658584
30 files changed +245 -1258
.eslintrc.js
+1
@@ -517,6 +517,7 @@ module.exports = {
517 __TEST__: 'readonly',
518 __UMD__: 'readonly',
519 __VARIANT__: 'readonly',
520 + __unmockReact: 'readonly',
521 gate: 'readonly',
522 trustedTypes: 'readonly',
523 IS_REACT_ACT_ENVIRONMENT: 'readonly',
packages/react-client/src/__tests__/ReactFlight-test.js
+64 -30
@@ -14,6 +14,7 @@ let act;
14 let use;
15 let startTransition;
16 let React;
17 +let ReactServer;
18 let ReactNoop;
19 let ReactNoopFlightServer;
20 let ReactNoopFlightClient;
@@ -25,12 +26,18 @@ let assertLog;
26 describe('ReactFlight', () => {
27 beforeEach(() => {
28 jest.resetModules();
28 -
29 + jest.mock('react', () => require('react/react.shared-subset'));
30 + ReactServer = require('react');
31 + ReactNoopFlightServer = require('react-noop-renderer/flight-server');
32 + // This stores the state so we need to preserve it
33 + const flightModules = require('react-noop-renderer/flight-modules');
34 + __unmockReact();
35 + jest.resetModules();
36 + jest.mock('react-noop-renderer/flight-modules', () => flightModules);
37 React = require('react');
38 startTransition = React.startTransition;
39 use = React.use;
40 ReactNoop = require('react-noop-renderer');
33 - ReactNoopFlightServer = require('react-noop-renderer/flight-server');
41 ReactNoopFlightClient = require('react-noop-renderer/flight-client');
42 act = require('internal-test-utils').act;
43 Scheduler = require('scheduler');
@@ -111,6 +118,19 @@ describe('ReactFlight', () => {
118 return ctx;
119 }
120
121 + function createServerServerContext(globalName, defaultValue, withStack) {
122 + let ctx;
123 + expect(() => {
124 + ctx = ReactServer.createServerContext(globalName, defaultValue);
125 + }).toErrorDev(
126 + 'Server Context is deprecated and will soon be removed. ' +
127 + 'It was never documented and we have found it not to be useful ' +
128 + 'enough to warrant the downside it imposes on all apps.',
129 + {withoutStack: !withStack},
130 + );
131 + return ctx;
132 + }
133 +
134 function clientReference(value) {
135 return Object.defineProperties(
136 function () {
@@ -970,7 +990,7 @@ describe('ReactFlight', () => {
990 const Context = React.createContext();
991 const ClientContext = clientReference(Context);
992 function ServerComponent() {
973 - return React.useContext(ClientContext);
993 + return ReactServer.useContext(ClientContext);
994 }
995 expect(() => {
996 const transport = ReactNoopFlightServer.render(<ServerComponent />);
@@ -982,7 +1002,7 @@ describe('ReactFlight', () => {
1002
1003 describe('Hooks', () => {
1004 function DivWithId({children}) {
985 - const id = React.useId();
1005 + const id = ReactServer.useId();
1006 return <div prop={id}>{children}</div>;
1007 }
1008
@@ -1039,7 +1059,7 @@ describe('ReactFlight', () => {
1059 // 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
1060 // we just accept that it is a nuance of useId in Flight
1061 function App() {
1042 - const id = React.useId();
1062 + const id = ReactServer.useId();
1063 const div = <div prop={id}>{id}</div>;
1064 return <ClientDoublerModuleRef el={div} />;
1065 }
@@ -1076,19 +1096,17 @@ describe('ReactFlight', () => {
1096 describe('ServerContext', () => {
1097 // @gate enableServerContext
1098 it('supports basic createServerContext usage', async () => {
1079 - const ServerContext = createServerContext(
1099 + const ServerContext = createServerServerContext(
1100 'ServerContext',
1101 'hello from server',
1102 );
1103 function Foo() {
1084 - const context = React.useContext(ServerContext);
1104 + const context = ReactServer.useContext(ServerContext);
1105 return <div>{context}</div>;
1106 }
1107
1108 const transport = ReactNoopFlightServer.render(<Foo />);
1109 await act(async () => {
1090 - ServerContext._currentRenderer = null;
1091 - ServerContext._currentRenderer2 = null;
1110 ReactNoop.render(await ReactNoopFlightClient.read(transport));
1111 });
1112
@@ -1097,7 +1115,10 @@ describe('ReactFlight', () => {
1115
1116 // @gate enableServerContext
1117 it('propagates ServerContext providers in flight', async () => {
1100 - const ServerContext = createServerContext('ServerContext', 'default');
1118 + const ServerContext = createServerServerContext(
1119 + 'ServerContext',
1120 + 'default',
1121 + );
1122
1123 function Foo() {
1124 return (
@@ -1109,14 +1130,12 @@ describe('ReactFlight', () => {
1130 );
1131 }
1132 function Bar() {
1112 - const context = React.useContext(ServerContext);
1133 + const context = ReactServer.useContext(ServerContext);
1134 return context;
1135 }
1136
1137 const transport = ReactNoopFlightServer.render(<Foo />);
1138 await act(async () => {
1118 - ServerContext._currentRenderer = null;
1119 - ServerContext._currentRenderer2 = null;
1139 ReactNoop.render(await ReactNoopFlightClient.read(transport));
1140 });
1141
@@ -1125,7 +1144,7 @@ describe('ReactFlight', () => {
1144
1145 // @gate enableServerContext
1146 it('errors if you try passing JSX through ServerContext value', () => {
1128 - const ServerContext = createServerContext('ServerContext', {
1147 + const ServerContext = createServerServerContext('ServerContext', {
1148 foo: {
1149 bar: <span>hi this is default</span>,
1150 },
@@ -1146,7 +1165,7 @@ describe('ReactFlight', () => {
1165 );
1166 }
1167 function Bar() {
1149 - const context = React.useContext(ServerContext);
1168 + const context = ReactServer.useContext(ServerContext);
1169 return context.foo.bar;
1170 }
1171
@@ -1159,7 +1178,10 @@ describe('ReactFlight', () => {
1178
1179 // @gate enableServerContext
1180 it('propagates ServerContext and cleans up the providers in flight', async () => {
1162 - const ServerContext = createServerContext('ServerContext', 'default');
1181 + const ServerContext = createServerServerContext(
1182 + 'ServerContext',
1183 + 'default',
1184 + );
1185
1186 function Foo() {
1187 return (
@@ -1181,7 +1203,7 @@ describe('ReactFlight', () => {
1203 );
1204 }
1205 function Bar() {
1184 - const context = React.useContext(ServerContext);
1206 + const context = ReactServer.useContext(ServerContext);
1207 return <span>{context}</span>;
1208 }
1209
@@ -1203,7 +1225,10 @@ describe('ReactFlight', () => {
1225
1226 // @gate enableServerContext
1227 it('propagates ServerContext providers in flight after suspending', async () => {
1206 - const ServerContext = createServerContext('ServerContext', 'default');
1228 + const ServerContext = createServerServerContext(
1229 + 'ServerContext',
1230 + 'default',
1231 + );
1232
1233 function Foo() {
1234 return (
@@ -1231,7 +1256,7 @@ describe('ReactFlight', () => {
1256 throw promise;
1257 }
1258 Scheduler.log('rendered');
1234 - const context = React.useContext(ServerContext);
1259 + const context = ReactServer.useContext(ServerContext);
1260 return context;
1261 }
1262
@@ -1248,8 +1273,6 @@ describe('ReactFlight', () => {
1273 assertLog(['rendered']);
1274
1275 await act(async () => {
1251 - ServerContext._currentRenderer = null;
1252 - ServerContext._currentRenderer2 = null;
1276 ReactNoop.render(await ReactNoopFlightClient.read(transport));
1277 });
1278
@@ -1258,11 +1281,15 @@ describe('ReactFlight', () => {
1281
1282 // @gate enableServerContext
1283 it('serializes ServerContext to client', async () => {
1261 - const ServerContext = createServerContext('ServerContext', 'default');
1284 + const ServerContext = createServerServerContext(
1285 + 'ServerContext',
1286 + 'default',
1287 + );
1288 + const ClientContext = createServerContext('ServerContext', 'default');
1289
1290 function ClientBar() {
1291 Scheduler.log('ClientBar');
1265 - const context = React.useContext(ServerContext);
1292 + const context = React.useContext(ClientContext);
1293 return <span>{context}</span>;
1294 }
1295
@@ -1285,8 +1312,6 @@ describe('ReactFlight', () => {
1312 assertLog([]);
1313
1314 await act(async () => {
1288 - ServerContext._currentRenderer = null;
1289 - ServerContext._currentRenderer2 = null;
1315 const flightModel = await ReactNoopFlightClient.read(transport);
1316 ReactNoop.render(flightModel.foo);
1317 });
@@ -1301,9 +1326,12 @@ describe('ReactFlight', () => {
1326
1327 // @gate enableServerContext
1328 it('takes ServerContext from the client for refetching use cases', async () => {
1304 - const ServerContext = createServerContext('ServerContext', 'default');
1329 + const ServerContext = createServerServerContext(
1330 + 'ServerContext',
1331 + 'default',
1332 + );
1333 function Bar() {
1306 - return <span>{React.useContext(ServerContext)}</span>;
1334 + return <span>{ReactServer.useContext(ServerContext)}</span>;
1335 }
1336 const transport = ReactNoopFlightServer.render(<Bar />, {
1337 context: [['ServerContext', 'Override']],
@@ -1321,7 +1349,7 @@ describe('ReactFlight', () => {
1349 let ServerContext;
1350 function inlineLazyServerContextInitialization() {
1351 if (!ServerContext) {
1324 - ServerContext = createServerContext('ServerContext', 'default');
1352 + ServerContext = createServerServerContext('ServerContext', 'default');
1353 }
1354 return ServerContext;
1355 }
@@ -1346,7 +1374,7 @@ describe('ReactFlight', () => {
1374 return (
1375 <article>
1376 <div>
1349 - {React.useContext(inlineLazyServerContextInitialization())}
1377 + {ReactServer.useContext(inlineLazyServerContextInitialization())}
1378 </div>
1379 <Baz />
1380 </article>
@@ -1381,11 +1409,17 @@ describe('ReactFlight', () => {
1409 // Reset all modules, except flight-modules which keeps the registry of Client Components
1410 const flightModules = require('react-noop-renderer/flight-modules');
1411 jest.resetModules();
1412 + jest.mock('react', () => require('react/react.shared-subset'));
1413 jest.mock('react-noop-renderer/flight-modules', () => flightModules);
1414
1415 + ReactServer = require('react');
1416 + ReactNoopFlightServer = require('react-noop-renderer/flight-server');
1417 +
1418 + __unmockReact();
1419 + jest.resetModules();
1420 + jest.mock('react-noop-renderer/flight-modules', () => flightModules);
1421 React = require('react');
1422 ReactNoop = require('react-noop-renderer');
1388 - ReactNoopFlightServer = require('react-noop-renderer/flight-server');
1423 ReactNoopFlightClient = require('react-noop-renderer/flight-client');
1424 act = require('internal-test-utils').act;
1425 Scheduler = require('scheduler');
packages/react-dom/package.json
+1
@@ -42,6 +42,7 @@
42 "test-utils.js",
43 "unstable_testing.js",
44 "unstable_server-external-runtime.js",
45 + "react-dom.shared-subset.js",
46 "cjs/",
47 "umd/"
48 ],
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOM-test.js
+1 -1
@@ -62,7 +62,7 @@ describe('ReactFlightDOM', () => {
62
63 // This reset is to load modules for the SSR/Browser scope.
64 jest.resetModules();
65 - jest.unmock('react');
65 + __unmockReact();
66 act = require('internal-test-utils').act;
67 Stream = require('stream');
68 React = require('react');
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMBrowser-test.js
+5 -1174
@@ -15,147 +15,27 @@ global.ReadableStream =
15 global.TextEncoder = require('util').TextEncoder;
16 global.TextDecoder = require('util').TextDecoder;
17
18 -let clientExports;
19 -let serverExports;
20 -let turbopackMap;
21 -let turbopackServerMap;
22 -let act;
18 let React;
24 -let ReactDOM;
25 -let ReactDOMClient;
26 -let ReactDOMFizzServer;
19 let ReactServerDOMServer;
20 let ReactServerDOMClient;
29 -let Suspense;
30 -let use;
21
22 describe('ReactFlightDOMBrowser', () => {
23 beforeEach(() => {
24 jest.resetModules();
25
26 // Simulate the condition resolution
27 + jest.mock('react', () => require('react/react.shared-subset'));
28 jest.mock('react-server-dom-turbopack/server', () =>
29 require('react-server-dom-turbopack/server.browser'),
30 );
31
41 - act = require('internal-test-utils').act;
42 - const TurbopackMock = require('./utils/TurbopackMock');
43 - clientExports = TurbopackMock.clientExports;
44 - serverExports = TurbopackMock.serverExports;
45 - turbopackMap = TurbopackMock.turbopackMap;
46 - turbopackServerMap = TurbopackMock.turbopackServerMap;
47 - React = require('react');
48 - ReactDOM = require('react-dom');
49 - ReactDOMClient = require('react-dom/client');
50 - ReactDOMFizzServer = require('react-dom/server.browser');
32 ReactServerDOMServer = require('react-server-dom-turbopack/server.browser');
52 - ReactServerDOMClient = require('react-server-dom-turbopack/client');
53 - Suspense = React.Suspense;
54 - use = React.use;
55 - });
56 -
57 - function makeDelayedText(Model) {
58 - let error, _resolve, _reject;
59 - let promise = new Promise((resolve, reject) => {
60 - _resolve = () => {
61 - promise = null;
62 - resolve();
63 - };
64 - _reject = e => {
65 - error = e;
66 - promise = null;
67 - reject(e);
68 - };
69 - });
70 - function DelayedText({children}, data) {
71 - if (promise) {
72 - throw promise;
73 - }
74 - if (error) {
75 - throw error;
76 - }
77 - return <Model>{children}</Model>;
78 - }
79 - return [DelayedText, _resolve, _reject];
80 - }
81 -
82 - const theInfinitePromise = new Promise(() => {});
83 - function InfiniteSuspend() {
84 - throw theInfinitePromise;
85 - }
86 -
87 - function requireServerRef(ref) {
88 - let name = '';
89 - let resolvedModuleData = turbopackServerMap[ref];
90 - if (resolvedModuleData) {
91 - // The potentially aliased name.
92 - name = resolvedModuleData.name;
93 - } else {
94 - // We didn't find this specific export name but we might have the * export
95 - // which contains this name as well.
96 - // TODO: It's unfortunate that we now have to parse this string. We should
97 - // probably go back to encoding path and name separately on the client reference.
98 - const idx = ref.lastIndexOf('#');
99 - if (idx !== -1) {
100 - name = ref.slice(idx + 1);
101 - resolvedModuleData = turbopackServerMap[ref.slice(0, idx)];
102 - }
103 - if (!resolvedModuleData) {
104 - throw new Error(
105 - 'Could not find the module "' +
106 - ref +
107 - '" in the React Client Manifest. ' +
108 - 'This is probably a bug in the React Server Components bundler.',
109 - );
110 - }
111 - }
112 - const mod = __turbopack_require__(resolvedModuleData.id);
113 - if (name === '*') {
114 - return mod;
115 - }
116 - return mod[name];
117 - }
118 -
119 - async function callServer(actionId, body) {
120 - const fn = requireServerRef(actionId);
121 - const args = await ReactServerDOMServer.decodeReply(
122 - body,
123 - turbopackServerMap,
124 - );
125 - return fn.apply(null, args);
126 - }
33
128 - it('should resolve HTML using W3C streams', async () => {
129 - function Text({children}) {
130 - return <span>{children}</span>;
131 - }
132 - function HTML() {
133 - return (
134 - <div>
135 - <Text>hello</Text>
136 - <Text>world</Text>
137 - </div>
138 - );
139 - }
140 -
141 - function App() {
142 - const model = {
143 - html: <HTML />,
144 - };
145 - return model;
146 - }
34 + __unmockReact();
35 + jest.resetModules();
36
148 - const stream = ReactServerDOMServer.renderToReadableStream(<App />);
149 - const response = ReactServerDOMClient.createFromReadableStream(stream);
150 - const model = await response;
151 - expect(model).toEqual({
152 - html: (
153 - <div>
154 - <span>hello</span>
155 - <span>world</span>
156 - </div>
157 - ),
158 - });
37 + React = require('react');
38 + ReactServerDOMClient = require('react-server-dom-turbopack/client');
39 });
40
41 it('should resolve HTML using W3C streams', async () => {
@@ -190,1053 +70,4 @@ describe('ReactFlightDOMBrowser', () => {
70 ),
71 });
72 });
193 -
194 - it('should progressively reveal server components', async () => {
195 - let reportedErrors = [];
196 -
197 - // Client Components
198 -
199 - class ErrorBoundary extends React.Component {
200 - state = {hasError: false, error: null};
201 - static getDerivedStateFromError(error) {
202 - return {
203 - hasError: true,
204 - error,
205 - };
206 - }
207 - render() {
208 - if (this.state.hasError) {
209 - return this.props.fallback(this.state.error);
210 - }
211 - return this.props.children;
212 - }
213 - }
214 -
215 - let errorBoundaryFn;
216 - if (__DEV__) {
217 - errorBoundaryFn = e => (
218 - <p>
219 - {e.message} + {e.digest}
220 - </p>
221 - );
222 - } else {
223 - errorBoundaryFn = e => {
224 - expect(e.message).toBe(
225 - 'An error occurred in the Server Components render. The specific message is omitted in production' +
226 - ' builds to avoid leaking sensitive details. A digest property is included on this error instance which' +
227 - ' may provide additional details about the nature of the error.',
228 - );
229 - return <p>{e.digest}</p>;
230 - };
231 - }
232 -
233 - function MyErrorBoundary({children}) {
234 - return (
235 - <ErrorBoundary fallback={errorBoundaryFn}>{children}</ErrorBoundary>
236 - );
237 - }
238 -
239 - // Model
240 - function Text({children}) {
241 - return children;
242 - }
243 -
244 - const [Friends, resolveFriends] = makeDelayedText(Text);
245 - const [Name, resolveName] = makeDelayedText(Text);
246 - const [Posts, resolvePosts] = makeDelayedText(Text);
247 - const [Photos, resolvePhotos] = makeDelayedText(Text);
248 - const [Games, , rejectGames] = makeDelayedText(Text);
249 -
250 - // View
251 - function ProfileDetails({avatar}) {
252 - return (
253 - <div>
254 - <Name>:name:</Name>
255 - {avatar}
256 - </div>
257 - );
258 - }
259 - function ProfileSidebar({friends}) {
260 - return (
261 - <div>
262 - <Photos>:photos:</Photos>
263 - {friends}
264 - </div>
265 - );
266 - }
267 - function ProfilePosts({posts}) {
268 - return <div>{posts}</div>;
269 - }
270 - function ProfileGames({games}) {
271 - return <div>{games}</div>;
272 - }
273 -
274 - const MyErrorBoundaryClient = clientExports(MyErrorBoundary);
275 -
276 - function ProfileContent() {
277 - return (
278 - <>
279 - <ProfileDetails avatar={<Text>:avatar:</Text>} />
280 - <Suspense fallback={<p>(loading sidebar)</p>}>
281 - <ProfileSidebar friends={<Friends>:friends:</Friends>} />
282 - </Suspense>
283 - <Suspense fallback={<p>(loading posts)</p>}>
284 - <ProfilePosts posts={<Posts>:posts:</Posts>} />
285 - </Suspense>
286 - <MyErrorBoundaryClient>
287 - <Suspense fallback={<p>(loading games)</p>}>
288 - <ProfileGames games={<Games>:games:</Games>} />
289 - </Suspense>
290 - </MyErrorBoundaryClient>
291 - </>
292 - );
293 - }
294 -
295 - const model = {
296 - rootContent: <ProfileContent />,
297 - };
298 -
299 - function ProfilePage({response}) {
300 - return use(response).rootContent;
301 - }
302 -
303 - const stream = ReactServerDOMServer.renderToReadableStream(
304 - model,
305 - turbopackMap,
306 - {
307 - onError(x) {
308 - reportedErrors.push(x);
309 - return __DEV__ ? `a dev digest` : `digest("${x.message}")`;
310 - },
311 - },
312 - );
313 - const response = ReactServerDOMClient.createFromReadableStream(stream);
314 -
315 - const container = document.createElement('div');
316 - const root = ReactDOMClient.createRoot(container);
317 - await act(() => {
318 - root.render(
319 - <Suspense fallback={<p>(loading)</p>}>
320 - <ProfilePage response={response} />
321 - </Suspense>,
322 - );
323 - });
324 - expect(container.innerHTML).toBe('<p>(loading)</p>');
325 -
326 - // This isn't enough to show anything.
327 - await act(() => {
328 - resolveFriends();
329 - });
330 - expect(container.innerHTML).toBe('<p>(loading)</p>');
331 -
332 - // We can now show the details. Sidebar and posts are still loading.
333 - await act(() => {
334 - resolveName();
335 - });
336 - // Advance time enough to trigger a nested fallback.
337 - jest.advanceTimersByTime(500);
338 - expect(container.innerHTML).toBe(
339 - '<div>:name::avatar:</div>' +
340 - '<p>(loading sidebar)</p>' +
341 - '<p>(loading posts)</p>' +
342 - '<p>(loading games)</p>',
343 - );
344 -
345 - expect(reportedErrors).toEqual([]);
346 -
347 - const theError = new Error('Game over');
348 - // Let's *fail* loading games.
349 - await act(() => {
350 - rejectGames(theError);
351 - });
352 -
353 - const gamesExpectedValue = __DEV__
354 - ? '<p>Game over + a dev digest</p>'
355 - : '<p>digest("Game over")</p>';
356 -
357 - expect(container.innerHTML).toBe(
358 - '<div>:name::avatar:</div>' +
359 - '<p>(loading sidebar)</p>' +
360 - '<p>(loading posts)</p>' +
361 - gamesExpectedValue,
362 - );
363 -
364 - expect(reportedErrors).toEqual([theError]);
365 - reportedErrors = [];
366 -
367 - // We can now show the sidebar.
368 - await act(() => {
369 - resolvePhotos();
370 - });
371 - expect(container.innerHTML).toBe(
372 - '<div>:name::avatar:</div>' +
373 - '<div>:photos::friends:</div>' +
374 - '<p>(loading posts)</p>' +
375 - gamesExpectedValue,
376 - );
377 -
378 - // Show everything.
379 - await act(() => {
380 - resolvePosts();
381 - });
382 - expect(container.innerHTML).toBe(
383 - '<div>:name::avatar:</div>' +
384 - '<div>:photos::friends:</div>' +
385 - '<div>:posts:</div>' +
386 - gamesExpectedValue,
387 - );
388 -
389 - expect(reportedErrors).toEqual([]);
390 - });
391 -
392 - it('should close the stream upon completion when rendering to W3C streams', async () => {
393 - // Model
394 - function Text({children}) {
395 - return children;
396 - }
397 -
398 - const [Friends, resolveFriends] = makeDelayedText(Text);
399 - const [Name, resolveName] = makeDelayedText(Text);
400 - const [Posts, resolvePosts] = makeDelayedText(Text);
401 - const [Photos, resolvePhotos] = makeDelayedText(Text);
402 -
403 - // View
404 - function ProfileDetails({avatar}) {
405 - return (
406 - <div>
407 - <Name>:name:</Name>
408 - {avatar}
409 - </div>
410 - );
411 - }
412 - function ProfileSidebar({friends}) {
413 - return (
414 - <div>
415 - <Photos>:photos:</Photos>
416 - {friends}
417 - </div>
418 - );
419 - }
420 - function ProfilePosts({posts}) {
421 - return <div>{posts}</div>;
422 - }
423 -
424 - function ProfileContent() {
425 - return (
426 - <Suspense fallback="(loading everything)">
427 - <ProfileDetails avatar={<Text>:avatar:</Text>} />
428 - <Suspense fallback={<p>(loading sidebar)</p>}>
429 - <ProfileSidebar friends={<Friends>:friends:</Friends>} />
430 - </Suspense>
431 - <Suspense fallback={<p>(loading posts)</p>}>
432 - <ProfilePosts posts={<Posts>:posts:</Posts>} />
433 - </Suspense>
434 - </Suspense>
435 - );
436 - }
437 -
438 - const model = {
439 - rootContent: <ProfileContent />,
440 - };
441 -
442 - const stream = ReactServerDOMServer.renderToReadableStream(
443 - model,
444 - turbopackMap,
445 - );
446 -
447 - const reader = stream.getReader();
448 - const decoder = new TextDecoder();
449 -
450 - let flightResponse = '';
451 - let isDone = false;
452 -
453 - reader.read().then(function progress({done, value}) {
454 - if (done) {
455 - isDone = true;
456 - return;
457 - }
458 -
459 - flightResponse += decoder.decode(value);
460 -
461 - return reader.read().then(progress);
462 - });
463 -
464 - // Advance time enough to trigger a nested fallback.
465 - jest.advanceTimersByTime(500);
466 -
467 - await act(() => {});
468 -
469 - expect(flightResponse).toContain('(loading everything)');
470 - expect(flightResponse).toContain('(loading sidebar)');
471 - expect(flightResponse).toContain('(loading posts)');
472 - expect(flightResponse).not.toContain(':friends:');
473 - expect(flightResponse).not.toContain(':name:');
474 -
475 - await act(() => {
476 - resolveFriends();
477 - });
478 -
479 - expect(flightResponse).toContain(':friends:');
480 -
481 - await act(() => {
482 - resolveName();
483 - });
484 -
485 - expect(flightResponse).toContain(':name:');
486 -
487 - await act(() => {
488 - resolvePhotos();
489 - });
490 -
491 - expect(flightResponse).toContain(':photos:');
492 -
493 - await act(() => {
494 - resolvePosts();
495 - });
496 -
497 - expect(flightResponse).toContain(':posts:');
498 -
499 - // Final pending chunk is written; stream should be closed.
500 - expect(isDone).toBeTruthy();
501 - });
502 -
503 - it('should be able to complete after aborting and throw the reason client-side', async () => {
504 - const reportedErrors = [];
505 -
506 - let errorBoundaryFn;
507 - if (__DEV__) {
508 - errorBoundaryFn = e => (
509 - <p>
510 - {e.message} + {e.digest}
511 - </p>
512 - );
513 - } else {
514 - errorBoundaryFn = e => {
515 - expect(e.message).toBe(
516 - 'An error occurred in the Server Components render. The specific message is omitted in production' +
517 - ' builds to avoid leaking sensitive details. A digest property is included on this error instance which' +
518 - ' may provide additional details about the nature of the error.',
519 - );
520 - return <p>{e.digest}</p>;
521 - };
522 - }
523 -
524 - class ErrorBoundary extends React.Component {
525 - state = {hasError: false, error: null};
526 - static getDerivedStateFromError(error) {
527 - return {
528 - hasError: true,
529 - error,
530 - };
531 - }
532 - render() {
533 - if (this.state.hasError) {
534 - return this.props.fallback(this.state.error);
535 - }
536 - return this.props.children;
537 - }
538 - }
539 -
540 - const controller = new AbortController();
541 - const stream = ReactServerDOMServer.renderToReadableStream(
542 - <div>
543 - <InfiniteSuspend />
544 - </div>,
545 - turbopackMap,
546 - {
547 - signal: controller.signal,
548 - onError(x) {
549 - const message = typeof x === 'string' ? x : x.message;
550 - reportedErrors.push(x);
551 - return __DEV__ ? 'a dev digest' : `digest("${message}")`;
552 - },
553 - },
554 - );
555 - const response = ReactServerDOMClient.createFromReadableStream(stream);
556 -
557 - const container = document.createElement('div');
558 - const root = ReactDOMClient.createRoot(container);
559 -
560 - function App({res}) {
561 - return use(res);
562 - }
563 -
564 - await act(() => {
565 - root.render(
566 - <ErrorBoundary fallback={errorBoundaryFn}>
567 - <Suspense fallback={<p>(loading)</p>}>
568 - <App res={response} />
569 - </Suspense>
570 - </ErrorBoundary>,
571 - );
572 - });
573 - expect(container.innerHTML).toBe('<p>(loading)</p>');
574 -
575 - await act(() => {
576 - controller.abort('for reasons');
577 - });
578 - const expectedValue = __DEV__
579 - ? '<p>Error: for reasons + a dev digest</p>'
580 - : '<p>digest("for reasons")</p>';
581 - expect(container.innerHTML).toBe(expectedValue);
582 -
583 - expect(reportedErrors).toEqual(['for reasons']);
584 - });
585 -
586 - it('basic use(promise)', async () => {
587 - function Server() {
588 - return (
589 - use(Promise.resolve('A')) +
590 - use(Promise.resolve('B')) +
591 - use(Promise.resolve('C'))
592 - );
593 - }
594 -
595 - const stream = ReactServerDOMServer.renderToReadableStream(<Server />);
596 - const response = ReactServerDOMClient.createFromReadableStream(stream);
597 -
598 - function Client() {
599 - return use(response);
600 - }
601 -
602 - const container = document.createElement('div');
603 - const root = ReactDOMClient.createRoot(container);
604 - await act(() => {
605 - root.render(
606 - <Suspense fallback="Loading...">
607 - <Client />
608 - </Suspense>,
609 - );
610 - });
611 - expect(container.innerHTML).toBe('ABC');
612 - });
613 -
614 - it('basic use(context)', async () => {
615 - let ContextA;
616 - let ContextB;
617 - expect(() => {
618 - ContextA = React.createServerContext('ContextA', '');
619 - ContextB = React.createServerContext('ContextB', 'B');
620 - }).toErrorDev(
621 - [
622 - 'Server Context is deprecated and will soon be removed. ' +
623 - 'It was never documented and we have found it not to be useful ' +
624 - 'enough to warrant the downside it imposes on all apps.',
625 - 'Server Context is deprecated and will soon be removed. ' +
626 - 'It was never documented and we have found it not to be useful ' +
627 - 'enough to warrant the downside it imposes on all apps.',
628 - ],
629 - {withoutStack: true},
630 - );
631 -
632 - function ServerComponent() {
633 - return use(ContextA) + use(ContextB);
634 - }
635 - function Server() {
636 - return (
637 - <ContextA.Provider value="A">
638 - <ServerComponent />
639 - </ContextA.Provider>
640 - );
641 - }
642 - const stream = ReactServerDOMServer.renderToReadableStream(<Server />);
643 - const response = ReactServerDOMClient.createFromReadableStream(stream);
644 -
645 - function Client() {
646 - return use(response);
647 - }
648 -
649 - const container = document.createElement('div');
650 - const root = ReactDOMClient.createRoot(container);
651 - await act(() => {
652 - // Client uses a different renderer.
653 - // We reset _currentRenderer here to not trigger a warning about multiple
654 - // renderers concurrently using this context
655 - ContextA._currentRenderer = null;
656 - root.render(<Client />);
657 - });
658 - expect(container.innerHTML).toBe('AB');
659 - });
660 -
661 - it('use(promise) in multiple components', async () => {
662 - function Child({prefix}) {
663 - return prefix + use(Promise.resolve('C')) + use(Promise.resolve('D'));
664 - }
665 -
666 - function Parent() {
667 - return (
668 - <Child prefix={use(Promise.resolve('A')) + use(Promise.resolve('B'))} />
669 - );
670 - }
671 -
672 - const stream = ReactServerDOMServer.renderToReadableStream(<Parent />);
673 - const response = ReactServerDOMClient.createFromReadableStream(stream);
674 -
675 - function Client() {
676 - return use(response);
677 - }
678 -
679 - const container = document.createElement('div');
680 - const root = ReactDOMClient.createRoot(container);
681 - await act(() => {
682 - root.render(
683 - <Suspense fallback="Loading...">
684 - <Client />
685 - </Suspense>,
686 - );
687 - });
688 - expect(container.innerHTML).toBe('ABCD');
689 - });
690 -
691 - it('using a rejected promise will throw', async () => {
692 - const promiseA = Promise.resolve('A');
693 - const promiseB = Promise.reject(new Error('Oops!'));
694 - const promiseC = Promise.resolve('C');
695 -
696 - // Jest/Node will raise an unhandled rejected error unless we await this. It
697 - // works fine in the browser, though.
698 - await expect(promiseB).rejects.toThrow('Oops!');
699 -
700 - function Server() {
701 - return use(promiseA) + use(promiseB) + use(promiseC);
702 - }
703 -
704 - const reportedErrors = [];
705 - const stream = ReactServerDOMServer.renderToReadableStream(
706 - <Server />,
707 - turbopackMap,
708 - {
709 - onError(x) {
710 - reportedErrors.push(x);
711 - return __DEV__ ? 'a dev digest' : `digest("${x.message}")`;
712 - },
713 - },
714 - );
715 - const response = ReactServerDOMClient.createFromReadableStream(stream);
716 -
717 - class ErrorBoundary extends React.Component {
718 - state = {error: null};
719 - static getDerivedStateFromError(error) {
720 - return {error};
721 - }
722 - render() {
723 - if (this.state.error) {
724 - return __DEV__
725 - ? this.state.error.message + ' + ' + this.state.error.digest
726 - : this.state.error.digest;
727 - }
728 - return this.props.children;
729 - }
730 - }
731 -
732 - function Client() {
733 - return use(response);
734 - }
735 -
736 - const container = document.createElement('div');
737 - const root = ReactDOMClient.createRoot(container);
738 - await act(() => {
739 - root.render(
740 - <ErrorBoundary>
741 - <Client />
742 - </ErrorBoundary>,
743 - );
744 - });
745 - expect(container.innerHTML).toBe(
746 - __DEV__ ? 'Oops! + a dev digest' : 'digest("Oops!")',
747 - );
748 - expect(reportedErrors.length).toBe(1);
749 - expect(reportedErrors[0].message).toBe('Oops!');
750 - });
751 -
752 - it("use a promise that's already been instrumented and resolved", async () => {
753 - const thenable = {
754 - status: 'fulfilled',
755 - value: 'Hi',
756 - then() {},
757 - };
758 -
759 - // This will never suspend because the thenable already resolved
760 - function Server() {
761 - return use(thenable);
762 - }
763 -
764 - const stream = ReactServerDOMServer.renderToReadableStream(<Server />);
765 - const response = ReactServerDOMClient.createFromReadableStream(stream);
766 -
767 - function Client() {
768 - return use(response);
769 - }
770 -
771 - const container = document.createElement('div');
772 - const root = ReactDOMClient.createRoot(container);
773 - await act(() => {
774 - root.render(<Client />);
775 - });
776 - expect(container.innerHTML).toBe('Hi');
777 - });
778 -
779 - it('unwraps thenable that fulfills synchronously without suspending', async () => {
780 - function Server() {
781 - const thenable = {
782 - then(resolve) {
783 - // This thenable immediately resolves, synchronously, without waiting
784 - // a microtask.
785 - resolve('Hi');
786 - },
787 - };
788 - try {
789 - return use(thenable);
790 - } catch {
791 - throw new Error(
792 - '`use` should not suspend because the thenable resolved synchronously.',
793 - );
794 - }
795 - }
796 -
797 - // Because the thenable resolves synchronously, we should be able to finish
798 - // rendering synchronously, with no fallback.
799 - const stream = ReactServerDOMServer.renderToReadableStream(<Server />);
800 - const response = ReactServerDOMClient.createFromReadableStream(stream);
801 -
802 - function Client() {
803 - return use(response);
804 - }
805 -
806 - const container = document.createElement('div');
807 - const root = ReactDOMClient.createRoot(container);
808 - await act(() => {
809 - root.render(<Client />);
810 - });
811 - expect(container.innerHTML).toBe('Hi');
812 - });
813 -
814 - it('can pass a higher order function by reference from server to client', async () => {
815 - let actionProxy;
816 -
817 - function Client({action}) {
818 - actionProxy = action;
819 - return 'Click Me';
820 - }
821 -
822 - function greet(transform, text) {
823 - return 'Hello ' + transform(text);
824 - }
825 -
826 - function upper(text) {
827 - return text.toUpperCase();
828 - }
829 -
830 - const ServerModuleA = serverExports({
831 - greet,
832 - });
833 - const ServerModuleB = serverExports({
834 - upper,
835 - });
836 - const ClientRef = clientExports(Client);
837 -
838 - const boundFn = ServerModuleA.greet.bind(null, ServerModuleB.upper);
839 -
840 - const stream = ReactServerDOMServer.renderToReadableStream(
841 - <ClientRef action={boundFn} />,
842 - turbopackMap,
843 - );
844 -
845 - const response = ReactServerDOMClient.createFromReadableStream(stream, {
846 - async callServer(ref, args) {
847 - const body = await ReactServerDOMClient.encodeReply(args);
848 - return callServer(ref, body);
849 - },
850 - });
851 -
852 - function App() {
853 - return use(response);
854 - }
855 -
856 - const container = document.createElement('div');
857 - const root = ReactDOMClient.createRoot(container);
858 - await act(() => {
859 - root.render(<App />);
860 - });
861 - expect(container.innerHTML).toBe('Click Me');
862 - expect(typeof actionProxy).toBe('function');
863 - expect(actionProxy).not.toBe(boundFn);
864 -
865 - const result = await actionProxy('hi');
866 - expect(result).toBe('Hello HI');
867 - });
868 -
869 - it('can call a module split server function', async () => {
870 - let actionProxy;
871 -
872 - function Client({action}) {
873 - actionProxy = action;
874 - return 'Click Me';
875 - }
876 -
877 - function greet(text) {
878 - return 'Hello ' + text;
879 - }
880 -
881 - const ServerModule = serverExports({
882 - // This gets split into another module
883 - split: greet,
884 - });
885 - const ClientRef = clientExports(Client);
886 -
887 - const stream = ReactServerDOMServer.renderToReadableStream(
888 - <ClientRef action={ServerModule.split} />,
889 - turbopackMap,
890 - );
891 -
892 - const response = ReactServerDOMClient.createFromReadableStream(stream, {
893 - async callServer(ref, args) {
894 - const body = await ReactServerDOMClient.encodeReply(args);
895 - return callServer(ref, body);
896 - },
897 - });
898 -
899 - function App() {
900 - return use(response);
901 - }
902 -
903 - const container = document.createElement('div');
904 - const root = ReactDOMClient.createRoot(container);
905 - await act(() => {
906 - root.render(<App />);
907 - });
908 - expect(container.innerHTML).toBe('Click Me');
909 - expect(typeof actionProxy).toBe('function');
910 -
911 - const result = await actionProxy('Split');
912 - expect(result).toBe('Hello Split');
913 - });
914 -
915 - it('can pass a server function by importing from client back to server', async () => {
916 - function greet(transform, text) {
917 - return 'Hello ' + transform(text);
918 - }
919 -
920 - function upper(text) {
921 - return text.toUpperCase();
922 - }
923 -
924 - const ServerModuleA = serverExports({
925 - greet,
926 - });
927 - const ServerModuleB = serverExports({
928 - upper,
929 - });
930 -
931 - let actionProxy;
932 -
933 - // This is a Proxy representing ServerModuleB in the Client bundle.
934 - const ServerModuleBImportedOnClient = {
935 - upper: ReactServerDOMClient.createServerReference(
936 - ServerModuleB.upper.$$id,
937 - async function (ref, args) {
938 - const body = await ReactServerDOMClient.encodeReply(args);
939 - return callServer(ref, body);
940 - },
941 - ),
942 - };
943 -
944 - function Client({action}) {
945 - // Client side pass a Server Reference into an action.
946 - actionProxy = text => action(ServerModuleBImportedOnClient.upper, text);
947 - return 'Click Me';
948 - }
949 -
950 - const ClientRef = clientExports(Client);
951 -
952 - const stream = ReactServerDOMServer.renderToReadableStream(
953 - <ClientRef action={ServerModuleA.greet} />,
954 - turbopackMap,
955 - );
956 -
957 - const response = ReactServerDOMClient.createFromReadableStream(stream, {
958 - async callServer(ref, args) {
959 - const body = await ReactServerDOMClient.encodeReply(args);
960 - return callServer(ref, body);
961 - },
962 - });
963 -
964 - function App() {
965 - return use(response);
966 - }
967 -
968 - const container = document.createElement('div');
969 - const root = ReactDOMClient.createRoot(container);
970 - await act(() => {
971 - root.render(<App />);
972 - });
973 - expect(container.innerHTML).toBe('Click Me');
974 -
975 - const result = await actionProxy('hi');
976 - expect(result).toBe('Hello HI');
977 - });
978 -
979 - it('can bind arguments to a server reference', async () => {
980 - let actionProxy;
981 -
982 - function Client({action}) {
983 - actionProxy = action;
984 - return 'Click Me';
985 - }
986 -
987 - const greet = serverExports(function greet(a, b, c) {
988 - return a + ' ' + b + c;
989 - });
990 - const ClientRef = clientExports(Client);
991 -
992 - const stream = ReactServerDOMServer.renderToReadableStream(
993 - <ClientRef action={greet.bind(null, 'Hello', 'World')} />,
994 - turbopackMap,
995 - );
996 -
997 - const response = ReactServerDOMClient.createFromReadableStream(stream, {
998 - async callServer(actionId, args) {
999 - const body = await ReactServerDOMClient.encodeReply(args);
1000 - return callServer(actionId, body);
1001 - },
1002 - });
1003 -
1004 - function App() {
1005 - return use(response);
1006 - }
1007 -
1008 - const container = document.createElement('div');
1009 - const root = ReactDOMClient.createRoot(container);
1010 - await act(() => {
1011 - root.render(<App />);
1012 - });
1013 - expect(container.innerHTML).toBe('Click Me');
1014 - expect(typeof actionProxy).toBe('function');
1015 - expect(actionProxy).not.toBe(greet);
1016 -
1017 - const result = await actionProxy('!');
1018 - expect(result).toBe('Hello World!');
1019 - });
1020 -
1021 - it('propagates server reference errors to the client', async () => {
1022 - let actionProxy;
1023 -
1024 - function Client({action}) {
1025 - actionProxy = action;
1026 - return 'Click Me';
1027 - }
1028 -
1029 - async function send(text) {
1030 - return Promise.reject(new Error(`Error for ${text}`));
1031 - }
1032 -
1033 - const ServerModule = serverExports({send});
1034 - const ClientRef = clientExports(Client);
1035 -
1036 - const stream = ReactServerDOMServer.renderToReadableStream(
1037 - <ClientRef action={ServerModule.send} />,
1038 - turbopackMap,
1039 - );
1040 -
1041 - const response = ReactServerDOMClient.createFromReadableStream(stream, {
1042 - async callServer(actionId, args) {
1043 - const body = await ReactServerDOMClient.encodeReply(args);
1044 - return ReactServerDOMClient.createFromReadableStream(
1045 - ReactServerDOMServer.renderToReadableStream(
1046 - callServer(actionId, body),
1047 - null,
1048 - {onError: error => 'test-error-digest'},
1049 - ),
1050 - );
1051 - },
1052 - });
1053 -
1054 - function App() {
1055 - return use(response);
1056 - }
1057 -
1058 - const container = document.createElement('div');
1059 - const root = ReactDOMClient.createRoot(container);
1060 - await act(() => {
1061 - root.render(<App />);
1062 - });
1063 -
1064 - if (__DEV__) {
1065 - await expect(actionProxy('test')).rejects.toThrow('Error for test');
1066 - } else {
1067 - let thrownError;
1068 -
1069 - try {
1070 - await actionProxy('test');
1071 - } catch (error) {
1072 - thrownError = error;
1073 - }
1074 -
1075 - expect(thrownError).toEqual(
1076 - new Error(
1077 - 'An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.',
1078 - ),
1079 - );
1080 -
1081 - expect(thrownError.digest).toBe('test-error-digest');
1082 - }
1083 - });
1084 -
1085 - it('supports Float hints before the first await in server components in Fiber', async () => {
1086 - function Component() {
1087 - return <p>hello world</p>;
1088 - }
1089 -
1090 - const ClientComponent = clientExports(Component);
1091 -
1092 - async function ServerComponent() {
1093 - ReactDOM.preload('before', {as: 'style'});
1094 - await 1;
1095 - ReactDOM.preload('after', {as: 'style'});
1096 - return <ClientComponent />;
1097 - }
1098 -
1099 - const stream = ReactServerDOMServer.renderToReadableStream(
1100 - <ServerComponent />,
1101 - turbopackMap,
1102 - );
1103 -
1104 - let response = null;
1105 - function getResponse() {
1106 - if (response === null) {
1107 - response = ReactServerDOMClient.createFromReadableStream(stream);
1108 - }
1109 - return response;
1110 - }
1111 -
1112 - function App() {
1113 - return getResponse();
1114 - }
1115 -
1116 - // pausing to let Flight runtime tick. This is a test only artifact of the fact that
1117 - // we aren't operating separate module graphs for flight and fiber. In a real app
1118 - // each would have their own dispatcher and there would be no cross dispatching.
1119 - await 1;
1120 -
1121 - const container = document.createElement('div');
1122 - const root = ReactDOMClient.createRoot(container);
1123 - await act(() => {
1124 - root.render(<App />);
1125 - });
1126 - expect(document.head.innerHTML).toBe(
1127 - '<link rel="preload" href="before" as="style">',
1128 - );
1129 - expect(container.innerHTML).toBe('<p>hello world</p>');
1130 - });
1131 -
1132 - it('Does not support Float hints in server components anywhere in Fizz', async () => {
1133 - // In environments that do not support AsyncLocalStorage the Flight client has no ability
1134 - // to scope hint dispatching to a specific Request. In Fiber this isn't a problem because
1135 - // the Browser scope acts like a singleton and we can dispatch away. But in Fizz we need to have
1136 - // a reference to Resources and this is only possible during render unless you support AsyncLocalStorage.
1137 - function Component() {
1138 - return <p>hello world</p>;
1139 - }
1140 -
1141 - const ClientComponent = clientExports(Component);
1142 -
1143 - async function ServerComponent() {
1144 - ReactDOM.preload('before', {as: 'style'});
1145 - await 1;
1146 - ReactDOM.preload('after', {as: 'style'});
1147 - return <ClientComponent />;
1148 - }
1149 -
1150 - const stream = ReactServerDOMServer.renderToReadableStream(
1151 - <ServerComponent />,
1152 - turbopackMap,
1153 - );
1154 -
1155 - let response = null;
1156 - function getResponse() {
1157 - if (response === null) {
1158 - response = ReactServerDOMClient.createFromReadableStream(stream);
1159 - }
1160 - return response;
1161 - }
1162 -
1163 - function App() {
1164 - return (
1165 - <html>
1166 - <body>{getResponse()}</body>
1167 - </html>
1168 - );
1169 - }
1170 -
1171 - // pausing to let Flight runtime tick. This is a test only artifact of the fact that
1172 - // we aren't operating separate module graphs for flight and fiber. In a real app
1173 - // each would have their own dispatcher and there would be no cross dispatching.
1174 - await 1;
1175 -
1176 - let fizzStream;
1177 - await act(async () => {
1178 - fizzStream = await ReactDOMFizzServer.renderToReadableStream(<App />);
1179 - });
1180 -
1181 - const decoder = new TextDecoder();
1182 - const reader = fizzStream.getReader();
1183 - let content = '';
1184 - while (true) {
1185 - const {done, value} = await reader.read();
1186 - if (done) {
1187 - content += decoder.decode();
1188 - break;
1189 - }
1190 - content += decoder.decode(value, {stream: true});
1191 - }
1192 -
1193 - expect(content).toEqual(
1194 - '<!DOCTYPE html><html><head>' +
1195 - '</head><body><p>hello world</p></body></html>',
1196 - );
1197 - });
1198 -
1199 - // @gate enablePostpone
1200 - it('supports postpone in Server Components', async () => {
1201 - function Server() {
1202 - React.unstable_postpone('testing postpone');
1203 - return 'Not shown';
1204 - }
1205 -
1206 - let postponed = null;
1207 -
1208 - const stream = ReactServerDOMServer.renderToReadableStream(
1209 - <Suspense fallback="Loading...">
1210 - <Server />
1211 - </Suspense>,
1212 - null,
1213 - {
1214 - onPostpone(reason) {
1215 - postponed = reason;
1216 - },
1217 - },
1218 - );
1219 - const response = ReactServerDOMClient.createFromReadableStream(stream);
1220 -
1221 - function Client() {
1222 - return use(response);
1223 - }
1224 -
1225 - const container = document.createElement('div');
1226 - const root = ReactDOMClient.createRoot(container);
1227 - await act(async () => {
1228 - root.render(
1229 - <div>
1230 - Shell: <Client />
1231 - </div>,
1232 - );
1233 - });
1234 - // We should have reserved the shell already. Which means that the Server
1235 - // Component should've been a lazy component.
1236 - expect(container.innerHTML).toContain('Shell:');
1237 - expect(container.innerHTML).toContain('Loading...');
1238 - expect(container.innerHTML).not.toContain('Not shown');
1239 -
1240 - expect(postponed).toBe('testing postpone');
1241 - });
73 });
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMEdge-test.js
+7 -1
@@ -33,6 +33,7 @@ describe('ReactFlightDOMEdge', () => {
33 jest.resetModules();
34
35 // Simulate the condition resolution
36 + jest.mock('react', () => require('react/react.shared-subset'));
37 jest.mock('react-server-dom-turbopack/server', () =>
38 require('react-server-dom-turbopack/server.edge'),
39 );
@@ -41,9 +42,14 @@ describe('ReactFlightDOMEdge', () => {
42 clientExports = TurbopackMock.clientExports;
43 turbopackMap = TurbopackMock.turbopackMap;
44 turbopackModules = TurbopackMock.turbopackModules;
45 +
46 + ReactServerDOMServer = require('react-server-dom-turbopack/server.edge');
47 +
48 + jest.resetModules();
49 + __unmockReact();
50 +
51 React = require('react');
52 ReactDOMServer = require('react-dom/server.edge');
46 - ReactServerDOMServer = require('react-server-dom-turbopack/server.edge');
53 ReactServerDOMClient = require('react-server-dom-turbopack/client.edge');
54 use = React.use;
55 });
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMForm-test.js
+4 -1
@@ -33,14 +33,17 @@ describe('ReactFlightDOMForm', () => {
33 beforeEach(() => {
34 jest.resetModules();
35 // Simulate the condition resolution
36 + jest.mock('react', () => require('react/react.shared-subset'));
37 jest.mock('react-server-dom-turbopack/server', () =>
38 require('react-server-dom-turbopack/server.edge'),
39 );
40 + ReactServerDOMServer = require('react-server-dom-turbopack/server.edge');
41 const TurbopackMock = require('./utils/TurbopackMock');
42 serverExports = TurbopackMock.serverExports;
43 turbopackServerMap = TurbopackMock.turbopackServerMap;
44 + __unmockReact();
45 + jest.resetModules();
46 React = require('react');
43 - ReactServerDOMServer = require('react-server-dom-turbopack/server.edge');
47 ReactServerDOMClient = require('react-server-dom-turbopack/client.edge');
48 ReactDOMServer = require('react-dom/server.edge');
49 container = document.createElement('div');
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMNode-test.js
+1 -1
@@ -42,7 +42,7 @@ describe('ReactFlightDOMNode', () => {
42 turbopackModuleLoading = TurbopackMock.moduleLoading;
43
44 jest.resetModules();
45 - jest.unmock('react');
45 + __unmockReact();
46 jest.unmock('react-server-dom-turbopack/server');
47 jest.mock('react-server-dom-turbopack/client', () =>
48 require('react-server-dom-turbopack/client.node'),
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMReply-test.js
+2
@@ -24,6 +24,7 @@ describe('ReactFlightDOMReply', () => {
24 beforeEach(() => {
25 jest.resetModules();
26 // Simulate the condition resolution
27 + jest.mock('react', () => require('react/react.shared-subset'));
28 jest.mock('react-server-dom-turbopack/server', () =>
29 require('react-server-dom-turbopack/server.browser'),
30 );
@@ -31,6 +32,7 @@ describe('ReactFlightDOMReply', () => {
32 // serverExports = TurbopackMock.serverExports;
33 turbopackServerMap = TurbopackMock.turbopackServerMap;
34 ReactServerDOMServer = require('react-server-dom-turbopack/server.browser');
35 + jest.resetModules();
36 ReactServerDOMClient = require('react-server-dom-turbopack/client');
37 });
38
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+1 -1
@@ -62,7 +62,7 @@ describe('ReactFlightDOM', () => {
62
63 // This reset is to load modules for the SSR/Browser scope.
64 jest.unmock('react-server-dom-webpack/server');
65 - jest.unmock('react');
65 + __unmockReact();
66 jest.resetModules();
67 act = require('internal-test-utils').act;
68 Stream = require('stream');
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
+36 -13
@@ -28,27 +28,37 @@ let ReactServerDOMServer;
28 let ReactServerDOMClient;
29 let Suspense;
30 let use;
31 +let ReactServer;
32 +let ReactServerDOM;
33
34 describe('ReactFlightDOMBrowser', () => {
35 beforeEach(() => {
36 jest.resetModules();
37
38 // Simulate the condition resolution
39 + jest.mock('react', () => require('react/react.shared-subset'));
40 jest.mock('react-server-dom-webpack/server', () =>
41 require('react-server-dom-webpack/server.browser'),
42 );
43
41 - act = require('internal-test-utils').act;
44 const WebpackMock = require('./utils/WebpackMock');
45 clientExports = WebpackMock.clientExports;
46 serverExports = WebpackMock.serverExports;
47 webpackMap = WebpackMock.webpackMap;
48 webpackServerMap = WebpackMock.webpackServerMap;
49 +
50 + ReactServer = require('react');
51 + ReactServerDOM = require('react-dom');
52 + ReactServerDOMServer = require('react-server-dom-webpack/server.browser');
53 +
54 + __unmockReact();
55 + jest.resetModules();
56 +
57 + act = require('internal-test-utils').act;
58 React = require('react');
59 ReactDOM = require('react-dom');
60 ReactDOMClient = require('react-dom/client');
61 ReactDOMFizzServer = require('react-dom/server.browser');
51 - ReactServerDOMServer = require('react-server-dom-webpack/server.browser');
62 ReactServerDOMClient = require('react-server-dom-webpack/client');
63 Suspense = React.Suspense;
64 use = React.use;
@@ -583,9 +593,9 @@ describe('ReactFlightDOMBrowser', () => {
593 it('basic use(promise)', async () => {
594 function Server() {
595 return (
586 - use(Promise.resolve('A')) +
587 - use(Promise.resolve('B')) +
588 - use(Promise.resolve('C'))
596 + ReactServer.use(Promise.resolve('A')) +
597 + ReactServer.use(Promise.resolve('B')) +
598 + ReactServer.use(Promise.resolve('C'))
599 );
600 }
601
@@ -627,7 +637,7 @@ describe('ReactFlightDOMBrowser', () => {
637 );
638
639 function ServerComponent() {
630 - return use(ContextA) + use(ContextB);
640 + return ReactServer.use(ContextA) + ReactServer.use(ContextB);
641 }
642 function Server() {
643 return (
@@ -657,12 +667,21 @@ describe('ReactFlightDOMBrowser', () => {
667
668 it('use(promise) in multiple components', async () => {
669 function Child({prefix}) {
660 - return prefix + use(Promise.resolve('C')) + use(Promise.resolve('D'));
670 + return (
671 + prefix +
672 + ReactServer.use(Promise.resolve('C')) +
673 + ReactServer.use(Promise.resolve('D'))
674 + );
675 }
676
677 function Parent() {
678 return (
665 - <Child prefix={use(Promise.resolve('A')) + use(Promise.resolve('B'))} />
679 + <Child
680 + prefix={
681 + ReactServer.use(Promise.resolve('A')) +
682 + ReactServer.use(Promise.resolve('B'))
683 + }
684 + />
685 );
686 }
687
@@ -695,7 +714,11 @@ describe('ReactFlightDOMBrowser', () => {
714 await expect(promiseB).rejects.toThrow('Oops!');
715
716 function Server() {
698 - return use(promiseA) + use(promiseB) + use(promiseC);
717 + return (
718 + ReactServer.use(promiseA) +
719 + ReactServer.use(promiseB) +
720 + ReactServer.use(promiseC)
721 + );
722 }
723
724 const reportedErrors = [];
@@ -755,7 +778,7 @@ describe('ReactFlightDOMBrowser', () => {
778
779 // This will never suspend because the thenable already resolved
780 function Server() {
758 - return use(thenable);
781 + return ReactServer.use(thenable);
782 }
783
784 const stream = ReactServerDOMServer.renderToReadableStream(<Server />);
@@ -783,7 +806,7 @@ describe('ReactFlightDOMBrowser', () => {
806 },
807 };
808 try {
786 - return use(thenable);
809 + return ReactServer.use(thenable);
810 } catch {
811 throw new Error(
812 '`use` should not suspend because the thenable resolved synchronously.',
@@ -1087,9 +1110,9 @@ describe('ReactFlightDOMBrowser', () => {
1110 const ClientComponent = clientExports(Component);
1111
1112 async function ServerComponent() {
1090 - ReactDOM.preload('before', {as: 'style'});
1113 + ReactServerDOM.preload('before', {as: 'style'});
1114 await 1;
1092 - ReactDOM.preload('after', {as: 'style'});
1115 + ReactServerDOM.preload('after', {as: 'style'});
1116 return <ClientComponent />;
1117 }
1118
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js
+3 -2
@@ -38,7 +38,6 @@ describe('ReactFlightDOMEdge', () => {
38 jest.mock('react-server-dom-webpack/server', () =>
39 require('react-server-dom-webpack/server.edge'),
40 );
41 - ReactServerDOMServer = require('react-server-dom-webpack/server');
41
42 const WebpackMock = require('./utils/WebpackMock');
43
@@ -47,8 +46,10 @@ describe('ReactFlightDOMEdge', () => {
46 webpackModules = WebpackMock.webpackModules;
47 webpackModuleLoading = WebpackMock.moduleLoading;
48
49 + ReactServerDOMServer = require('react-server-dom-webpack/server');
50 +
51 jest.resetModules();
51 - jest.unmock('react');
52 + __unmockReact();
53 jest.unmock('react-server-dom-webpack/server');
54 jest.mock('react-server-dom-webpack/client', () =>
55 require('react-server-dom-webpack/client.edge'),
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js
+4 -1
@@ -38,16 +38,19 @@ describe('ReactFlightDOMForm', () => {
38 beforeEach(() => {
39 jest.resetModules();
40 // Simulate the condition resolution
41 + jest.mock('react', () => require('react/react.shared-subset'));
42 jest.mock('react-server-dom-webpack/server', () =>
43 require('react-server-dom-webpack/server.edge'),
44 );
45 + ReactServerDOMServer = require('react-server-dom-webpack/server.edge');
46 const WebpackMock = require('./utils/WebpackMock');
47 clientExports = WebpackMock.clientExports;
48 serverExports = WebpackMock.serverExports;
49 webpackMap = WebpackMock.webpackMap;
50 webpackServerMap = WebpackMock.webpackServerMap;
51 + __unmockReact();
52 + jest.resetModules();
53 React = require('react');
50 - ReactServerDOMServer = require('react-server-dom-webpack/server.edge');
54 ReactServerDOMClient = require('react-server-dom-webpack/client.edge');
55 ReactDOMServer = require('react-dom/server.edge');
56 ReactDOMClient = require('react-dom/client');
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js
+1 -1
@@ -42,7 +42,7 @@ describe('ReactFlightDOMNode', () => {
42 webpackModuleLoading = WebpackMock.moduleLoading;
43
44 jest.resetModules();
45 - jest.unmock('react');
45 + __unmockReact();
46 jest.unmock('react-server-dom-webpack/server');
47 jest.mock('react-server-dom-webpack/client', () =>
48 require('react-server-dom-webpack/client.node'),
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js
+2
@@ -24,6 +24,7 @@ describe('ReactFlightDOMReply', () => {
24 beforeEach(() => {
25 jest.resetModules();
26 // Simulate the condition resolution
27 + jest.mock('react', () => require('react/react.shared-subset'));
28 jest.mock('react-server-dom-webpack/server', () =>
29 require('react-server-dom-webpack/server.browser'),
30 );
@@ -31,6 +32,7 @@ describe('ReactFlightDOMReply', () => {
32 // serverExports = WebpackMock.serverExports;
33 webpackServerMap = WebpackMock.webpackServerMap;
34 ReactServerDOMServer = require('react-server-dom-webpack/server.browser');
35 + jest.resetModules();
36 ReactServerDOMClient = require('react-server-dom-webpack/client');
37 });
38
packages/react-server/src/ReactFlightServer.js
+4 -3
@@ -104,7 +104,7 @@ import {
104 } from 'shared/ReactSerializationErrors';
105
106 import {getOrCreateServerContext} from 'shared/ReactServerContextRegistry';
107 -import ReactSharedInternals from 'shared/ReactSharedInternals';
107 +import ReactServerSharedInternals from './ReactServerSharedInternals';
108 import isArray from 'shared/isArray';
109 import {SuspenseException, getSuspendedThenable} from './ReactFlightThenable';
110
@@ -197,8 +197,9 @@ export type Request = {
197 toJSON: (key: string, value: ReactClientValue) => ReactJSONValue,
198 };
199
200 -const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
201 -const ReactCurrentCache = ReactSharedInternals.ReactCurrentCache;
200 +const ReactCurrentDispatcher =
201 + ReactServerSharedInternals.ReactCurrentDispatcher;
202 +const ReactCurrentCache = ReactServerSharedInternals.ReactCurrentCache;
203
204 function defaultErrorHandler(error: mixed) {
205 console['error'](error);
packages/react-server/src/ReactServerSharedInternals.js new
+24
@@ -0,0 +1,24 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import * as React from 'react';
11 +
12 +const ReactSharedServerInternals =
13 + // $FlowFixMe: It's defined in the one we resolve to.
14 + React.__SECRET_SERVER_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
15 +
16 +if (!ReactSharedServerInternals) {
17 + throw new Error(
18 + 'The "react" package in this environment is not configured correctly. ' +
19 + 'The "react-server" condition must be enabled in any environment that ' +
20 + 'runs React Server Components.',
21 + );
22 +}
23 +
24 +export default ReactSharedServerInternals;
packages/react/src/React.js
+1 -1
@@ -67,7 +67,7 @@ import {
67 cloneElementWithValidation,
68 } from './ReactElementValidator';
69 import {createServerContext} from './ReactServerContext';
70 -import ReactSharedInternals from './ReactSharedInternals';
70 +import ReactSharedInternals from './ReactSharedInternalsClient';
71 import {startTransition} from './ReactStartTransition';
72 import {act} from './ReactAct';
73
packages/react/src/ReactServerContext.js
+1 -3
@@ -19,9 +19,7 @@ import type {
19 } from 'shared/ReactTypes';
20
21 import {enableServerContext} from 'shared/ReactFeatureFlags';
22 -import ReactSharedInternals from 'shared/ReactSharedInternals';
23 -
24 -const ContextRegistry = ReactSharedInternals.ContextRegistry;
22 +import {ContextRegistry} from './ReactServerContextRegistry';
23
24 export function createServerContext<T: ServerContextJSONValue>(
25 globalName: string,
packages/react/src/ReactServerSharedInternals.js new
+16
@@ -0,0 +1,16 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import ReactCurrentDispatcher from './ReactCurrentDispatcher';
9 +import ReactCurrentCache from './ReactCurrentCache';
10 +
11 +const ReactServerSharedInternals = {
12 + ReactCurrentDispatcher,
13 + ReactCurrentCache,
14 +};
15 +
16 +export default ReactServerSharedInternals;
packages/react/src/ReactSharedInternalsClient.js renamed
packages/react/src/ReactSharedInternalsServer.js new
+25
@@ -0,0 +1,25 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import ReactCurrentOwner from './ReactCurrentOwner';
9 +import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';
10 +import {enableServerContext} from 'shared/ReactFeatureFlags';
11 +import {ContextRegistry} from './ReactServerContextRegistry';
12 +
13 +const ReactSharedInternals = {
14 + ReactCurrentOwner,
15 +};
16 +
17 +if (__DEV__) {
18 + ReactSharedInternals.ReactDebugCurrentFrame = ReactDebugCurrentFrame;
19 +}
20 +
21 +if (enableServerContext) {
22 + ReactSharedInternals.ContextRegistry = ContextRegistry;
23 +}
24 +
25 +export default ReactSharedInternals;
packages/react/src/ReactSharedSubset.experimental.js
+4 -1
@@ -10,8 +10,11 @@
10 // Patch fetch
11 import './ReactFetch';
12
13 +export {default as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './ReactSharedInternalsServer';
14 +
15 +export {default as __SECRET_SERVER_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './ReactServerSharedInternals';
16 +
17 export {
14 - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
18 Children,
19 Fragment,
20 Profiler,
packages/react/src/ReactSharedSubset.js
+4 -1
@@ -10,8 +10,11 @@
10 // Patch fetch
11 import './ReactFetch';
12
13 +export {default as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './ReactSharedInternalsServer';
14 +
15 +export {default as __SECRET_SERVER_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './ReactServerSharedInternals';
16 +
17 export {
14 - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
18 Children,
19 Fragment,
20 Profiler,
packages/react/src/__tests__/ReactFetch-test.js
+7 -8
@@ -32,6 +32,7 @@ async function fetchMock(resource, options) {
32 }
33
34 let React;
35 +let ReactServer;
36 let ReactServerDOMServer;
37 let ReactServerDOMClient;
38 let use;
@@ -43,23 +44,21 @@ describe('ReactFetch', () => {
44 fetchCount = 0;
45 global.fetch = fetchMock;
46
46 - if (gate(flags => !flags.www)) {
47 - jest.mock('react', () => require('react/react.shared-subset'));
48 - }
47 + jest.mock('react', () => require('react/react.shared-subset'));
48 jest.mock('react-server-dom-webpack/server', () =>
49 require('react-server-dom-webpack/server.browser'),
50 );
51 require('react-server-dom-webpack/src/__tests__/utils/WebpackMock');
53 -
54 - React = require('react');
52 ReactServerDOMServer = require('react-server-dom-webpack/server');
53 + ReactServer = require('react');
54
55 jest.resetModules();
58 - jest.unmock('react');
56 + __unmockReact();
57 jest.unmock('react-server-dom-webpack/server');
58 ReactServerDOMClient = require('react-server-dom-webpack/client');
61 - use = React.use;
62 - cache = React.cache;
59 + React = require('react');
60 + use = ReactServer.use;
61 + cache = ReactServer.cache;
62 });
63
64 async function render(Component) {
packages/react/src/__tests__/ReactFetchEdge-test.js
+2 -4
@@ -48,9 +48,7 @@ describe('ReactFetch', () => {
48 fetchCount = 0;
49 global.fetch = fetchMock;
50
51 - if (gate(flags => !flags.www)) {
52 - jest.mock('react', () => require('react/react.shared-subset'));
53 - }
51 + jest.mock('react', () => require('react/react.shared-subset'));
52 jest.mock('react-server-dom-webpack/server', () =>
53 require('react-server-dom-webpack/server.edge'),
54 );
@@ -60,7 +58,7 @@ describe('ReactFetch', () => {
58 ReactServerDOMServer = require('react-server-dom-webpack/server');
59
60 jest.resetModules();
63 - jest.unmock('react');
61 + __unmockReact();
62 jest.unmock('react-server-dom-webpack/server');
63 ReactServerDOMClient = require('react-server-dom-webpack/client');
64 use = React.use;
scripts/error-codes/codes.json
+2 -1
@@ -476,5 +476,6 @@
476 "488": "Couldn't find all resumable slots by key/index during replaying. The tree doesn't match so React will fallback to client rendering.",
477 "489": "Expected to see a component of type \"%s\" in this slot. The tree doesn't match so React will fallback to client rendering.",
478 "490": "Expected to see a Suspense boundary in this slot. The tree doesn't match so React will fallback to client rendering.",
479 - "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."
479 + "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.",
480 + "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."
481 }
\ No newline at end of file
scripts/jest/setupHostConfigs.js
+15 -8
@@ -43,13 +43,20 @@ function resolveEntryFork(resolvedEntry, isFBBundle) {
43 return resolvedEntry;
44 }
45
46 -jest.mock('react', () => {
47 - const resolvedEntryPoint = resolveEntryFork(
48 - require.resolve('react'),
49 - global.__WWW__
50 - );
51 - return jest.requireActual(resolvedEntryPoint);
52 -});
46 +function mockReact() {
47 + jest.mock('react', () => {
48 + const resolvedEntryPoint = resolveEntryFork(
49 + require.resolve('react'),
50 + global.__WWW__
51 + );
52 + return jest.requireActual(resolvedEntryPoint);
53 + });
54 +}
55 +
56 +// When we want to unmock React we really need to mock it again.
57 +global.__unmockReact = mockReact;
58 +
59 +mockReact();
60
61 jest.mock('react/react.shared-subset', () => {
62 const resolvedEntryPoint = resolveEntryFork(
@@ -162,7 +169,7 @@ inlinedHostConfigs.forEach(rendererInfo => {
169 // Make it possible to import this module inside
170 // the React package itself.
171 jest.mock('shared/ReactSharedInternals', () =>
165 - jest.requireActual('react/src/ReactSharedInternals')
172 + jest.requireActual('react/src/ReactSharedInternalsClient')
173 );
174
175 // Make it possible to import this module inside
scripts/jest/setupTests.build.js
+2
@@ -1,3 +1,5 @@
1 'use strict';
2
3 jest.mock('scheduler', () => jest.requireActual('scheduler/unstable_mock'));
4 +
5 +global.__unmockReact = () => jest.unmock('react');
scripts/rollup/forks.js
+5 -2
@@ -60,8 +60,11 @@ const forks = Object.freeze({
60 entry,
61 dependencies
62 ) => {
63 - if (entry === 'react' || entry === 'react/src/ReactSharedSubset.js') {
64 - return './packages/react/src/ReactSharedInternals.js';
63 + if (entry === 'react') {
64 + return './packages/react/src/ReactSharedInternalsClient.js';
65 + }
66 + if (entry === 'react/src/ReactSharedSubset.js') {
67 + return './packages/react/src/ReactSharedInternalsServer.js';
68 }
69 if (!entry.startsWith('react/') && dependencies.indexOf('react') === -1) {
70 // React internals are unavailable if we can't reference the package.