Stacked on #33403.
When a Promise is coming from React such as when it's passed from
another environment, we should forward the debug information from that
environment. We already do that when rendered as a child.
This makes it possible to also `await promise` and have the information
from that instrumented promise carry through to the next render.
This is a bit tricky because the current protocol is that we have to
read it from the Promise after it resolves so it has time to be assigned
to the promise. `async_hooks` doesn't pass us the instance (even though
it has it) when it gets resolved so we need to keep it around. However,
we have to be very careful because if we get this wrong it'll cause a
memory leak since we retain things by `asyncId` and then manually listen
for `destroy()` which can only be called once a Promise is GC:ed, which
it can't be if we retain it. We have to therefore use a `WeakRef` in
case it never resolves, and then read the `_debugInfo` when it resolves.
We could maybe install a setter or something instead but that's also
heavy.
The other issues is that we don't use native Promises in
ReactFlightClient so our instrumented promises aren't picked up by the
`async_hooks` implementation and so we never get a handle to our
thenable instance. To solve this we can create a native wrapper only in
DEV.
We want to change the defaults for `revealOrder` and `tail` on
SuspenseList. This is an intermediate step to allow experimental users
to upgrade.
To explicitly specify these options I added `revealOrder="independent"`
and `tail="visible"`.
I then added warnings if `undefined` or `null` is passed. You must now
always explicitly specify them. However, semantics are still preserved
for now until the next step.
We also want to change the rendering order of the `children` prop for
`revealOrder="backwards"`. As an intermediate step I first added
`revealOrder="unstable_legacy-backwards"` option. This will only be
temporary until all users can switch to the new `"backwards"` semantics
once we flip it in the next step.
I also clarified the types that the directional props requires iterable
children but not iterable inside of those. Rows with multiple items can
be modeled as explicit fragments.
Stacked on #33402.
There's a bug in Chrome Performance tracking which uses the enclosing
line/column instead of the callsite in stacks.
For our fake eval:ed functions that represents functions on the server,
we can position the enclosing function body at the position of the
callsite to simulate getting the right line.
Unfortunately, that doesn't give us exactly the right callsite when it's
used for other purposes that uses the callsite like console logs and
error reporting and stacks inside breakpoints. So I don't think we want
to always do this.
For ReactAsyncInfo/ReactIOInfo, the only thing we're going to use the
fake task for is the Performance tracking, so it doesn't have any
downsides until Chrome fixes the bug and we'd have to revert it.
Therefore this PR uses that techniques only for those entries.
We could do this for Server Components too but we're going to use those
for other things too like console logs. I don't think it's worth
duplicating the Task objects. That would also make it inconsistent with
Client Components.
For Client Components, we could in theory also generate fake evals but
that would be way slower since there's so many of them and currently we
rely on the native implementation for those. So doesn't seem worth
fixing.
But since we can at least fix it for RSC I/O/awaits we can do this hack.
Stacked on #33400.
<img width="1261" alt="Screenshot 2025-06-01 at 10 27 47 PM"
src="https://github.com/user-attachments/assets/a5a73ee2-49e0-4851-84ac-e0df6032efb5"
/>
This is emitted with the start/end time and stack of the "await". Which
may be different than the thing that started the I/O.
These awaits aren't quite as simple as just every await since you can
start a sequence in parallel there can actually be multiple overlapping
awaits and there can be CPU work interleaved with the await on the same
component.
```js
function getData() {
await fetch(...);
await fetch(...);
}
const promise = getData();
doWork();
await promise;
```
This has two "I/O" awaits but those are actually happening in parallel
with `doWork()`.
Since these also could have started before we started rendering this
sequence (e.g. a component) we have to clamp it so that we don't
consider awaits that start before the component.
What we're conceptually trying to convey is the time this component was
blocked due to that I/O resource. Whether it's blocked from completing
the last result or if it's blocked from issuing a waterfall request.
Stacked on #33395.
This lets us keep track of which environment this was fetched and
awaited.
Currently the IO and await is in the same environment. It's just kept
when forwarded. Once we support forwarding information from a Promise
fetched from another environment and awaited in this environment then
the await can end up being in a different environment.
There's a question of when the await is inside Flight itself such as
when you return a promise fetched from another environment whether that
should mean that the await is in the current environment. I don't think
so since the original stack trace is the best stack trace. It's only if
you `await` it in user space in this environment first that this might
happen and even then it should only be considered if there wasn't a
better await earlier or if reading from the other environment was itself
I/O.
The timing of *when* we read `environmentName()` is a little interesting
here too.
Stacked on #33394.
This lets us create async stack traces to the owner that was in context
when the I/O was started or awaited.
<img width="615" alt="Screenshot 2025-06-01 at 12 31 52 AM"
src="https://github.com/user-attachments/assets/6ff5a146-33d6-4a4b-84af-1b57e73047d4"
/>
This owner might not be the immediate closest parent where the I/O was
awaited.
Stacked on #33392.
This adds another track to the Performance Track called `"Server
Requests"`.
<img width="1015" alt="Screenshot 2025-06-01 at 12 02 14 AM"
src="https://github.com/user-attachments/assets/c4d164c4-cfdf-4e14-9a87-3f011f65fd20"
/>
This logs the flat list of I/O awaited on by Server Components. There
will be other views that are more focused on what data blocks a specific
Component or Suspense boundary but this is just the list of all the I/O
basically so you can get an overview of those waterfalls without the
noise of all the Component trees and rendering. It's similar to what the
"Network" track is on the client.
I've been going back and forth on what to call this track but I went
with `"Server Requests"` for now. The idea is that the name should
communicate that this is something that happens on the server and is a
pairing with the `"Server Components"` track. Although we don't use that
feature, since it's missing granularity, it's also similar to "Server
Timings".
Stacked on #33390.
The stack trace doesn't include the thing you called when calling into
ignore listed content. We consider the ignore listed content
conceptually the abstraction that you called that's interesting.
This extracts the name of the first ignore listed function that was
called from user space. For example `"fetch"`. So we can know what kind
of request this is.
This could be enhanced and tweaked with heuristics in the future. For
example, when you create a Promise yourself and call I/O inside of it
like my `delay` examples, then we use that Promise as the I/O node but
its stack doesn't have the actual I/O performed. It might be better to
use the inner I/O node in that case. E.g. `setTimeout`. Currently I pick
the name from the first party code instead - in my example `delay`.
Another case that could be improved is the case where your whole
component is third-party. In that case we still log the I/O but it has
no context about what kind of I/O since the whole stack is ignored it
just gets the component name for example. We could for example look at
the first name that is in a different package than the package name of
the ignored listed component. So if
`node_modules/my-component-library/index.js` calls into
`node_modules/mysql/connection.js` then we could use the name from the
inner.
Stacked on #33388.
This encodes the I/O entries as their own row type (`"J"`). This makes
it possible to parse them directly without first parsing the debug info
for each component. E.g. if you're just interested in logging the I/O
without all the places it was awaited.
This is not strictly necessary since the debug info is also readily
available without parsing the actual trees. (That's how the Server
Components Performance Track works.) However, we might want to exclude
this information in profiling builds while retaining some limited form
of I/O tracking.
It also allows for logging side-effects that are not awaited if we
wanted to.
This lets us track what data each Server Component depended on. This
will be used by Performance Track and React DevTools.
We use Node.js `async_hooks`. This has a number of downside. It is
Node.js specific so this feature is not available in other runtimes
until something equivalent becomes available. It's [discouraged by
Node.js docs](https://nodejs.org/api/async_hooks.html#async-hooks). It's
also slow which makes this approach only really viable in development
mode. At least with stack traces. However, it's really the only solution
that gives us the data that we need.
The [Diagnostic
Channel](https://nodejs.org/api/diagnostics_channel.html) API is not
sufficient. Not only is many Node.js built-in APIs missing but all
libraries like databases are also missing. Were as `async_hooks` covers
pretty much anything async in the Node.js ecosystem.
However, even if coverage was wider it's not actually showing the
information we want. It's not enough to show the low level I/O that is
happening because that doesn't provide the context. We need the stack
trace in user space code where it was initiated and where it was
awaited. It's also not each low level socket operation that we want to
surface but some higher level concept which can span a sequence of I/O
operations but as far as user space is concerned.
Therefore this solution is anchored on stack traces and ignore listing
to determine what the interesting span is. It is somewhat
Promise-centric (and in particular async/await) because it allows us to
model an abstract span instead of just random I/O. Async/await points
are also especially useful because this allows Async Stacks to show the
full sequence which is not supported by random callbacks. However, if no
Promises are involved we still to our best to show the stack causing
plain I/O callbacks.
Additionally, we don't want to track all possible I/O. For example,
side-effects like logging that doesn't affect the rendering performance
doesn't need to be included. We only want to include things that
actually block the rendering output. We also need to track which data
blocks each component so that we can track which data caused a
particular subtree to suspend.
We can do this using `async_hooks` because we can track the graph of
what resolved what and then spawned what.
To track what suspended what, something has to resolve. Therefore it
needs to run to completion before we can show what it was suspended on.
So something that never resolves, won't be tracked for example.
We use the `async_hooks` in `ReactFlightServerConfigDebugNode` to build
up an `ReactFlightAsyncSequence` graph that collects the stack traces
for basically all I/O and Promises allocated in the whole app. This is
pretty heavy, especially the stack traces, but it's because we don't
know which ones we'll need until they resolve. We don't materialize the
stacks until we need them though.
Once they end up pinging the Flight runtime, we collect which current
executing task that pinged the runtime and then log the sequence that
led up until that runtime into the RSC protocol. Currently we only
include things that weren't already resolved before we started rendering
this task/component, so that we don't log the entire history each time.
Each operation is split into two parts. First a `ReactIOInfo` which
represents an I/O operation and its start/end time. Basically the start
point where it was start. This is basically represents where you called
`new Promise()` or when entering an `async function` which has an
implied Promise. It can be started in a different component than where
it's awaited and it can be awaited in multiple places. Therefore this is
global information and not associated with a specific Component.
The second part is `ReactAsyncInfo`. This represents where this I/O was
`await`:ed or `.then()` called. This is associated with a point in the
tree (usually the Promise that's a direct child of a Component). Since
you can have multiple different I/O awaited in a sequence technically it
forms a dependency graph but to simplify the model these awaits as
flattened into the `ReactDebugInfo` list. Basically it contains each
await in a sequence that affected this part from unblocking.
This means that the same `ReactAsyncInfo` can appear in mutliple
components if they all await the same `ReactIOInfo` but the same Promise
only appears once.
Promises that are only resolved by other Promises or immediately are not
considered here. Only if they're resolved by an I/O operation. We pick
the Promise basically on the border between user space code and ignored
listed code (`node_modules`) to pick the most specific span but abstract
enough to not give too much detail irrelevant to the current audience.
Similarly, the deepest `await` in user space is marked as the relevant
`await` point.
This feature is only available in the `node` builds of React. Not if you
use the `edge` builds inside of Node.js.
---------
Co-authored-by: Sebastian "Sebbie" Silbermann <silbermann.sebastian@gmail.com>
Alternative to #33421. The difference is that this also adds an
underscore between the "R" and the ID.
The reason we wanted to use special characters is because we use the
full spectrum of A-Z 0-9 in our ID generation so we can basically
collide with any common word (or anyone using a similar algorithm,
base64 or even base16). It's a little less likely that someone would put
`_R_` specifically unless you generate like two IDs separated by
underscore.

## Summary
This tool leverages DevTools to get the component tree from the
currently open React App. This gives realtime information to agents
about the state of the app.
## How did you test this change?
Tested integration with Claude Desktop
This is a babel bug + edge case.
Babel compact mode produces invalid JavaScript (i.e. parse error) when
given a `NumericLiteral` with a negative value.
See https://codesandbox.io/p/devbox/5d47fr for repro.
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=development --persistent, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=development --persistent, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=development --persistent, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=development --persistent, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=development --persistent, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=development, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=development, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=development, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=development, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=development, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=production, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=production, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=production, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=production, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=experimental --env=production, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=stable --env=development --persistent, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=stable --env=development --persistent, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=stable --env=development --persistent, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=stable --env=development --persistent, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=stable --env=development --persistent, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=development --variant=false, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=development --variant=false, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=development --variant=false, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=development --variant=false, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=development --variant=false, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=development --variant=true, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=development --variant=true, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=development --variant=true, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=development --variant=true, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=development --variant=true, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=production --variant=false, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=production --variant=false, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=production --variant=false, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=production --variant=false, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=production --variant=false, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=production --variant=true, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=production --variant=true, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=production --variant=true, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=production --variant=true, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-classic --env=production --variant=true, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=development --variant=false, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=development --variant=false, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=development --variant=false, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=development --variant=false, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=development --variant=false, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=development --variant=true, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=development --variant=true, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=development --variant=true, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=development --variant=true, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=development --variant=true, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=production --variant=false, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=production --variant=false, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=production --variant=false, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=production --variant=false, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=production --variant=false, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=production --variant=true, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=production --variant=true, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=production --variant=true, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=production --variant=true, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=www-modern --env=production --variant=true, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=development --variant=false, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=development --variant=false, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=development --variant=false, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=development --variant=false, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=development --variant=false, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=development --variant=true, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=development --variant=true, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=development --variant=true, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=development --variant=true, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=development --variant=true, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=production --variant=false, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=production --variant=false, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=production --variant=false, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=production --variant=false, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=production --variant=false, 5/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=production --variant=true, 1/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=production --variant=true, 2/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=production --variant=true, 3/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=production --variant=true, 4/5) (push) Has been cancelled
(Runtime) Build and Test / yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) (-r=xplat --env=production --variant=true, 5/5) (push) Has been cancelled
## Summary
While investigating the root cause of #33208, I noticed a clear typo for
one of the validation files.
## How did you test this change?
Inside `/react/compiler/packages/babel-plugin-react-compiler` I ran the
test script successfully:
<img width="415" alt="Screenshot at May 22 16-43-06"
src="https://github.com/user-attachments/assets/3fe8c5e1-37ce-4a31-b35e-7e323e57cd9d"
/>
## Summary
We completed testing on these internally, so can cleanup the separate
fast and slow paths and remove the `enableShallowPropDiffing` flag which
we're not pursuing.
## How did you test this change?
```
yarn test ReactNativeAttributePayloadFabric
```
fixes https://github.com/facebook/react/issues/32449
This is my first time touching this code. There are multiple systems in
place here and I wouldn't be surprised to learn that this has to be
handled in some other areas too. I have found some other style-related
code areas but I had no time yet to double-check them.
cc @gnoff
This is the same technique we do for the client except we don't check
whether this is newly created font loading to keep code small.
Unfortunately, we can't use this technique for Suspensey images. They'll
need to block before we call `startViewTransition` in a separate
refactor. This is due to a bug in Chrome where `img.decode()` doesn't
resolve until `startViewTransition` does.
There seems to be some bugs still to work out in Chrome. See #33187.
Additionally, since you can't really rely on this function existing
across browsers, it's hard to depend on its behavior anyway. In fact,
you now have a source of inconsistent behaviors across browsers to deal
with.
Ideally it would also be more widely spread in fake DOM implementations
like JSDOM so that we can use it unconditionally. #33177.
We still want to enable this since it's a great feature but maybe not
until it's more widely available cross-browsers with fewer bugs.
Summary:
To prepare for automatic effect dependencies, some codebases may want to
codemod
existing useEffect calls with no deps to include an explicit undefined
second argument
in order to preserve the "run on every render" behavior. In sufficiently
large codebases,
this may require a temporary enforcement period where all effects
provide an explicit
dependencies argument.
Outside of migration, relying on a component to render can lead to real
bugs,
especially when working with memoization.
## Summary
This PR fixes a likely incorrect condition in the
`scheduleUpdateOnFiber` function inside `ReactFiberWorkLoop.js`.
Previously, the code checked:
```js
(executionContext & RenderContext) !== NoLanes
````
However, `NoLanes` is part of the lane priority system, not the
execution context flags. The intent here seems to be to detect whether
the current execution context includes `RenderContext`, which should be
compared against `NoContext`, not `NoLanes`.
This fix replaces `NoLanes` with `NoContext` for semantic correctness
and consistency with other checks throughout the codebase.
**Fixes
[[#33169](https://github.com/facebook/react/issues/33169)](https://github.com/facebook/react/issues/33169)**
---
## How did you test this change?
I ran the following commands to validate correctness and ensure nothing
was broken:
* `yarn lint`
* `yarn linc`
* `yarn test`
* `yarn test --prod`
* `yarn flow`
* `yarn prettier`
All checks passed. Since this is a minor internal logic fix and doesn't
change public behavior or APIs, no additional tests are necessary at
this time.
## Summary
I am writing code that isn't so good, so I saw this error message many
times. It appears to have a typo. This PR fixes the typo.
## How did you test this change?
Ran the tests
Inferred effect dependencies now include optional chains.
This is a temporary solution while
https://github.com/facebook/react/pull/32099 and its followups are
worked on. Ideally, we should model reactive scope dependencies in the
IR similarly to `ComputeIR` -- dependencies should be hoisted and all
references rewritten to use the hoisted dependencies.
`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33326).
* __->__ #33326
* #33325
* #32286
Stacked on #33330.
This walks the element tree to activate the various classes under
different scenarios. There are some edge case things that are a little
different since we can't express every scenario without virtual nodes.
The main thing that's still missing though is avoiding animating updates
if it can be contained to a layout or enter/exit/share if they're out of
the viewport. I.e. layout stuff.
Follow up to #33293.
This solves a race condition when boundaries are added to the batch
after the `startViewTransition` call.
This doesn't matter yet but it will once we start assigning names before
the `startViewTransition` call.
A possible alternative solution might be to ensure the names are added
synchronously in the event that adds to the batch. It's possible to keep
adding to a batch until the snapshot has happened.
I believe that these mean the same thing. We don't have to emit the
attribute if it's `none` for these cases because if there is no matching
scenario we won't apply the animation in this case.
The only case where we have to emit `none` in the attribute is for
`vt-update` because those can block updates from propagating upwards.
Follow up to #33321.
We can mark boundaries that were blocked in the prerender as postponed
but without anything to replayed inside them. That way they're not
emitted in the prerender but is unblocked when replayed.
Technically this does some unnecessary replaying of the path to the
otherwise already completed boundary but it simplifies our model by just
marking the boundary as needing replaying.
This uses the richer `serverAct` helper that we already use in other
tests.
This avoids using the `Scheduler`. We don't use that package on the
server so it doesn't make sense to simulate going through it.
Additionally, we really should be getting rid of it on the client too to
favor `postTask` polyfills.
When a new child of a fragment instance is inserted, we need to notify
the instance to keep any relevant tracking up to date. For example, we
automatically observe the new child with any active
IntersectionObserver.
For mutable renderers (DOM), we reuse the existing traversal in
`commitPlacement` that does the insertions for HostComponents. Immutable
renderers (Fabric) exit this path before the traversal though, so
currently we can't notify the fragment instances.
Here I've created a separate traversal in `commitPlacement`,
specifically for immutable renders when `enableFragmentRefs` is on.
There's an interesting case when a SuspenseList is partially prerendered
but some of the completed boundaries are blocked by rows to be resumed.
This handles it but just unblocking the future rows to avoid stalling.
However, the correct semantics will need special handling in the
postponed state.
We have many cases internally where the `containerInstance` resolves to
a comment node. `restoreRootViewTransitionName` is called when
`enableViewTransition` is on, even without introducing a
`<ViewTransition />`. So that means it can crash pages because
`containerInstance.style` is `undefined` just by turning on the flag.
This skips cancel/restore of root view transition name if a comment node is the root.
I missed setting the `keyPath` because the `renderChildrenArray` that
this is forked from doesn't need to set a path but since this is
rendered from the `SuspenseList` element it needs it.
Stacked on #33311.
When a row contains Suspense boundaries that themselves depend on CSS,
they will not resolve until the CSS has loaded on the client. We need
future rows in a list to be blocked until this happens. We could do
something in the runtime but a simpler approach is to just add those CSS
dependencies to all those boundaries as well.
To do this, we first hoist the HoistableState from a completed boundary
onto its parent row. Then when the row finishes do we hoist it onto the
next row and onto any boundaries within that row.
Stacked on #33308.
For "together" mode, we can be a self-blocking row that adds all its
boundaries to the blocked set, but there's no parent row that unblocks
it.
A particular quirk of this mode is that it's not enough to just unblock
them all on the server together. Because if one boundary downloads all
its html and then issues a complete instruction it'll appear before the
others while streaming in. What we actually want is to reveal them all
in a single batch.
This implementation takes a short cut by unblocking the rows in
`flushPartialBoundary`. That ensures that all the segments of every
boundary has a chance to flush before we start emitting any of the
complete boundary instructions. Once the last one unblocks, all the
complete boundary instructions are queued. Ideally this would be a
single `<script>` tag so that they can't be split up even if we get a
chunk containing some of them.
~A downside of this approach is that we always outline these boundaries.
We could inline them if they all complete before the parent flushes.
E.g. by checking if the row is blocked only by its own boundaries and if
all the boundaries would fit without getting outlined, then we can
inline them all at once.~ I went ahead and did this because it solves an
issue with `renderToString` where it doesn't support the script runtime
so it can only handle this if inlined.
Follow up to #33306.
If we're nested inside a SuspenseList and we have a row, then we can
point our last row to block the parent row and unblock the parent when
the last child unblocks.
We support AsyncIterable (more so when it's a cached form like in coming
from Flight) as children.
This fixes some warnings and bugs when passed to SuspenseList.
Ideally SuspenseList with `tail="hidden"` should support unblocking
before the full result has resolved but that's an optimization on top.
We also might want to change semantics for this for
`revealOrder="backwards"` so it becomes possible to stream items in
reverse order.
Basically we track a `SuspenseListRow` on the task. These keep track of
"pending tasks" that block the row. A row is blocked by:
- First itself completing rendering.
- A previous row completing.
- Any tasks inside the row and before the Suspense boundary inside the
row. This is mainly because we don't yet know if we'll discover more
SuspenseBoundaries.
- Previous row's SuspenseBoundaries completing.
If a boundary might get outlined, then we can't consider it completed
until we have written it because it determined whether other future
boundaries in the row can finish.
This is just handling basic semantics. Features not supported yet that
need follow ups later:
- CSS dependencies of previous rows should be added as dependencies of
future row's suspense boundary. Because otherwise if the client is
blocked on CSS then a previous row could be blocked but the server
doesn't know it.
- I need a second pass on nested SuspenseList semantics.
- `revealOrder="together"`
- `tail="hidden"`/`tail="collapsed"`. This needs some new runtime
semantics to the Fizz runtime and to allow the hydration to handle
missing rows in the HTML. This should also be future compatible with
AsyncIterable where we don't know how many rows upfront.
- Need to double check resuming semantics.
---------
Co-authored-by: Sebastian "Sebbie" Silbermann <silbermann.sebastian@gmail.com>