When you use the `createFromFetch` API we assume that the start time of
the request is the same time as when you call `createFromFetch` but in
principle you could use it with a Promise that starts earlier and just
happens to resolve to a `Response`.
When you use `createFromReadableStream` that is almost definitely the
case. E.g. you might have started it way earlier and you don't call
`createFromReadableStream` until you get the headers back (the fetch
promise resolves).
This adds an option to pass in the start time for debug purposes if you
started the request before starting to parse it.
One thing that can suspend is the downloading of the RSC stream itself.
This tracks an I/O entry for each Promise (`SomeChunk<T>`) that
represents the request to the RSC stream. As the value we use the
`Response` for `createFromFetch` (or the `ReadableStream` for
`createFromReadableStream`). The start time is when you called those.
Since we're not awaiting the whole stream, each I/O entry represents the
part of the stream up until it got unblocked. However, in a production
environment with TLS packets and buffering in practice the chunks
received by the client isn't exactly at the boundary of each row. It's a
bit longer into larger chunks. From testing, it seems like multiples of
16kb or 64kb uncompressed are common. To simulate a production
environment we group into roughly 64kb chunks if they happen in rapid
sequence. Note that this might be too small to give a good idea because
of the throttle many boundaries might be skipped anyway so this might
show too many.
The React DevTools will see each I/O entry as separate but dedupe if an
outer boundary already depends on the same chunk. This deduping makes it
so that small boundaries that are blocked on the same chunk, don't get
treated as having unique suspenders. If you have a boundary with large
content, then that content will likely be in a separate chunk which is
not in the parent and then it gets marked as.
This is all just an approximation. The goal of this is just to highlight
that very large boundaries will very likely suspend even if they don't
suspend on any I/O on the server. In practice, these boundaries can
float around a lot and it's really any Suspense boundary that might
suspend but some are more likely than others which this is meant to
highlight.
It also just lets you inspect how many bytes needs to be transferred
before you can show a particular part of the content, to give you an
idea that it's not just I/O on the server that might suspend.
If you don't use the debug channel it can be misleading since the data
in development mode stream will have a lot more data in it which leads
to more chunking.
Similarly to "client references" these I/O infos don't have an "env"
since it's the client that has the I/O and so those are excluded from
flushing in the Server performance tracks.
Note that currently the same Response can appear many times in the same
Instance of SuspenseNode in DevTools when there are multiple chunks. In
a follow up I'll show only the last one per Response at any given level.
Note that when a separate debugChannel is used it has its own I/O entry
that's on the `_debugInfo` for the debug chunks in that channel.
However, if everything works correctly these should never leak into the
DevTools UI since they should never be propagated from a debug chunk to
the values waited by the runtime. This is easy to break though.
This lets us pass a writable on the server side and readable on the
client side to send debug info through a separate channel so that it
doesn't interfere with the main payload as much. The main payload refers
to chunks defined in the debug info which means it's still blocked on it
though. This ensures that the debug data has loaded by the time the
value is rendered so that the next step can forward the data.
This will be a bit fragile to race conditions until #33665 lands.
Another follow up needed is the ability to skip the debug channel on the
receiving side. Right now it'll block forever if you don't provide one
since we're blocking on the debug data.
This adds plumbing for opening a stream from the Flight Client to the
Flight Server so it can ask for more data on-demand. In this mode, the
Flight Server keeps the connection open as long as the client is still
alive and there's more objects to load. It retains any depth limited
objects so that they can be asked for later. In this first PR it just
releases the object when it's discovered on the server and doesn't
actually lazy load it yet. That's coming in a follow up.
This strategy is built on the model that each request has its own
channel for this. Instead of some global registry. That ensures that
referential identity is preserved within a Request and the Request can
refer to previously written objects by reference.
The fixture implements a WebSocket per request but it doesn't have to be
done that way. It can be multiplexed through an existing WebSocket for
example. The current protocol is just a Readable(Stream) on the server
and WritableStream on the client. It could even be sent through a HTTP
request body if browsers implemented full duplex (which they don't).
This PR only implements the direction of messages from Client to Server.
However, I also plan on adding Debug Channel in the other direction to
allow debug info (optionally) be sent from Server to Client through this
channel instead of through the main RSC request. So the `debugChannel`
option will be able to take writable or readable or both.
---------
Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
<!--
Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.
Before submitting a pull request, please make sure the following is
done:
1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
10. If you haven't already, complete the CLA.
Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->
## Summary
In order to adopt react 19's ref-as-prop model, Flow needs to eliminate
all the places where they are treated differently.
`React.AbstractComponent` is the worst example of this, and we need to
eliminate it.
This PR eliminates them from the react repo, and only keeps the one that
has 1 argument of props.
## How did you test this change?
yarn flow
Stacked on #31299.
We already have an option for resolving Client References to other
Client References when consuming an RSC payload on the server.
This lets you resolve Server References on the consuming side when the
environment where you're consuming the RSC payload also has access to
those Server References. Basically they becomes like Client References
for this consumer but for another consumer they wouldn't be.