mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
When we have a debug channel open that can ask for more objects. That doesn't close until all lazy objects have been explicitly asked for. If you GC an object before the lazy references inside of it before asking for or releasing the objects, then it'll never close. This ensures that if there are no more PendingChunk and no more ResolvedModelChunk then we can close the connection. There's two sources of retaining the Response object. On one side we have a handle to it from the stream coming from the server. On the other side we have a handle to it from ResolvedModelChunk to ask for more data when we lazily parse a model. This PR makes a weak handle from the stream to the Response. However, it keeps a strong reference alive whenever we're waiting on a pending chunk because then the stream might be the root if the only listeners are the callbacks passed to the promise and no references to the promise itself. The pending chunks count can end up being zero even if we might get more data because the references might be inside lazy chunks. In this case the lazy chunks keeps the Response alive. When the lazy chunk gets parsed it can find more chunks that then end up pending to keep the response strongly alive until they resolve.
132 lines
3.1 KiB
JavaScript
132 lines
3.1 KiB
JavaScript
import * as React from 'react';
|
|
import {use, Suspense, useState, startTransition, Profiler} from 'react';
|
|
import ReactDOM from 'react-dom/client';
|
|
import {createFromFetch, encodeReply} from 'react-server-dom-webpack/client';
|
|
|
|
// TODO: This should be a dependency of the App but we haven't implemented CSS in Node yet.
|
|
import './style.css';
|
|
|
|
function findSourceMapURL(fileName) {
|
|
return (
|
|
document.location.origin +
|
|
'/source-maps?name=' +
|
|
encodeURIComponent(fileName)
|
|
);
|
|
}
|
|
|
|
let updateRoot;
|
|
async function callServer(id, args) {
|
|
let response;
|
|
if (
|
|
process.env.NODE_ENV === 'development' &&
|
|
typeof WebSocketStream === 'function'
|
|
) {
|
|
const requestId = crypto.randomUUID();
|
|
const wss = new WebSocketStream(
|
|
'ws://localhost:3001/debug-channel?' + requestId
|
|
);
|
|
const debugChannel = await wss.opened;
|
|
response = createFromFetch(
|
|
fetch('/', {
|
|
method: 'POST',
|
|
headers: {
|
|
Accept: 'text/x-component',
|
|
'rsc-action': id,
|
|
'rsc-request-id': requestId,
|
|
},
|
|
body: await encodeReply(args),
|
|
}),
|
|
{
|
|
callServer,
|
|
debugChannel,
|
|
findSourceMapURL,
|
|
}
|
|
);
|
|
} else {
|
|
response = createFromFetch(
|
|
fetch('/', {
|
|
method: 'POST',
|
|
headers: {
|
|
Accept: 'text/x-component',
|
|
'rsc-action': id,
|
|
},
|
|
body: await encodeReply(args),
|
|
}),
|
|
{
|
|
callServer,
|
|
findSourceMapURL,
|
|
}
|
|
);
|
|
}
|
|
const {returnValue, root} = await response;
|
|
// Refresh the tree with the new RSC payload.
|
|
startTransition(() => {
|
|
updateRoot(root);
|
|
});
|
|
return returnValue;
|
|
}
|
|
|
|
function Shell({data}) {
|
|
const [root, setRoot] = useState(data);
|
|
updateRoot = setRoot;
|
|
return root;
|
|
}
|
|
|
|
async function hydrateApp() {
|
|
let response;
|
|
if (
|
|
process.env.NODE_ENV === 'development' &&
|
|
typeof WebSocketStream === 'function'
|
|
) {
|
|
const requestId = crypto.randomUUID();
|
|
const wss = new WebSocketStream(
|
|
'ws://localhost:3001/debug-channel?' + requestId
|
|
);
|
|
const debugChannel = await wss.opened;
|
|
response = createFromFetch(
|
|
fetch('/', {
|
|
headers: {
|
|
Accept: 'text/x-component',
|
|
'rsc-request-id': requestId,
|
|
},
|
|
}),
|
|
{
|
|
callServer,
|
|
debugChannel,
|
|
findSourceMapURL,
|
|
}
|
|
);
|
|
} else {
|
|
response = createFromFetch(
|
|
fetch('/', {
|
|
headers: {
|
|
Accept: 'text/x-component',
|
|
},
|
|
}),
|
|
{
|
|
callServer,
|
|
findSourceMapURL,
|
|
}
|
|
);
|
|
}
|
|
const {root, returnValue, formState} = await response;
|
|
|
|
ReactDOM.hydrateRoot(
|
|
document,
|
|
<Profiler id="root">
|
|
<Shell data={root} />
|
|
</Profiler>,
|
|
{
|
|
// TODO: This part doesn't actually work because the server only returns
|
|
// form state during the request that submitted the form. Which means it
|
|
// the state needs to be transported as part of the HTML stream. We intend
|
|
// to add a feature to Fizz for this, but for now it's up to the
|
|
// metaframework to implement correctly.
|
|
formState: formState,
|
|
}
|
|
);
|
|
}
|
|
|
|
// Remove this line to simulate MPA behavior
|
|
hydrateApp();
|