60 Commits
Author SHA1 Message Date
150f022444 [Flight] Ignore async stack frames when determining if a Promise was created from user space (#33739)
We use the stack of a Promise as the start of the I/O instead of the
actual I/O since that can symbolize the start of the operation even if
the actual I/O is batched, deduped or pooled. It can also group multiple
I/O operations into one.

We want the deepest possible Promise since otherwise it would just be
the Component's Promise.

However, we don't really need deeper than the boundary between first
party and third party. We can't just take the outer most that has third
party things on the stack though because third party can have callbacks
into first party and then we want the inner one. So we take the inner
most Promise that depends on I/O that has a first party stack on it.

The realization is that for the purposes of determining whether we have
a first party stack we need to ignore async stack frames. They can
appear on the stack when we resume third party code inside a resumption
frame of a first party stack.

<img width="832" alt="Screenshot 2025-07-08 at 6 34 25 PM"
src="https://github.com/user-attachments/assets/1636f980-be4c-4340-ad49-8d2b31953436"
/>

---------

Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
2025-07-09 09:08:09 -04:00
Sebastian MarkbågeandGitHub bbea677b77 [Flight] Lazy load objects from the debug channel (#33728)
When a debug channel is available, we now allow objects to be lazily
requested though the debug channel and only then will the server send
it.

The client will actually eagerly ask for the next level of objects once
it parses its payload. That way those objects have likely loaded by the
time you actually expand that deep e.g. in the console repl. This is
needed since the console repl is synchronous when you ask it to invoke
getters.

Each level is lazily parsed which means that we don't parse the next
level even though we eagerly loaded it. We parse it once the getter is
invoked (in Chrome DevTools you have to click a little `(...)` to invoke
the getter). When the getter is invoked, the chunk is initialized and
parsed. This then causes the next level to be asked for through the
debug channel. Ensuring that if you expand one more level you can do so
synchronously.

Currently debug chunks are eagerly parsed, which means that if you have
things like server component props that are lazy they can end up being
immediately asked for, but I'm trying to move to make the debug chunks
lazy.
2025-07-08 10:49:25 -04:00
Sebastian MarkbågeandGitHub 7cafeff340 [Flight] Close Debug Channel when All Lazy References Have Been GC:ed (#33718)
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.
2025-07-07 11:28:15 -04:00
bbc13fa17b [Flight] Add Debug Channel option for stateful connection to the backend in DEV (#33627)
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>
2025-06-24 11:16:09 -04:00
Sebastian MarkbågeandGitHub 2a911f27dd [Flight] Send the awaited Promise to the client as additional debug information (#33592)
Stacked on #33588, #33589 and #33590.

This lets us automatically show the resolved value in the UI.

<img width="863" alt="Screenshot 2025-06-22 at 12 54 41 AM"
src="https://github.com/user-attachments/assets/a66d1d5e-0513-4767-910c-5c7169fc2df4"
/>

We can also show rejected I/O that may or may not have been handled with
the error message.

<img width="838" alt="Screenshot 2025-06-22 at 12 55 06 AM"
src="https://github.com/user-attachments/assets/e0a8b6ae-08ba-46d8-8cc5-efb60956a1d1"
/>

To get this working we need to keep the Promise around for longer so
that we can access it once we want to emit an async sequence. I do this
by storing the WeakRefs but to ensure that the Promise doesn't get
garbage collected, I keep a WeakMap of Promise to the Promise that it
depended on. This lets the VM still clean up any Promise chains that
have leaves that are cleaned up. So this makes Promises live until the
last Promise downstream is done. At that point we can go back up the
chain to read the values out of them.

Additionally, to get the best possible value we don't want to get a
Promise that's used by internals of a third-party function. We want the
value that the first party gets to observe. To do this I had to change
the logic for which "await" to use, to be the one that is the first
await that happened in user space. It's not enough that the await has
any first party at all on the stack - it has to be the very first frame.
This is a little sketchy because it relies on the `.then()` call or
`await` call not having any third party wrappers. But it gives the best
object since it hides all the internals. For example when you call
`fetch()` we now log that actual `Response` object.
2025-06-23 10:12:45 -04:00
56408a5b12 [Flight] Emit timestamps only in forwards advancing time in debug info (#33482)
Previously you weren't guaranteed to have only advancing time entries,
you could jump back in time, but now it omits unnecessary duplicates and
clamps automatically if you emit a previous time entry to enforce
forwards order only.

The reason I didn't do this originally is because `await` can jump in
the order because we're trying to encode a graph into a flat timeline
for simplicity of the protocol and consumers.

```js
async function a() {
  await fetch1();
  await fetch2();
}

async function b() {
  await fetch3();
}

async function foo() {
  const p = a();
  await b();
  return p;
}
```

This can effectively create two parallel sequences:

```
--1.................----2.......--
------3......---------------------
```

This can now be flattened to either:

```
--1.................3---2.......--
```

Or:

```
------3......1......----2.......--
```

Depending on which one we visit first. Regardless, information is lost.

I'd say that the second one is worse encoding of this scenario because
it pretends that we weren't waiting for part of the timespan that we
were. To solve this I think we should probably make `emitAsyncSequence`
create a temporary flat list and then sort it by start time before
emitting.

Although we weren't actually blocked since there was some CPU time that
was able to proceed to get to 3. So maybe the second one is actually
better. If we wanted that consistently we'd have to figure out what the
intersection was.

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
2025-06-10 11:03:20 -04:00
Sebastian MarkbågeandGitHub 428ab82001 [Flight] Simulate fetch to third party in fixture (#33484)
This adds some I/O to go get the third party thing to test how it
overlaps.

With #33482, this is what it looks like. The await gets cut off when the
third party component starts rendering. I.e. after the latency to start.

<img width="735" alt="Screenshot 2025-06-08 at 5 42 46 PM"
src="https://github.com/user-attachments/assets/f68d9a84-05a1-4125-b3f0-8f3e4eaaa5c1"
/>

This doesn't fully simulate everything because it should actually also
simulate each chunk of the stream coming back too. We could wrap the
ReadableStream to simulate that. In that scenario, it would probably get
some awaits on the chunks at the end too.
2025-06-09 10:04:40 -04:00
Hendrik LiebauandGitHub c0b5a0cad3 [Flight] Use Web Streams APIs for 3rd-party component in Flight fixture (#33481) 2025-06-08 06:33:25 +02:00
Sebastian MarkbågeandGitHub b367b60927 [Flight] Add "use ..." boundary after the change instead of before it (#33478)
I noticed that the ThirdPartyComponent in the fixture was showing the
wrong stack and the `"use third-party"` is in the wrong location.

<img width="628" alt="Screenshot 2025-06-06 at 11 22 11 PM"
src="https://github.com/user-attachments/assets/f0013380-d79e-4765-b371-87fd61b3056b"
/>

When creating the initial JSX inside the third party server, we should
make sure that it has no owner. In a real cross-server environment you
get this by default by just executing in different context. But since
the fixture example is inside the same AsyncLocalStorage as the parent
it already has an owner which gets transferred. So we should make sure
that were we create the JSX has no owner to simulate this.

When we then parse a null owner on the receiving side, we replace its
owner/stack with the owner/stack of the call to `createFrom...` to
connect them. This worked fine with only two environments. The bug was
that when we did this and then transferred the result to a third
environment we took the original parsed stack trace. We should instead
parse a new one from the replaced stack in the current environment.

The second bug was that the `"use third-party"` badge ends up in the
wrong place when we do this kind of thing. Because the stack of the
thing entering the new environment is the call to `createFrom...` which
is in the old environment even though the component itself executes in
the new environment. So to see if there's a change we should be
comparing the current environment of the task to the owner's environment
instead of the next environment after the task.

After:

<img width="494" alt="Screenshot 2025-06-07 at 1 13 28 AM"
src="https://github.com/user-attachments/assets/e2e870ba-f125-4526-a853-bd29f164cf09"
/>
2025-06-07 11:28:57 -04:00
Hendrik LiebauandGitHub b1759882c0 [Flight] Bypass caches in Flight fixture if requested (#33445) 2025-06-06 06:42:58 +02:00
Hendrik LiebauandGitHub b4477d3800 [Flight] Add a cached 3rd-party component to the Flight fixture (#33443)
This should allow us to visualize what
https://github.com/facebook/react/pull/33438 is trying to convey.

An uncached 3rd-party component is displayed like this in the dev tools:

<img width="1072" alt="Screenshot 2025-06-05 at 12 57 32"
src="https://github.com/user-attachments/assets/d418ae23-d113-4dc9-98b8-ab426710454a"
/>

However, when the component is restored from a cache, it looks like
this:

<img width="1072" alt="Screenshot 2025-06-05 at 12 56 56"
src="https://github.com/user-attachments/assets/a0e34379-d8c0-4b14-8b54-b5c06211232b"
/>

The `Server Components ⚛` track is missing completely here, and the
`Loading profile...` phase also took way longer than without caching the
3rd-party component.

On `main`, the `Server Components ⚛` track is not missing:

<img width="1072" alt="Screenshot 2025-06-05 at 14 31 20"
src="https://github.com/user-attachments/assets/c35e405d-27ca-4b04-a34c-03bd959a7687"
/>

The cached 3rd-party component starts before the current render, and is
also not excluded here, which is of course expected without #33438.
2025-06-05 17:19:54 +02:00
Sebastian "Sebbie" SilbermannandGitHub 5717f1933f [react-dom] Enforce small gap between completed navigation and default Transition indicator (#33354) 2025-05-28 19:46:12 +02:00
Sebastian MarkbågeandGitHub 59440424d0 Implement Navigation API backed default indicator for DOM renderer (#33162)
Stacked on #33160.

By default, if `onDefaultTransitionIndicator` is not overridden, this
will trigger a fake Navigation event using the Navigation API. This is
intercepted to create an on-going navigation until we complete the
Transition. Basically each default Transition is simulated as a
Navigation.

This triggers the native browser loading state (in Chrome at least). So
now by default the browser spinner spins during a Transition if no other
loading state is provided. Firefox and Safari hasn't shipped Navigation
API yet and even in the flag Safari has, it doesn't actually trigger the
native loading state.

To ensures that you can still use other Navigations concurrently, we
don't start our fake Navigation if there's one on-going already.
Similarly if our fake Navigation gets interrupted by another. We wait
for on-going ones to finish and then start a new fake one if we're
supposed to be still pending.

There might be other routers on the page that might listen to intercept
Navigation Events. Typically you'd expect them not to trigger a refetch
when navigating to the same state. However, if they want to detect this
we provide the `"react-transition"` string in the `info` field for this
purpose.
2025-05-13 16:00:38 -04:00
Sebastian MarkbågeandGitHub ea05b750a5 Allow Passing Blob/File/MediaSource/MediaStream to src of <img>, <video> and <audio> (#32828)
Behind the `enableSrcObject` flag. This is revisiting a variant of what
was discussed in #11163.

Instead of supporting the [`srcObject`
property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject)
as a separate name, this adds an overload of `src` to allow objects to
be passed. The DOM needs to add separate properties for the object forms
since you read back but it doesn't make sense for React's write-only API
to do that. Similar to how we'll like add an overload for
`popoverTarget` instead of calling it `popoverTargetElement` and how
`style` accepts an object and it's not `styleObject={{...}}`.

There are a number of reason to revisit this.

- It's just way more convenient to have this built-in and it makes
conceptual sense. We typically support declarative APIs and polyfill
them when necessary.
- RSC supports Blobs and by having it built-in you don't need a Client
Component wrapper to render it where as doing it with effects would
require more complex wrappers. By picking Blobs over base64,
client-navigations can use the more optimized binary encoding in the RSC
protocol.
- The timing aspect of coordinating it with Suspensey images and image
decoding is a bit tricky to get right because if you set it in an effect
it's too late because you've already rendered it.
- SSR gets complicated when done in user space because you have to
handle both branches. Likely with `useSyncExternalStore`.
- By having it built-in we could optimize the payloads shared between
RSC payloads embedded in the HTML and data URLs.

This does not support objects for `<source src>` nor `<img srcset>`.
Those don't really have equivalents in the DOM neither. They're mainly
for picking an option when you don't know programmatically. However, for
this use case you're really better off picking a variant before
generating the blobs.

We may support Response objects in the future too as per
https://github.com/whatwg/fetch/issues/49
2025-04-08 12:11:41 -04:00
Sebastian MarkbågeandGitHub d4ac7689f9 Add Profiler mode to fixtures even if React DevTools is not installed (#31877)
Currently you need to do one of either:

1. Install React DevTools
2. Install React Refresh
3. Add Profiler component

To opt in to component level profiling.

It was a bit confusing that some of the fixtures was doing 2 which made
them work while other was depending on if you had DevTools.

Really React Refresh shouldn't really opt you in I think.
2024-12-28 02:01:49 -05:00
Sebastian MarkbågeandGitHub bdf187174d [Flight] Emit Deduped Server Components Marker (#31737)
Stacked on #31736.

<img width="1223" alt="Screenshot 2024-12-11 at 8 21 12 PM"
src="https://github.com/user-attachments/assets/a7cbc04b-c831-476b-aa2f-baddec9461c9"
/>

This emits a placeholder when we're deduping a component. This starts
when the parent's self time ends, where we would've started rendering
this component if it wasn't already started. The end time is when the
actual render ends since the parent is also blocked by it.
2024-12-16 13:16:53 -05:00
Sebastian MarkbågeandGitHub 6928bf2f7c [Flight] Log Server Component into Performance Track (#31729)
<img width="966" alt="Screenshot 2024-12-10 at 10 49 19 PM"
src="https://github.com/user-attachments/assets/27a21bdf-86b9-4203-893b-89523e698138">

This emits a tree view visualization of the timing information for each
Server Component provided in the RSC payload.

The unique thing about this visualization is that the end time of each
Server Component spans the end of the last child. Now what is
conceptually a blocking child is kind of undefined in RSC. E.g. if
you're not using a Promise on the client, or if it is wrapped in
Suspense, is it really blocking the parent?

Here I reconstruct parent-child relationship by which chunks reference
other chunks. A child can belong to more than one parent like when we
dedupe the result of a Server Component.

Then I wait until the whole RSC payload has streamed in, and then I
traverse the tree collecting the end time from children as I go and emit
the `performance.measure()` calls on the way up.

There's more work for this visualization in follow ups but this is the
basics. For example, since the Server Component time span includes async
work it's possible for siblings to execute their span in parallel (Foo
and Bar in the screenshot are parallel siblings). To deal with this we
need to spawn parallel work into separate tracks. Each one can be deep
due to large trees. This can makes this type of visualization unwieldy
when you have a lot of parallelism. Therefore I also plan another
flatter Timeline visualization in a follow up.
2024-12-12 14:03:18 -05:00
Sebastian MarkbågeandGitHub e483df4658 [Flight ESM] Wire up Source Maps in the flight-esm fixture (#30758)
Same as #29708 but for the flight-esm fixture.
2024-08-22 12:34:48 -04:00
Josh StoryandGitHub fa6eab5854 [Flight] Implement prerender (#30686)
Prerendering in flight is similar to prerendering in Fizz. Instead of
receiving a result (the stream) immediately a promise is returned which
resolves to the stream when the prerender is complete. The promise will
reject if the flight render fatally errors otherwise it will resolve
when the render is completed or is aborted.
2024-08-15 14:28:28 -07:00
Sebastian MarkbågeandGitHub 274c980c53 Warn for useFormState on initial render (#30292)
This was missed in the mount dev dispatcher. It was only in the rerender
dispatcher which means that it was only logged during the rerender.
Since DevTools can hide logs during rerenders, this hid the warning in
StrictMode.
2024-07-08 16:45:24 -04:00
Sebastian MarkbågeandGitHub cc1ec60d0d [Flight] Run recreated Errors within a fake native stack (#29717)
Stacked on #29740.

Before:

<img width="719" alt="Screenshot 2024-06-02 at 11 51 20 AM"
src="https://github.com/facebook/react/assets/63648/8f79fa82-2474-4583-894e-a2329e9a6304">

After (updated with my patches to Chrome):

<img width="813" alt="Screenshot 2024-06-06 at 5 16 20 PM"
src="https://github.com/facebook/react/assets/63648/bcc4f52f-e0ac-4708-ac2b-9629acdff705">

Sources panel after:

<img width="1188" alt="Screenshot 2024-06-06 at 5 14 21 PM"
src="https://github.com/facebook/react/assets/63648/2c673fac-d32d-42e4-8fac-bb63704e4b7f">

The fake eval file is now under "React" and the real file is now under
`file://`
2024-06-07 11:54:14 -04:00
Sebastian MarkbågeandGitHub ba099e442b [Flight] Add findSourceMapURL option to get a URL to load Server source maps from (#29708)
This lets you click a stack frame on the client and see the Server
source code inline.

<img width="871" alt="Screenshot 2024-06-01 at 11 44 24 PM"
src="https://github.com/facebook/react/assets/63648/581281ce-0dce-40c0-a084-4a6d53ba1682">

<img width="840" alt="Screenshot 2024-06-01 at 11 43 37 PM"
src="https://github.com/facebook/react/assets/63648/00dc77af-07c1-4389-9ae0-cf1f45199efb">

We could do some logic on the server that sends a source map url for
every stack frame in the RSC payload. That would make the client
potentially config free. However regardless we need the config to
describe what url scheme to use since that’s not built in to the bundler
config. In practice you likely have a common pattern for your source
maps so no need to send data over and over when we can just have a
simple function configured on the client.

The server must return a source map, even if the file is not actually
compiled since the fake file is still compiled.

The source mapping strategy can be one of two models depending on if the
server’s stack traces (`new Error().stack`) are source mapped back to
the original (`—enable-source-maps`) or represents the location in
compiled code (like in the browser).

If it represents the location in compiled code it’s actually easier. You
just serve the source map generated for that file by the tooling.

If it is already source mapped it has to generate a source map where
everything points to the same location (as if not compiled) ideally with
a segment per logical ast node.
2024-06-02 22:58:24 -04:00
Sebastian SilbermannandGitHub e63918d2cd Add Promise as a child test to Flight fixture (#28778) 2024-04-08 17:06:17 +02:00
Andrew ClarkandGitHub 44d40a077a Remove prefix from formState option (#27460)
`useFormState` is now in canary.
2023-10-04 15:17:37 -04:00
Andrew ClarkandGitHub bfefb22842 Upgrade Server Actions to canary (#27459)
Upgrades the stability of Server Actions from experimental to canary.

- Turns on enableAsyncActions and enableFormActions
- Removes "experimental_" prefix from useOptimistic, useFormStatus, and
useFormState
2023-10-04 14:51:36 -04:00
Andrew ClarkandGitHub 612b2b6601 useFormState: Reuse state from previous form submission (#27321)
If a Server Action is passed to useFormState, the action may be
submitted before it has hydrated. This will trigger a full page
(MPA-style) navigation. We can transfer the form state to the next page
by comparing the key path of the hook instance.

`ReactServerDOMServer.decodeFormState` is used by the server to extract
the form state from the submitted action. This value can then be passed
as an option when rendering the new page. It must be passed during both
SSR and hydration.

```js
const boundAction = await decodeAction(formData, serverManifest);
const result = await boundAction();
const formState = decodeFormState(result, formData, serverManifest);

// SSR
const response = createFromReadableStream(<App />);
const ssrStream = await renderToReadableStream(response, { formState })

// Hydration
hydrateRoot(container, <App />, { formState });
```

If the `formState` option is omitted, then the state won't be
transferred to the next page. However, it must be passed in both places,
or in neither; misconfiguring will result in a hydration mismatch.

(The `formState` option is currently prefixed with `experimental_`)
2023-09-13 18:30:40 -04:00
Josh StoryandGitHub b9be4537c2 [Flight] provide property descriptors for client references (#27328)
Client reference proxy should implement getOwnPropertyDescriptor. One
practical place where this shows up is when consuming CJS module.exports
in ESM modules. Node creates named exports it statically infers from the
underlying source but it only sets the named export if the CJS exports
hasOwnProperty. This trap will allow the proxy to respond affirmatively.

I did not add unit tests because contriving the ESM <-> CJS scenario in
Jest is challenging. I did add new components to the flight fixture
which demonstrate that the named exports are properly constructed with
the client reference whereas they were not before.
2023-09-05 13:45:16 -07:00
Sebastian MarkbågeandGitHub 5945e068ab [Flight] Instrument the Promise for Async Module instead of using a Module Cache (#26985)
Currently, since we use a module cache for async modules, it doesn't
automatically get updated when the module registry gets updated (HMR).

This technique ensures that if Webpack replaces the module (HMR) then
we'll get the new Promise when we require it again.

This technique doesn't work for ESM and probably not Vite since ESM will
provide a new Promise each time you call `import()` but in the
Webpack/CJS approach this Promise is an entry in the module cache and
not a promise for the entry.

I tried to replicate the original issue in the fixture but it's tricky
to replicate because 1) we can't really use async modules the same way
without compiling both server and client 2) even then I'm not quite sure
how to repro the HMR issue.
2023-06-28 14:30:09 -04:00
Sebastian MarkbågeandGitHub aef7ce5547 [Flight] Progressively Enhanced Server Actions (#26774)
This automatically exposes `$$FORM_ACTIONS` on Server References coming
from Flight. So that when they're used in a form action, we can encode
the ID for the server reference as a hidden field or as part of the name
of a button.

If the Server Action is a bound function it can have complex data
associated with it. In this case this additional data is encoded as
additional form fields.

To process a POST on the server there's now a `decodeAction` helper that
can take one of these progressive posts from FormData and give you a
function that is prebound with the correct closure and FormData so that
you can just invoke it.

I updated the fixture which now has a "Server State" that gets
automatically refreshed. This also lets us visualize form fields.
There's no "Action State" here for showing error messages that are not
thrown, that's still up to user space.
2023-05-03 18:36:57 -04:00
Andrew ClarkandGitHub f533cee8cb Add useFormStatus to Flight fixture (#26773) 2023-05-03 15:29:32 -04:00
Sebastian MarkbågeandGitHub 9c58a0b647 Update Flight fixture to use use() instead of Promise as a child (#26715)
The Promise as a child case seems buggy. It ends up throwing the Promise
as fatal when used in Sync rendering.
2023-04-24 14:00:25 -04:00
Sebastian MarkbågeandGitHub a21d1475ff [Flight] Fix File Upload in Node.js (#26700)
Use the Blob constructor + append with filename instead of File
constructor. Node.js doesn't expose a global File constructor but does
support it in this form.

Queue fields until we get the 'end' event from the previous file. We
rely on previous files being available by the time a field is resolved.
However, since the 'end' event in Readable is fired after two
micro-tasks, these are not resolved in order.

I use a queue of the fields while we're still waiting on files to
finish. This still doesn't resolve files and fields in order relative to
each other but that doesn't matter for our usage.
2023-04-22 01:04:24 -04:00
Andrew ClarkandGitHub 967d46c76c Add error boundary to Flight fixture (#26695)
Errors in form actions are now rethrown during render (#26689), so we
can handle them using an error boundary.
2023-04-21 14:23:50 -04:00
Sebastian MarkbågeandGitHub c826dc50de Add (Client) Functions as Form Actions (#26674)
This lets you pass a function to `<form action={...}>` or `<button
formAction={...}>` or `<input type="submit formAction={...}>`. This will
behave basically like a `javascript:` URL except not quite implemented
that way. This is a convenience for the `onSubmit={e => {
e.preventDefault(); const fromData = new FormData(e.target); ... }`
pattern.

You can still implement a custom `onSubmit` handler and if it calls
`preventDefault`, it won't invoke the action, just like it would if you
used a full page form navigation or javascript urls. It behaves just
like a navigation and we might implement it with the Navigation API in
the future.

Currently this is just a synchronous function but in a follow up this
will accept async functions, handle pending states and handle errors.

This is implemented by setting `javascript:` URLs, but these only exist
to trigger an error message if something goes wrong instead of
navigating away. Like if you called `stopPropagation` to prevent React
from handling it or if you called `form.submit()` instead of
`form.requestSubmit()` which by-passes the `submit` event. If CSP is
used to ban `javascript:` urls, those will trigger errors when these
URLs are invoked which would be a different error message but it's still
there to notify the user that something went wrong in the plumbing.

Next up is improving the SSR state with action replaying and progressive
enhancement.
2023-04-19 16:31:08 -04:00
d8089f2cf2 [Flight Reply] Encode FormData (#26663)
Builds on top of https://github.com/facebook/react/pull/26661

This lets you pass FormData objects through the Flight Reply
serialization. It does that by prefixing each entry with the ID of the
reference and then the decoding side creates a new FormData object
containing only those fields (without the prefix).

Ideally this should be more generic. E.g. you should be able to pass
Blobs, Streams and Typed Arrays by reference inside plain objects too.
You should also be able to send Blobs and FormData in the regular Flight
serialization too so that they can go both directions. They should be
symmetrical. We'll get around to adding more of those features in the
Flight protocol as we go.

---------

Co-authored-by: Sophie Alpert <git@sophiebits.com>
2023-04-18 14:57:33 -04:00
Andrew ClarkandGitHub 56a3c18e56 [Flight fixture] Remove redundant use (#26373)
Now that promises are renderable nodes, we can remove the `use` call
from the root of the Flight fixture.

Uncached promises will likely be accompanied by a warning when they are
rendered outside a transition. But this promise is the result of a
Flight response, so it's cached. And it's also a rendered as part of a
transition. So it's fine. Indeed, this is the canonical way to use this
feature.
2023-03-14 11:14:53 -04:00
ef8bdbecb6 [Flight Reply] Add Reply Encoding (#26360)
This adds `encodeReply` to the Flight Client and `decodeReply` to the
Flight Server.

Basically, it's a reverse Flight. It serializes values passed from the
client to the server. I call this a "Reply". The tradeoffs and
implementation details are a bit different so it requires its own
implementation but is basically a clone of the Flight Server/Client but
in reverse. Either through callServer or ServerContext.

The goal of this project is to provide the equivalent serialization as
passing props through RSC to client. Except React Elements and
Components and such. So that you can pass a value to the client and back
and it should have the same serialization constraints so when we add
features in one direction we should mostly add it in the other.

Browser support for streaming request bodies are currently very limited
in that only Chrome supports it. So this doesn't produce a
ReadableStream. Instead `encodeReply` produces either a JSON string or
FormData. It uses a JSON string if it's a simple enough payload. For
advanced features it uses FormData. This will also let the browser
stream things like File objects (even though they're not yet supported
since it follows the same rules as the other Flight).

On the server side, you can either consume this by blocking on
generating a FormData object or you can stream in the
`multipart/form-data`. Even if the client isn't streaming data, the
network does. On Node.js busboy seems to be the canonical library for
this, so I exposed a `decodeReplyFromBusboy` in the Node build. However,
if there's ever a web-standard way to stream form data, or if a library
wins in that space we can support it. We can also just build a multipart
parser that takes a ReadableStream built-in.

On the server, server references passed as arguments are loaded from
Node or Webpack just like the client or SSR does. This means that you
can create higher order functions on the client or server. This can be
tokenized when done from a server components but this is a security
implication as it might be tempting to think that these are not fungible
but you can swap one function for another on the client. So you have to
basically treat an incoming argument as insecure, even if it's a
function.

I'm not too happy with the naming parity:

Encode `server.renderToReadableStream` Decode: `client.createFromFetch`

Decode `client.encodeReply` Decode: `server.decodeReply`

This is mainly an implementation details of frameworks but it's annoying
nonetheless. This comes from that `renderToReadableStream` does do some
"rendering" by unwrapping server components etc. The `create` part comes
from the parity with Fizz/Fiber where you `render` on the server and
`create` a root on the client.

Open to bike-shedding this some more.

---------

Co-authored-by: Josh Story <josh.c.story@gmail.com>
2023-03-10 11:36:15 -05:00
f905da2276 [Flight] Send server reference error chunks to the client (#26293)
Previously when a called server reference function was rejected, the
emitted error chunk was not flushed, and the request was not properly
closed.

Co-authored-by: Sebastian Markbage <sebastian@calyptus.eu>
2023-03-04 22:56:19 -05:00
Sebastian MarkbågeandGitHub e0241b6600 Simplify Webpack References by encoding file path + export name as single id (#26300)
We always look up these references in a map so it doesn't matter what
their value is. It could be a hash for example.

The loaders now encode a single $$id instead of filepath + name.

This changes the react-client-manifest to have a single level. The value
inside the map is still split into module id + export name because
that's what gets looked up in webpack.

The react-ssr-manifest is still two levels because that's a reverse
lookup.
2023-03-04 19:51:34 -05:00
Sebastian MarkbågeandGitHub 67a61d5bd7 [Flight Fixture] Show SSR Support with CSS (#26263)
Builds on #26257.

To do this we need access to a manifest for which scripts and CSS are
used for each "page" (entrypoint).

The initial script to bootstrap the app is inserted with
`bootstrapScripts`. Subsequent content are loaded using the chunks
mechanism built-in.

The stylesheets for each pages are prepended to each RSC payload and
rendered using Float. This doesn't yet support styles imported in
components that are also SSR:ed nor imported through Server Components.
That's more complex and not implemented in the node loader.

HMR doesn't work after reloads right now because the SSR renderer isn't
hot reloaded because there's no idiomatic way to hot reload ESM modules
in Node.js yet. Without killing the HMR server. This leads to hydration
mismatches when reloading the page after a hot reload.

Notably this doesn't show serializing the stream through the HTML like
real implementations do. This will lead to possible hydration mismatches
based on the data. However, manually serializing the stream as a string
isn't exactly correct due to binary data. It's not the idiomatic way
this is supposed to work. This will all be built-in which will make this
automatic in the future.
2023-02-28 19:44:37 -05:00
Sebastian MarkbågeandGitHub 40755c01a6 [Flight Fixture] Proxy requests through the global server instead of directly (#26257)
This proxies requests through the global server instead of requesting
RSC responses from the regional server. This is a bit closer to
idiomatic, and closer to SSR.

This also wires up HMR using the Middleware technique instead of server.
This will be an important part of RSC compatibility because there will
be a `react-refresh` aspect to the integration.

This convention uses `Accept` header to branch a URL between HTML/RSC
but it could be anything really. Special headers, URLs etc. We might be
more opinionated about this in the future but now it's up to the router.

Some fixes for Node 16/17 support in the loader and fetch polyfill.
2023-02-28 19:24:16 -05:00
Sebastian MarkbågeandGitHub ef9f6e77b8 Enable passing Server References from Server to Client (#26124)
This is the first of a series of PRs, that let you pass functions, by
reference, to the client and back. E.g. through Server Context. It's
like client references but they're opaque on the client and resolved on
the server.

To do this, for security, you must opt-in to exposing these functions to
the client using the `"use server"` directive. The `"use client"`
directive lets you enter the client from the server. The `"use server"`
directive lets you enter the server from the client.

This works by tagging those functions as Server References. We could
potentially expand this to other non-serializable or stateful objects
too like classes.

This only implements server->server CJS imports and server->server ESM
imports. We really should add a loader to the webpack plug-in for
client->server imports too. I'll leave closures as an exercise for
integrators.

You can't "call" a client reference on the server, however, you can
"call" a server reference on the client. This invokes a callback on the
Flight client options called `callServer`. This lets a router implement
calling back to the server. Effectively creating an RPC. This is using
JSON for serializing those arguments but more utils coming from
client->server serialization.
2023-02-09 19:45:05 -05:00
Sebastian MarkbågeandGitHub f0cf832e1d Update Flight Fixture to "use client" instead of .client.js (#26118)
This updates the Flight fixture to support the new ESM loaders in newer
versions of Node.js.

It also uses native fetch since react-fetch is gone now. (This part
requires Node 18 to run the fixture.)

I also updated everything to use the `"use client"` convention instead
of file name based convention.

The biggest hack here is that the Webpack plugin now just writes every
`.js` file in the manifest. This needs to be more scoped. In practice,
this new convention effectively requires you to traverse the server
graph first to find the actual used files. This is enough to at least
run our own fixture though.

I didn't update the "blocks" fixture.

More details in each commit message.
2023-02-07 12:09:29 -05:00
Sebastian MarkbågeandGitHub e7c5af45ce Update cache() and use() to the canary aka next channel (#25502)
Testing what it would look like to move this to the `next` channel.
2022-10-23 23:20:52 -04:00
Sebastian MarkbågeandGitHub 3bb71dfd4b Rename react-server-dom-webpack entry points to /client and /server (#25504) 2022-10-18 10:15:52 -04:00
Sebastian MarkbågeandGitHub c5d06fdc5d [Flight] Fix Webpack Chunk Loading (#25271)
* Fix acorn import

I'm not sure how this ever worked.

* Fix cache to wait for entries already added to the chunk cache

* Modernize API
2022-09-14 22:57:35 -04:00
Sebastian MarkbågeandGitHub 975b644643 [Flight] response.readRoot() -> use(response) (#25267)
* [Flight] Move from suspensey readRoot() to use(thenable)

* Update noop tests

These are no longer sync so they need some more significant updating.

Some of these tests are written in a non-idiomatic form too which is not
great.

* Update Relay tests

I kept these as sync for now and just assume a sync Promise.

* Updated the main tests

* Gate tests

* We need to cast through any because Thenable doesn't support unknown strings
2022-09-14 20:20:33 -04:00
Sebastian MarkbågeandGitHub 5fd9db732d [Flight] Rename react-transport-... packages to react-server-... (#20403)
* Move files

* Update paths

* Rename import variables

* Rename /server to /writer

This is mainly because "React Server Server" is weird so we need another
dimension.

* Use "react-server" convention to enforce that writer is only loaded in a server
2020-12-08 08:08:57 -05:00
Sebastian MarkbågeandGitHub 30dfb86025 [Flight] Basic scan of the file system to find Client modules (#20383)
* Basic scan of the file system to find Client modules

This does a rudimentary merge of the plugins. It still uses the global
scan and writes to file system.

Now the plugin accepts a search path or a list of referenced client files.
In prod, the best practice is to provide a list of files that are actually
referenced rather than including everything possibly reachable. Probably
in dev too since it's faster.

This is using the same convention as the upstream ContextModule - which
powers the require.context helpers.

* Add neo-async to dependencies
2020-12-07 12:10:18 -05:00
Dan AbramovandGitHub 41c5d00fc9 [Flight] Minimal webpack plugin (#20228) 2020-12-03 21:21:19 +00:00