25
let webpackMap;
26
let Stream;
27
let React;
28
+let ReactDOM;
29
let ReactDOMClient;
30
let ReactServerDOMServer;
31
let ReactServerDOMClient;
32
+let ReactDOMFizzServer;
33
let Suspense;
34
let ErrorBoundary;
35
44
45
Stream = require('stream');
46
React = require('react');
47
+ ReactDOM = require('react-dom');
48
+ ReactDOMFizzServer = require('react-dom/server.node');
49
use = React.use;
50
Suspense = React.Suspense;
51
ReactDOMClient = require('react-dom/client');
1157
);
1158
expect(reportedErrors).toEqual([theError]);
1159
});
1160
+
1161
+ // @gate enableUseHook
1162
+ it('should support ReactDOM.preload when rendering in Fiber', async () => {
1163
+ function Component() {
1164
+ return <p>hello world</p>;
1165
+ }
1166
+
1167
+ const ClientComponent = clientExports(Component);
1168
+
1169
+ async function ServerComponent() {
1170
+ ReactDOM.preload('before', {as: 'style'});
1171
+ await 1;
1172
+ ReactDOM.preload('after', {as: 'style'});
1173
+ return <ClientComponent />;
1174
+ }
1175
+
1176
+ const {writable, readable} = getTestStream();
1177
+ const {pipe} = ReactServerDOMServer.renderToPipeableStream(
1178
+ <ServerComponent />,
1179
+ webpackMap,
1180
+ );
1181
+ pipe(writable);
1182
+
1183
+ let response = null;
1184
+ function getResponse() {
1185
+ if (response === null) {
1186
+ response = ReactServerDOMClient.createFromReadableStream(readable);
1187
+ }
1188
+ return response;
1189
+ }
1190
+
1191
+ function App() {
1192
+ return getResponse();
1193
+ }
1194
+
1195
+ // We pause to allow the float call after the await point to process before the
1196
+ // HostDispatcher gets set for Fiber by createRoot. This is only needed in testing
1197
+ // because the module graphs are not different and the HostDispatcher is shared.
1198
+ // In a real environment the Fiber and Flight code would each have their own independent
1199
+ // dispatcher.
1200
+ // @TODO consider what happens when Server-Components-On-The-Client exist. we probably
1201
+ // want to use the Fiber HostDispatcher there too since it is more about the host than the runtime
1202
+ // but we need to make sure that actually makes sense
1203
+ await 1;
1204
+
1205
+ const container = document.createElement('div');
1206
+ const root = ReactDOMClient.createRoot(container);
1207
+ await act(() => {
1208
+ root.render(<App />);
1209
+ });
1210
+ expect(document.head.innerHTML).toBe(
1211
+ '<link href="before" rel="preload" as="style">' +
1212
+ '<link href="after" rel="preload" as="style">',
1213
+ );
1214
+ expect(container.innerHTML).toBe('<p>hello world</p>');
1215
+ });
1216
+
1217
+ // @gate enableUseHook
1218
+ it('should support ReactDOM.preload when rendering in Fizz', async () => {
1219
+ function Component() {
1220
+ return <p>hello world</p>;
1221
+ }
1222
+
1223
+ const ClientComponent = clientExports(Component);
1224
+
1225
+ async function ServerComponent() {
1226
+ ReactDOM.preload('before', {as: 'style'});
1227
+ await 1;
1228
+ ReactDOM.preload('after', {as: 'style'});
1229
+ return <ClientComponent />;
1230
+ }
1231
+
1232
+ const {writable: flightWritable, readable: flightReadable} =
1233
+ getTestStream();
1234
+ const {writable: fizzWritable, readable: fizzReadable} = getTestStream();
1235
+
1236
+ // In a real environment you would want to call the render during the Fizz render.
1237
+ // The reason we cannot do this in our test is because we don't actually have two separate
1238
+ // module graphs and we are contriving the sequencing to work in a way where
1239
+ // the right HostDispatcher is in scope during the Flight Server Float calls and the
1240
+ // Flight Client hint dispatches
1241
+ const {pipe} = ReactServerDOMServer.renderToPipeableStream(
1242
+ <ServerComponent />,
1243
+ webpackMap,
1244
+ );
1245
+ pipe(flightWritable);
1246
+
1247
+ let response = null;
1248
+ function getResponse() {
1249
+ if (response === null) {
1250
+ response =
1251
+ ReactServerDOMClient.createFromReadableStream(flightReadable);
1252
+ }
1253
+ return response;
1254
+ }
1255
+
1256
+ function App() {
1257
+ return (
1258
+ <html>
1259
+ <body>{getResponse()}</body>
1260
+ </html>
1261
+ );
1262
+ }
1263
+
1264
+ await act(async () => {
1265
+ ReactDOMFizzServer.renderToPipeableStream(<App />).pipe(fizzWritable);
1266
+ });
1267
+
1268
+ const decoder = new TextDecoder();
1269
+ const reader = fizzReadable.getReader();
1270
+ let content = '';
1271
+ while (true) {
1272
+ const {done, value} = await reader.read();
1273
+ if (done) {
1274
+ content += decoder.decode();
1275
+ break;
1276
+ }
1277
+ content += decoder.decode(value, {stream: true});
1278
+ }
1279
+
1280
+ expect(content).toEqual(
1281
+ '<!DOCTYPE html><html><head><link rel="preload" as="style" href="before"/>' +
1282
+ '<link rel="preload" as="style" href="after"/></head><body><p>hello world</p></body></html>',
1283
+ );
1284
+ });
1285
+
1286
+ it('supports Float hints from concurrent Flight -> Fizz renders', async () => {
1287
+ function Component() {
1288
+ return <p>hello world</p>;
1289
+ }
1290
+
1291
+ const ClientComponent = clientExports(Component);
1292
+
1293
+ async function ServerComponent1() {
1294
+ ReactDOM.preload('before1', {as: 'style'});
1295
+ await 1;
1296
+ ReactDOM.preload('after1', {as: 'style'});
1297
+ return <ClientComponent />;
1298
+ }
1299
+
1300
+ async function ServerComponent2() {
1301
+ ReactDOM.preload('before2', {as: 'style'});
1302
+ await 1;
1303
+ ReactDOM.preload('after2', {as: 'style'});
1304
+ return <ClientComponent />;
1305
+ }
1306
+
1307
+ const {writable: flightWritable1, readable: flightReadable1} =
1308
+ getTestStream();
1309
+ const {writable: flightWritable2, readable: flightReadable2} =
1310
+ getTestStream();
1311
+
1312
+ ReactServerDOMServer.renderToPipeableStream(
1313
+ <ServerComponent1 />,
1314
+ webpackMap,
1315
+ ).pipe(flightWritable1);
1316
+
1317
+ ReactServerDOMServer.renderToPipeableStream(
1318
+ <ServerComponent2 />,
1319
+ webpackMap,
1320
+ ).pipe(flightWritable2);
1321
+
1322
+ const responses = new Map();
1323
+ function getResponse(stream) {
1324
+ let response = responses.get(stream);
1325
+ if (!response) {
1326
+ response = ReactServerDOMClient.createFromReadableStream(stream);
1327
+ responses.set(stream, response);
1328
+ }
1329
+ return response;
1330
+ }
1331
+
1332
+ function App({stream}) {
1333
+ return (
1334
+ <html>
1335
+ <body>{getResponse(stream)}</body>
1336
+ </html>
1337
+ );
1338
+ }
1339
+
1340
+ // pausing to let Flight runtime tick. This is a test only artifact of the fact that
1341
+ // we aren't operating separate module graphs for flight and fiber. In a real app
1342
+ // each would have their own dispatcher and there would be no cross dispatching.
1343
+ await 1;
1344
+
1345
+ const {writable: fizzWritable1, readable: fizzReadable1} = getTestStream();
1346
+ const {writable: fizzWritable2, readable: fizzReadable2} = getTestStream();
1347
+ await act(async () => {
1348
+ ReactDOMFizzServer.renderToPipeableStream(
1349
+ <App stream={flightReadable1} />,
1350
+ ).pipe(fizzWritable1);
1351
+ ReactDOMFizzServer.renderToPipeableStream(
1352
+ <App stream={flightReadable2} />,
1353
+ ).pipe(fizzWritable2);
1354
+ });
1355
+
1356
+ async function read(stream) {
1357
+ const decoder = new TextDecoder();
1358
+ const reader = stream.getReader();
1359
+ let buffer = '';
1360
+ while (true) {
1361
+ const {done, value} = await reader.read();
1362
+ if (done) {
1363
+ buffer += decoder.decode();
1364
+ break;
1365
+ }
1366
+ buffer += decoder.decode(value, {stream: true});
1367
+ }
1368
+ return buffer;
1369
+ }
1370
+
1371
+ const [content1, content2] = await Promise.all([
1372
+ read(fizzReadable1),
1373
+ read(fizzReadable2),
1374
+ ]);
1375
+
1376
+ expect(content1).toEqual(
1377
+ '<!DOCTYPE html><html><head><link rel="preload" as="style" href="before1"/>' +
1378
+ '<link rel="preload" as="style" href="after1"/></head><body><p>hello world</p></body></html>',
1379
+ );
1380
+ expect(content2).toEqual(
1381
+ '<!DOCTYPE html><html><head><link rel="preload" as="style" href="before2"/>' +
1382
+ '<link rel="preload" as="style" href="after2"/></head><body><p>hello world</p></body></html>',
1383
+ );
1384
+ });
1385
+
1386
+ it('supports deduping hints by Float key', async () => {
1387
+ function Component() {
1388
+ return <p>hello world</p>;
1389
+ }
1390
+
1391
+ const ClientComponent = clientExports(Component);
1392
+
1393
+ async function ServerComponent() {
1394
+ ReactDOM.prefetchDNS('dns');
1395
+ ReactDOM.preconnect('preconnect');
1396
+ ReactDOM.preload('load', {as: 'style'});
1397
+ ReactDOM.preinit('init', {as: 'script'});
1398
+ // again but vary preconnect to demonstrate crossOrigin participates in the key
1399
+ ReactDOM.prefetchDNS('dns');
1400
+ ReactDOM.preconnect('preconnect', {crossOrigin: 'anonymous'});
1401
+ ReactDOM.preload('load', {as: 'style'});
1402
+ ReactDOM.preinit('init', {as: 'script'});
1403
+ await 1;
1404
+ // after an async point
1405
+ ReactDOM.prefetchDNS('dns');
1406
+ ReactDOM.preconnect('preconnect', {crossOrigin: 'use-credentials'});
1407
+ ReactDOM.preload('load', {as: 'style'});
1408
+ ReactDOM.preinit('init', {as: 'script'});
1409
+ return <ClientComponent />;
1410
+ }
1411
+
1412
+ const {writable, readable} = getTestStream();
1413
+
1414
+ ReactServerDOMServer.renderToPipeableStream(
1415
+ <ServerComponent />,
1416
+ webpackMap,
1417
+ ).pipe(writable);
1418
+
1419
+ const hintRows = [];
1420
+ async function collectHints(stream) {
1421
+ const decoder = new TextDecoder();
1422
+ const reader = stream.getReader();
1423
+ let buffer = '';
1424
+ while (true) {
1425
+ const {done, value} = await reader.read();
1426
+ if (done) {
1427
+ buffer += decoder.decode();
1428
+ if (buffer.includes(':H')) {
1429
+ hintRows.push(buffer);
1430
+ }
1431
+ break;
1432
+ }
1433
+ buffer += decoder.decode(value, {stream: true});
1434
+ let line;
1435
+ while ((line = buffer.indexOf('\n')) > -1) {
1436
+ const row = buffer.slice(0, line);
1437
+ buffer = buffer.slice(line + 1);
1438
+ if (row.includes(':H')) {
1439
+ hintRows.push(row);
1440
+ }
1441
+ }
1442
+ }
1443
+ }
1444
+
1445
+ await collectHints(readable);
1446
+ expect(hintRows.length).toEqual(6);
1447
+ });
1448
});