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.