Commit Graph

16 Commits

Author SHA1 Message Date
sebmarkbage 3ff23f9234 [Flight] Better error message if you pass a function as a child to a client component (#28367)
Similar to #28362 but if you pass it to a client component.

DiffTrain build for [2e84e16299](https://github.com/facebook/react/commit/2e84e1629924e6cb278638305fa92040f6ef6eb5)
2024-02-19 17:41:20 +00:00
sebmarkbage 94683ffb3d [Flight] Allow a Server Reference to be registered twice (#28343)
It's possible for the same function instance to appear more than once in
the same graph or even the same file.

Currently this errors on trying to reconfigure the property but it
really doesn't matter which one wins. First or last.

Regardless there will be an entry point generated that can get them.

DiffTrain build for [9444c51c7e](https://github.com/facebook/react/commit/9444c51c7e44936c24e1f44db905cfd45c51cff3)
2024-02-19 16:54:24 +00:00
sebmarkbage edb6eccbe2 [Fizz] Align recoverable error serialization in dev mode (#28340)
Same as #28327 but for Fizz.

One thing that's weird about this recoverable error is that we don't
send the regular stack for it, just the component stack it seems. This
is missing some potential information and if we move toward integrated
since stacks it would be one thing.

DiffTrain build for [2e470a788e](https://github.com/facebook/react/commit/2e470a788e359e34feeadb422daaff046baf66cc)
2024-02-15 01:20:40 +00:00
sebmarkbage 0fda2b49c4 [Flight] Improve error message when it's not a real Error object (#28327)
Also deals with symbols. Alternative to #28312.

We currently always normalize rejections or thrown values into `Error`
objects. Partly because in prod it'll be an error object and you
shouldn't fork behavior on knowing the value outside a digest. We might
want to even make the message always opaque to avoid being tempted and
then discover in prod that it doesn't work.

However, we do include the message in DEV.

If this is a non-Error object we don't know what the properties mean.
Ofc, we don't want to include too much information in the rendered
string, so we use the general `describeObjectForErrorMessage` helper.
Unfortunately it's pretty conservative about emitting values so it's
likely to exclude any embedded string atm. Could potentially expand it a
bit.

We could in theory try to serialize as much as possible and re-throw the
actual object to allow for inspection to be expanded inside devtools
which is what I plan on for consoles, but since we're normalizing to an
Error this is in conflict with that approach.

DiffTrain build for [a7144f297c](https://github.com/facebook/react/commit/a7144f297c1c6fe457ca30ce6a211ab59feac11e)
2024-02-14 23:26:54 +00:00
sebmarkbage c4aeba9155 [Flight] Transfer Debug Info in Server-to-Server Flight Requests (#28275)
A Flight Server can be a consumer of a stream from another Server. In
this case the meta data is attached to debugInfo properties on lazy,
Promises, Arrays or Elements that might in turn get forwarded to the
next stream. In this case we want to forward this debug information to
the client in the stream.

I also added a DEV only `environmentName` option to the Flight Server.
This lets you name the server that is producing the debug info so that
you can trace the origin of where that component is executing. This
defaults to `"server"`. DevTools could use this for badges or different
colors.

DiffTrain build for [629541bcc0](https://github.com/facebook/react/commit/629541bcc09fc7c0cc5c257541d084ee27457512)
2024-02-12 18:43:27 +00:00
sebmarkbage b2392647b5 [Flight] Serialize deduped elements by direct reference even if they suspend (#28283)
In #28123 I switched these to be lazy references. However that creates a
lazy wrapper even if they're synchronously available. We try to as much
as possible preserve the original data structure in these cases.

E.g. here in the dev outlining I only use a lazy wrapper if it didn't
complete synchronously:
https://github.com/facebook/react/pull/28272/files#diff-d4c9c509922b3671d3ecce4e051df66dd5c3d38ff913c7a7fe94abc3ba2ed72eR638

Unfortunately we don't have a data structure that tracks the status of
each emitted row. We could store the task in the map but then they
couldn't be GC:ed as they complete. We could maybe store the status of
each element but seems so heavy.

For now I just went back to direct reference which might be an issue
since it can suspend something higher up when deduped.

DiffTrain build for [ba5e6a8329](https://github.com/facebook/react/commit/ba5e6a8329c7194a2c573c037a37f24ce45ee58f)
2024-02-08 23:50:18 +00:00
sebmarkbage c064e1665c [Flight] Emit debug info for a Server Component (#28272)
This adds a new DEV-only row type `D` for DebugInfo. If we see this in
prod, that's an error. It can contain extra debug information about the
Server Components (or Promises) that were compiled away during the
server render. It's DEV-only since this can contain sensitive
information (similar to errors) and since it'll be a lot of data, but
it's worth using the same stream for simplicity rather than a
side-channel.

In this first pass it's just the Server Component's name but I'll keep
adding more debug info to the stream, and it won't always just be a
Server Component's stack frame.

Each row can get more debug rows data streaming in as it resolves and
renders multiple server components in a row.

The data structure is just a side-channel and it would be perfectly fine
to ignore the D rows and it would behave the same as prod. With this
data structure though the data is associated with the row ID / chunk, so
you can't have inline meta data. This means that an inline Server
Component that doesn't get an ID otherwise will need to be outlined. The
way I outline Server Components is using a direct reference where it's
synchronous though so on the client side it behaves the same (i.e.
there's no lazy wrapper in this case).

In most cases the `_debugInfo` is on the Promises that we yield and we
also expose this on the `React.Lazy` wrappers. In the case where it's a
synchronous render it might attach this data to Elements or Arrays
(fragments) too.

In a future PR I'll wire this information up with Fiber to stash it in
the Fiber data structures so that DevTools can pick it up. This property
and the information in it is not limited to Server Components. The name
of the property that we look for probably shouldn't be `_debugInfo`
since it's semi-public. Should consider the name we use for that.

If it's a synchronous render that returns a string or number (text node)
then we don't have anywhere to attach them to. We could add a
`React.Lazy` wrapper for those but I chose to prioritize keeping the
data structure untouched. Can be useful if you use Server Components to
render data instead of React Nodes.

DiffTrain build for [b229f540e2](https://github.com/facebook/react/commit/b229f540e2da91370611945f9875e00a96196df6)
2024-02-08 16:06:43 +00:00
sebmarkbage 7a0151305a [Flight] Unify plain Server Component and forwardRef under one function (#28261)
This used to be trivial but it's no longer trivial.

In Fizz and Fiber this is split into renderWithHooks and
finishFunctionComponent since they also support indeterminate
components.

Interestingly thanks to this unification we always call functions with
an arity of 2 which is a bit weird - with the second argument being
undefined in everything except forwardRef and legacy context consumers.

This makes Flight makes the same thing but we could also call it with an
arity of 1.

Since Flight errors early if you try to pass it a ref, and there's no
legacy context, the second arg is always undefined.

The practical change in this PR is that returning a Promise from a
forwardRef now turns it into a lazy. We previously didn't support async
forwardRef since it wasn't supported on the client. However, since
eventually this will be supported by child-as-a-promise it seems fine to
support it.

DiffTrain build for [f07ac1e268](https://github.com/facebook/react/commit/f07ac1e2680a26c5b3bf9c651d62c792de71d46d)
2024-02-07 00:46:08 +00:00
sebmarkbage e46929f3ed [Flight] Move pendingChunks ref count increment into createTask (#28260)
Every time we create a task we need to wait for it so we increase a ref
count. We can do this in `createTask`. This is in line with what Fizz
does too.

They differ in that Flight counts when they're actually flushed where as
Fizz decrements them when they complete.

Flight should probably count them when they complete so it's possible to
wait for the end before flushing for buffering purposes.

DiffTrain build for [0d11563b4a](https://github.com/facebook/react/commit/0d11563b4a96e0f4f2361cdf7375b12375688163)
2024-02-06 21:22:53 +00:00
gaearon c178d6f4b1 [Flight] Delete Server Context (#28225)
Server Context was never documented, and has been deprecated in
https://github.com/facebook/react/pull/27424.

This PR removes it completely, including the implementation code.

Notably, `useContext` is removed from the shared subset, so importing it
from a React Server environment would now should be a build error in
environments that are able to enforce that.

DiffTrain build for [472854820b](https://github.com/facebook/react/commit/472854820bfd0058dfc85524051171c7b7c998c1)
2024-02-05 22:44:00 +00:00
sebmarkbage b505fe3f92 [Flight] Support Keyed Server Components (#28123)
Conceptually a Server Component in the tree is the same as a Client
Component.

When we render a Server Component with a key, that key should be used as
part of the reconciliation process to ensure the children's state are
preserved when they move in a set. The key of a child should also be
used to clear the state of the children when that key changes.

Conversely, if a Server Component doesn't have a key it should get an
implicit key based on the slot number. It should not inherit the key of
its children since the children don't know if that would collide with
other keys in the set the Server Component is rendered in.

A Client Component also has an identity based on the function's
implementation type. That mainly has to do with the state (or future
state after a refactor) that Component might contain. To transfer state
between two implementations it needs to be of the same state type. This
is not a concern for a Server Components since they never have state so
identity doesn't matter.

A Component returns a set of children. If it returns a single child,
that's the same as returning a fragment of one child. So if you
conditionally return a single child or a fragment, they should
technically reconcile against each other.

The simple way to do this is to simply emit a Fragment for every Server
Component. That would be correct in all cases. Unfortunately that is
also unfortunate since it bloats the payload in the common cases. It
also means that Fiber creates an extra indirection in the runtime.

Ideally we want to fold Server Component aways into zero cost on the
client. At least where possible. The common cases are that you don't
specify a key on a single return child, and that you do specify a key on
a Server Component in a dynamic set.

The approach in this PR treats a Server Component that returns other
Server Components or Lazy Nodes as a sequence that can be folded away.
I.e. the parts that don't generate any output in the RSC payload.
Instead, it keeps track of their keys on an internal "context". Which
gets reset after each new reified JSON node gets rendered.

Then we transfer the accumulated keys from any parent Server Components
onto the child element. In the simple case, the child just inherits the
key of the parent.

If the Server Component itself is keyless but a child isn't, we have to
add a wrapper fragment to ensure that this fragment gets the implicit
key but we can still use the key to reset state. This is unusual though
because typically if you keyed something it's because it was already in
a fragment.

In the case a Server Component is keyed but forks its children using a
fragment, we need to key that fragment so that the whole set can move
around as one. In theory this could be flattened into a parent array but
that gets tricky if something suspends, because then we can't send the
siblings early.

The main downside of this approach is that switching between single
child and fragment in a Server Component isn't always going to reconcile
against each other. That's because if we saw a single child first, we'd
have to add the fragment preemptively in case it forks later. This
semantic of React isn't very well known anyway and it might be ok to
break it here for pragmatic reasons. The tests document this
discrepancy.

Another compromise of this approach is that when combining keys we don't
escape them fully. We instead just use a simple `,` separated concat.
This is probably good enough in practice. Additionally, since we don't
encode the implicit 0 index slot key, you can move things around between
parents which shouldn't really reconcile but does. This keeps the keys
shorter and more human readable.

DiffTrain build for [95ec128399](https://github.com/facebook/react/commit/95ec128399a8b34884cc6bd90a041e03ce5c1844)
2024-02-05 17:38:19 +00:00
sebmarkbage b9b97d9ac8 [Flight/Fizz] Reset ThenableState Only in Branches Where It's Added (#28068)
Before, we used to reset the thenable state and extract the previous
state very early so that it's only the retried task that can possibly
consume it. This is nice because we can't accidentally consume that
state for any other node.

However, it does add a lot of branches of code that has to pass this
around. It also adds extra bytes on the stack per node. Even though it's
mostly just null.

This changes it so that where ever we can create a thenable state (e.g.
entering a component with hooks) we first extract this from the task.
The principle is that whatever could've created the thenable state in
the first place, must always be rerendered so it'll take the same code
paths to get there and so we'll always consume it.

DiffTrain build for [382190c595](https://github.com/facebook/react/commit/382190c595126837ee7e43b4fa953f4edd30e01c)
2024-01-26 00:58:02 +00:00
sebmarkbage 68cd468ecd [Flight] Refactor the Render Loop to Behave More Like Fizz (#28065)
This refactors the Flight render loop to behave more like Fizz with
similar naming conventions. So it's easier to apply similar techniques
across both. This is not necessarily better/faster - at least not yet.

This doesn't yet implement serialization by writing segments to chunks
but we probably should do that since the built-in parts that
`JSON.stringify` gets us isn't really much anymore (except serializing
strings). When we switch to that it probably makes sense for the whole
thing to be recursive.

Right now it's not technically fully recursive because each recursive
render returns the next JSON value to encode. So it's kind of like a
trampoline. This means we can't have many contextual things on the
stack. It needs to use the Server Context `__POP` trick. However, it
does work for things that are contextual only for one sequence of server
component abstractions in a row. Since those are now recursive.

An interesting observation here is that `renderModel` means that
anything can suspend while still serializing the outer siblings.
Typically only Lazy or Components would suspend but in principle a Proxy
can suspend/postpone too and now that is left serialized by reference to
a future value. It's only if the thing that we rendered was something
that can reduce to Lazy e.g. an Element that we can serialize it as a
lazy.

Similarly to how Suspense boundaries in Fizz can catch errors, anything
that can be reduced to Lazy can also catch an error rather than bubbling
it. It only errors when the Lazy resolves. Unlike Suspense boundaries
though, those things don't render anything so they're otherwise going to
use the destructive form. To ensure that throwing in an Element can
reuse the current task, this must be handled by `renderModel`, not for
example `renderElement`.

DiffTrain build for [b123b9c4f0](https://github.com/facebook/react/commit/b123b9c4f054a7def7ed84e350ccd46cc86672a6)
2024-01-25 17:14:06 +00:00
alunyov beb4a4d2d1 [RSC @ Meta] Simplify implementation of isClientReference, getClientReferenceKey, resolveClientReferenceMetadata (#27839)
For clientReferences we can just check the instance of the
`clientReference`.
The implementation of `isClientReference` is provided via configuration.
The class for ClientReference has to implement an interface that has
`getModuleId() method.

DiffTrain build for [cb2439624f](https://github.com/facebook/react/commit/cb2439624f43c510007f65aea5c50a8bb97917e4)
2023-12-19 14:22:53 +00:00
gnoff 7d8a8d9c3f [Flight] Support postponing through a serialized promise (#27818)
Postponing in a promise that is being serialized to the client from the
server should be possible however prior to this change Flight treated
this case like an error rather than a postpone. This fix adds support
for postponing in this position and adds a test asserting you can
successfully prerender the root if you unwrap this promise inside a
suspense boundary.

DiffTrain build for [5bcade5fcf](https://github.com/facebook/react/commit/5bcade5fcf5610e82e7cda05cc6de574bdace0c7)
2023-12-08 19:10:45 +00:00
alunyov 2f391b032a FB-specific builds of Flight Server, Flight Client, and React Shared Subset (#27579)
This PR adds a new FB-specific configuration of Flight. We also need to
bundle a version of ReactSharedSubset that will be used for running
Flight on the server.

This initial implementation does not support server actions yet.

The FB-Flight still uses the text protocol on the server (the flag
`enableBinaryFlight` is set to false). It looks like we need some
changes in Hermes to properly support this binary format.

DiffTrain build for [c17a27ef49](https://github.com/facebook/react/commit/c17a27ef492d9812351aecdfb017488e8e8404ce)
2023-11-27 23:40:03 +00:00