Commit Graph
210 Commits
Author SHA1 Message Date
Josh StoryandGitHub 8bda71558c [Fiber] support hydration when rendering Suspense anywhere (#32224)
follow up to https://github.com/facebook/react/pull/32163

This continues the work of making Suspense workable anywhere in a
react-dom tree. See the prior PRs for how we handle server rendering and
client rendering. In this change we update the hydration implementation
to be able to locate expected nodes. In particular this means hydration
understands now that the default hydration context is the document body
when the container is above the body.

One case that is unique to hydration is clearing Suspense boundaries.
When hydration fails or when the server instructs the client to recover
an errored boundary it's possible that the html, head, and body tags in
the initial document were written from a fallback or a different primary
content on the server and need to be replaced by the client render.
However these tags (and in the case of head, their content) won't be
inside the comment nodes that identify the bounds of the Suspense
boundary. And when client rendering you may not even render the same
singletons that were server rendered. So when server rendering a
boudnary which contributes to the preamble (the html, head, and body tag
openings plus the head contents) we emit a special marker comment just
before closing the boundary out. This marker encodes which parts of the
preamble this boundary owned. If we need to clear the suspense boundary
on the client we read this marker and use it to reset the appropriate
singleton state.
2025-02-04 12:30:30 -08:00
Josh StoryandGitHub b25bcd460f [Fizz] Support Suspense boundaries anywhere (#32069)
Suspense is meant to be composable but there has been a lonstanding
limitation with using Suspense above the `<body>` tag of an HTML
document due to peculiarities of how HTML is parsed. For instance if you
used Suspense to render an entire HTML document and had a fallback that
might flush an alternate Document the comment nodes which describe this
boundary scope won't be where they need to be in the DOM for client
React to properly hydrate them. This is somewhat a problem of our own
making in that we have a concept of a Preamble and we leave the closing
body and html tags behind until streaming has completed which produces a
valid HTML document that also matches the DOM structure that would be
parsed from it. However Preambles as a concept are too important to
features like Float to imagine moving away from this model and so we can
either choose to just accept that you cannot use Suspense anywhere
except inside the `<body>` or we can build special support for Suspense
into react-dom that has a coherent semantic with how HTML documents are
written and parsed.

This change implements Suspense support for react-dom/server by
correctly serializing boundaries during rendering, prerendering, and
resumgin on the server. It does not yet support Suspense everywhere on
the client but this will arrive in a subsequent change. In practice
Suspense cannot be used above the `<body>` tag today so this is not a
breaking change since no programs in the wild could be using this
feature anyway.

React's streaming rendering of HTML doesn't lend itself to replacing the
contents of the documentElement, head, or body of a Document. These are
already special cased in fiber as HostSingletons and similarly for Fizz
the values we render for these tags must never be updated by the Fizz
runtime once written. To accomplish these we redefine the Preamble as
the tags that represent these three singletons plus the contents of the
document.head. If you use Suspense above any part of the Preamble then
nothing will be written to the destination until the boundary is no
longer pending. If the boundary completes then the preamble from within
that boudnary will be output. If the boundary postpones or errors then
the preamble from the fallback will be used instead.

Additionally, by default anything that is not part of the preamble is
implicitly in body scope. This leads to the somewhat counterintuitive
consequence that the comment nodes we use to mark the borders of a
Suspense boundary in Fizz can appear INSIDE the preamble that was
rendered within it.

```typescript
render((
  <Suspense>
    <html lang="en">
      <body>
        <div>hello world</div>
      </body>
    </html>
  </Suspense>
))
```
will produce an HTML document like this
```html
<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
    <!--$--> <-- this is the comment Node representing the outermost Suspense
    <div>hello world</div>
    <$--/$-->
  </body>
</html>
```

Later when I update Fiber to support Suspense anywhere hydration will
similarly start implicitly in the document body when the root is part of
the preamble (the document or one of it's singletons).
2025-01-17 10:54:11 -08:00
a4d122f2d1 Add <ViewTransition> Component (#31975)
This will provide the opt-in for using [View
Transitions](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API)
in React.

View Transitions only trigger for async updates like `startTransition`,
`useDeferredValue`, Actions or `<Suspense>` revealing from fallback to
content. Synchronous updates provide an opt-out but also guarantee that
they commit immediately which View Transitions can't.

There's no need to opt-in to View Transitions at the "cause" side like
event handlers or actions. They don't know what UI will change and
whether that has an animated transition described.

Conceptually the `<ViewTransition>` component is like a DOM fragment
that transitions its children in its own isolate/snapshot. The API works
by wrapping a DOM node or inner component:

```js
import {ViewTransition} from 'react';

<ViewTransition><Component /></ViewTransition>
```

The default is `name="auto"` which will automatically assign a
`view-transition-name` to the inner DOM node. That way you can add a
View Transition to a Component without controlling its DOM nodes styling
otherwise.

A difference between this and the browser's built-in
`view-transition-name: auto` is that switching the DOM nodes within the
`<ViewTransition>` component preserves the same name so this example
cross-fades between the DOM nodes instead of causing an exit and enter:

```js
<ViewTransition>{condition ? <ComponentA /> : <ComponentB />}</ViewTransition>
```

This becomes especially useful with `<Suspense>` as this example
cross-fades between Skeleton and Content:

```js
<ViewTransition>
  <Suspense fallback={<Skeleton />}>
    <Content />
  </Suspense>
</ViewTransition>
```

Where as this example triggers an exit of the Skeleton and an enter of
the Content:

```js
<Suspense fallback={<ViewTransition><Skeleton /></ViewTransition>}>
  <ViewTransition><Content /></ViewTransition>
</Suspense>
```

Managing instances and keys becomes extra important.

You can also specify an explicit `name` property for example for
animating the same conceptual item from one page onto another. However,
best practices is to property namespace these since they can easily
collide. It's also useful to add an `id` to it if available.

```js
<ViewTransition name="my-shared-view">
```

The model in general is the same as plain `view-transition-name` except
React manages a set of heuristics for when to apply it. A problem with
the naive View Transitions model is that it overly opts in every
boundary that *might* transition into transitioning. This is leads to
unfortunate effects like things floating around when unrelated updates
happen. This leads the whole document to animate which means that
nothing is clickable in the meantime. It makes it not useful for smaller
and more local transitions. Best practice is to add
`view-transition-name` only right before you're about to need to animate
the thing. This is tricky to manage globally on complex apps and is not
compositional. Instead we let React manage when a `<ViewTransition>`
"activates" and add/remove the `view-transition-name`. This is also when
React calls `startViewTransition` behind the scenes while it mutates the
DOM.

I've come up with a number of heuristics that I think will make a lot
easier to coordinate this. The principle is that only if something that
updates that particular boundary do we activate it. I hope that one day
maybe browsers will have something like these built-in and we can remove
our implementation.

A `<ViewTransition>` only activates if:

- If a mounted Component renders a `<ViewTransition>` within it outside
the first DOM node, and it is within the viewport, then that
ViewTransition activates as an "enter" animation. This avoids inner
"enter" animations trigger when the parent mounts.
- If an unmounted Component had a `<ViewTransition>` within it outside
the first DOM node, and it was within the viewport, then that
ViewTransition activates as an "exit" animation. This avoids inner
"exit" animations triggering when the parent unmounts.
- If an explicitly named `<ViewTransition name="...">` is deep within an
unmounted tree and one with the same name appears in a mounted tree at
the same time, then both are activated as a pair, but only if they're
both in the viewport. This avoids these triggering "enter" or "exit"
animations when going between parents that don't have a pair.
- If an already mounted `<ViewTransition>` is visible and a DOM
mutation, that might affect how it's painted, happens within its
children but outside any nested `<ViewTransition>`. This allows it to
"cross-fade" between its updates.
- If an already mounted `<ViewTransition>` resizes or moves as the
result of direct DOM nodes siblings changing or moving around. This
allows insertion, deletion and reorders into a list to animate all
children. It is only within one DOM node though, to avoid unrelated
changes in the parent to trigger this. If an item is outside the
viewport before and after, then it's skipped to avoid things flying
across the screen.
- If a `<ViewTransition>` boundary changes size, due to a DOM mutation
within it, then the parent activates (or the root document if there are
no more parents). This ensures that the container can cross-fade to
avoid abrupt relayout. This can be avoided by using absolutely
positioned children. When this can avoid bubbling to the root document,
whatever is not animating is still responsive to clicks during the
transition.

Conceptually each DOM node has its own default that activates the parent
`<ViewTransition>` or no transition if the parent is the root. That
means that if you add a DOM node like `<div><ViewTransition><Component
/></ViewTransition></div>` this won't trigger an "enter" animation since
it was the div that was added, not the ViewTransition. Instead, it might
cause a cross-fade of the parent ViewTransition or no transition if it
had no parent. This ensures that only explicit boundaries perform coarse
animations instead of every single node which is really the benefit of
the View Transitions model. This ends up working out well for simple
cases like switching between two pages immediately while transitioning
one floating item that appears on both pages. Because only the floating
item transitions by default.

Note that it's possible to add manual `view-transition-name` with CSS or
`style={{ viewTransitionName: 'auto' }}` that always transitions as long
as something else has a `<ViewTransition>` that activates. For example a
`<ViewTransition>` can wrap a whole page for a cross-fade but inside of
it an explicit name can be added to something to ensure it animates as a
move when something relates else changes its layout. Instead of just
cross-fading it along with the Page which would be the default.

There's more PRs coming with some optimizations, fixes and expanded
APIs. This first PR explores the above core heuristic.

---------

Co-authored-by: Sebastian "Sebbie" Silbermann <silbermann.sebastian@gmail.com>
2025-01-08 12:11:18 -05:00
RickyandGitHub e06c72fcf4 [flags] Cleanup enableCache (#31778)
This is landed everywhere
2024-12-15 12:34:08 -05:00
RickyandGitHub 2d320563f3 [flags] Delete enableDebugTracing (#31780)
This is unused, even in the one builds that uses it, and we don't plan
on landing it in this form.
2024-12-15 12:16:10 -05:00
RickyandGitHub 9e2c233139 [flags] Delete enableSuspenseAvoidThisFallbackFizz (#31779)
We're not shipping `enableSuspenseAvoidThisFallback` and the fizz flag
is already off so we can delete it.
2024-12-14 13:05:17 -05:00
Jan KassensandGitHub e1378902bb [string-refs] cleanup string ref code (#31443) 2024-11-06 14:00:10 -05:00
Jan KassensandGitHub 07aa494432 Remove enableRefAsProp feature flag (#30346)
The flag is fully rolled out.
2024-11-04 14:30:58 -05:00
Josh StoryandGitHub 67fee58b1f [Fizz] Start initial work immediately (#31079)
In a recent update we make Flight start working immediately rather than
waitin for a new task. This commit updates fizz to have similar
mechanics. We start the render in the currently running task but we do
so in a microtask to avoid reentrancy. This aligns Fizz with Flight.

ref: https://github.com/facebook/react/pull/30961
2024-09-26 13:51:45 -07:00
Josh StoryandGitHub ae75d5a3f5 [Fizz] Include componentStack at the root when aborting (#31011)
When aborting we currently don't produce a componentStack when aborting
the shell. This is likely just an oversight and this change updates this
behavior to be consistent with what we do when there is a boundary
2024-09-20 10:00:02 -07:00
Sebastian MarkbågeandGitHub 473522093d [Fizz] Add resumeAndPrerender to Static Rendering (#30950)
This is only in the same experimental exports as `resume`. Useful with
Postpone/Halt.

We already have `prerender()` to create a partial tree with postponed
state. We also have `resume()` to dynamically resume such a tree.

This lets you do a new prerender by resuming an already existing
postponed state. Basically creating a chain of preludes. The next
prelude would include the scripts to patch up the document.

This mostly just works since both prerender and resume are already
implemented using the same code so we just enable both at the root. I'm
sure we'll find some edge cases since this wasn't considered when it was
first written but so far I've only found an unrelated existing bug with
`keyPath` fixed here.
2024-09-12 10:51:01 -04:00
Sebastian MarkbågeandGitHub 1bb056363c [Fizz] Use RequestInstance constructor for resuming (#30947)
We added enough fields to need a constructor instead of inline object in
V8.

We didn't update the resumeRequest path though so it wasn't using the
constructor and had a different hidden class.
2024-09-11 11:35:01 -04:00
Sebastian MarkbågeandGitHub 96aca5f4f3 Spawn new task if we hit stack overflow (#30419)
If we see the "Maximum call stack size exceeded" error we know we've hit
stack overflow. We can recover from this by spawning a new task and
trying again. Effectively a zero-cost trampoline in the normal case. The
new task will have a clean stack. If you have a lot of siblings at the
same depth that hits the limit you can end up hitting this once for each
sibling but within that new sibling you're unlikely to hit this again.
So it's not too expensive.

If it errors again in the retryTask pass, the other error handling takes
over which causes this to be able to still not infinitely stall. E.g.
when the component itself throws an error like this.

It's still better to increase the stack limit for performance if you
have a really deep tree but it doesn't really hurt to be able to recover
since it's zero cost when it doesn't happen.

We could do the same thing for Flight. Those trees don't tend to be as
deep but could happen.
2024-08-27 13:10:37 -04:00
Josh StoryandGitHub ab24f643d0 [Fizz] use microtasks rather than tasks when scheduling work while prerendering (#30770)
Similar to https://github.com/facebook/react/pull/30768 we want to
schedule work during prerendering in microtasks both for the root task
and pings. We continue to schedule flushes as Tasks to allow as much
work to be batched up as possible.
2024-08-21 07:55:56 -07:00
Josh StoryandGitHub 85180b8cf8 [Fizz][Static] when aborting a prerender halt unfinished boundaries instead of erroring (#30732)
When we introduced prerendering for flight we modeled an abort of a
flight prerender as having unfinished rows. This is similar to how
postpone was already implemented when you postponed from "within" a
prerender using React.unstable_postpone. However when aborting with a
postponed instance every boundary would be eagerly marked for client
rendering which is more akin to prerendering and then resuming with an
aborted signal.

The insight with the flight work was that it's not so much the postpone
that describes the intended semantics but the abort combined with a
prerender. So like in flight when you abort a prerender and enableHalt
is enabled boundaries and the shell won't error for any reason. Fizz
will still call onPostpone and onError according to the abort reason but
the consuemr of the prerender should expect to resume it before trying
to use it.
2024-08-20 13:30:51 -07:00
Josh StoryandGitHub 2505bf9b34 [Fizz] track postpones when aborting boundaries with a postpone (#30751)
When aborting with a postpone value boundaries are put into client
rendered mode even during prerenders. This doesn't follow the postpoen
semantics of the rest of fizz where during a prerender a postpone is
tracked and it will leave holes in tracked postpone state that can be
resumed. This change updates this behavior to match the postpones
semantics between aborts and imperative postpones.
2024-08-20 09:49:41 -07:00
Josh StoryandGitHub 7954db9398 [Fizz] handle throwing after abort during render (#30730)
It is possible to throw after aborting during a render and we were not
properly tracking this. We use an AbortSigil to mark whether a rendering
task needs to abort but the throw interrupts that and we end up handling
an error on the error pathway instead.

This change reworks the abort-while-rendering support to be robust to
throws after calling abort
2024-08-16 18:29:18 -07:00
Josh StoryandGitHub a7d1240c96 [Fizz] Update postpone abort semantics when prerendering (#30541)
When aborting with a postpone value in Fizz if any tasks are still
pending in the root while prerendering the prerender will fatally error.
This is different from postponing imperatively in a root task and really
the semantics should be the same. This change updates React to treat an
abort with a postpone value as a postponed root rather than a fatal
error.
2024-07-31 08:33:43 -07:00
Josh StoryandGitHub a451de014c [Fizz] Allow aborting during render (#30488)
Currently if you abort a Fizz render during rendering the render will
not complete correctly because there are inconsistencies with task
counting. This change updates the abort implementation to allow you to
abort from within a render itself. We already landed a similar change
for Flight in #29764
2024-07-29 13:18:15 -07:00
Josh StoryandGitHub 9938e248fe [Fizz] Don't perform work when closing (#30497)
When a Fizz render is closing but not yet closed it's possible that
pinged tasks can spawn more work. The point of the closing state is to
allow time to start piping/reading the underlying stream but
semantically the render is finished at that point so work should no
longer happen.
2024-07-29 11:09:54 -07:00
Josh StoryandGitHub d17e9d1ce5 [Fizz] Prerender fallbacks before children (#30483)
When prerendering it can be convenient to abort the prerender while
rendering. However if any Suspense fallbacks have not yet rendered
before the abort happens the fallback itself will error and cause the
nearest parent Suspense boundary to render a fallback instead.
Prerenders are by definition not time critical so the prioritization of
children over fallbacks which makes sense for render isn't similarly
motivated for prerender. Given this, this change updates fallback
rendering during a prerender to attempt the fallback before attempting
children.
2024-07-26 14:06:47 -07:00
Sebastian MarkbågeandGitHub e8df0cf9f7 Switch to binding the console with badging instead of calling it directly (#30461)
This is a major nit but this avoids an extra stack frame when we're
replaying logs.

Normally the `printToConsole` frame doesn't show up because it'd be
ignore listed.

<img width="421" alt="Screenshot 2024-07-25 at 11 49 39 AM"
src="https://github.com/user-attachments/assets/81334c2f-e19e-476a-871e-c4db9dee294e">

When you expand to show ignore listed frames a ton of other frames show
up.

<img width="516" alt="Screenshot 2024-07-25 at 11 49 47 AM"
src="https://github.com/user-attachments/assets/2ab8bdfb-464c-408d-9176-ee2fabc114b6">

The annoying thing about this frame is that it's at the top of the stack
where as typically framework stuff ends up at the bottom and something
you can ignore. The user space stack comes first.

With this fix there's no longer any `printToConsole` frame.

<img width="590" alt="Screenshot 2024-07-25 at 12 09 09 PM"
src="https://github.com/user-attachments/assets/b8365d53-31f3-43df-abce-172d608d3c9c">

Am I wiling to eat the added complexity and slightly slower performance
for this nit? Definitely.
2024-07-25 12:32:16 -04:00
Jan KassensandGitHub b7e7f1a3fa [BE] upgrade prettier to 3.3.3 (#30420)
Mostly just changes in ternary formatting.
2024-07-22 16:09:01 -04:00
Sebastian MarkbågeandGitHub b15c1983dc [Flight] Normalize Stack Using Fake Evals (#30401)
Stacked on https://github.com/facebook/react/pull/30400 and
https://github.com/facebook/react/pull/30369

Previously we were using fake evals to recreate a stack for console
replaying and thrown errors. However, for owner stacks we just used the
raw string that came from the server.

This means that the format of the owner stack could include different
formats. Like Spidermonkey format for the client components and V8 for
the server components. This means that this stack can't be parsed
natively by the browser like when printing them as error like in
https://github.com/facebook/react/pull/30289. Additionally, since
there's no source file registered with that name and no source mapping
url, it can't be source mapped.

Before:

<img width="1329" alt="before-firefox"
src="https://github.com/user-attachments/assets/cbe03f9c-96ac-48fb-b58f-f3a224a774f4">

Instead, we need to create a fake stack like we do for the other things.
That way when it's printed as an Error it gets source mapped. It also
means that the format is consistently in the native format of the
current browser.

After:

<img width="753" alt="after-firefox"
src="https://github.com/user-attachments/assets/b436f1f5-ca37-4203-b29f-df9828c9fad3">

So this is nice because you can just take the result from
`captureOwnerStack()` and append it to an `Error` stack and print it
natively. E.g. this is what React DevTools will do.

If you want to parse and present it yourself though it's a bit awkward
though. The `captureOwnerStack()` API now includes a bunch of
`rsc://React/` URLs. These don't really have any direct connection to
the source map. Only the browser knows this connection from the eval.
You basically have to strip the prefix and then manually pass the
remainder to your own `findSourceMapURL`.

Another awkward part is that since Safari doesn't support eval sourceURL
exposed into `error.stack` - it means that `captureOwnerStack()` get an
empty location for server components since the fake eval doesn't work
there. That's not a big deal since these stacks are already broken even
for client modules for many because the `eval-source-map` strategy in
Webpack doesn't work in Safari for this same reason.

A lot of this refactoring is just clarifying that there's three kind of
ReactComponentInfo fields:

- `stack` - The raw stack as described on the original server.
- `debugStack` - The Error object containing the stack as represented in
the current client as fake evals.
- `debugTask` - The same thing as `debugStack` but described in terms of
a native `console.createTask`.
2024-07-22 11:03:15 -04:00
Sebastian MarkbågeandGitHub 792f192114 Tag all user space call sites with the "react-stack-bottom-frame" name (#30369)
Ideally we wouldn't need to filter out React internals and it'd just be
covered by ignore listing by any downstream tool. E.g. a framework using
captureOwnerStack could have its own ignore listing. Printed owner
stacks would get browser source map ignore-listing. React DevTools could
have its own ignore list for internals. However, it's nice to be able to
provide nice owner stacks without a bunch of noise by default.
Especially on the server since they have to be serialized.

We currently call each function that calls into user space and track its
stack frame. However, this needs code for checking each one and doesn't
let us work across bundles.

Instead, we can name each of these frame something predictable by giving
the function a name.

Unfortunately, it's a common practice to rename functions or inline them
in compilers. Even if we didn't, others downstream from us or a dev-mode
minifier could. I use this `.bind()` trick to avoid minifying these
functions and ensure they get a unique name added to them in all
browsers. It's not 100% fool proof since a smart enough compiler could
also discover that the `this` value is not used and strip out the
function and then inline it but nobody does this yet at least.

This lets us find the bottom stack easily from stack traces just by
looking for the name.
2024-07-22 10:47:38 -04:00
Jan KassensandGitHub af28f480e8 Feature flag to disable legacy context for function components (#30319)
While the goal is to remove legacy context completely, I think we can
already land the removal of legacy context for function components. I
didn't even know this feature existed until reading the code recently.

The win is just a couple of property lookups on function renders, but it
trims down the API already as the full removal will likely still take a
bit more time.

www: Starting with enabled test renderer and a feature flag for
production rollout.

RN: Not enabled, will follow up on this.
2024-07-11 16:21:12 -04:00
Jan KassensandGitHub 8e00cf04de easy: add link to legacy context warning (#30315)
Missed this when adding the link to other places of the same warning in
https://github.com/facebook/react/commit/378b305958eb7259cacfce8ad0e66eec07e07074
2024-07-11 10:42:37 -04:00
Jan KassensandGitHub 378b305958 Warn about legacy context when legacy context is not disabled (#30297)
For environments that still have legacy contexts available, this adds a
warning to make the remaining call sites easier to locate and encourage
upgrades.
2024-07-10 11:53:00 -04:00
Sebastian MarkbågeandGitHub b73dcdc04f [Fizz] Refactor Component Stack Nodes (#30298)
Component stacks have a similar problem to the problem with keyPath
where we had to move it down and set it late right before recursing.
Currently we work around that by popping exactly one off when something
suspends. That doesn't work with the new server stacks being added which
are more than one. It also meant that we kept having add a single frame
that could be popped when there shouldn't need to be one.

Unlike keyPath component stacks has this weird property that once
something throws we might need the stack that was attempted for errors
or the previous stack if we're going to retry and just recreate it.

I've tried a few different approaches and I didn't like either but this
is the one that seems least problematic.

I first split out renderNodeDestructive into a retryNode helper. During
retries only retryNode is called. When we first discover a node, we pass
through renderNodeDestructive.

Instead of add a component stack frame deep inside renderNodeDestructive
after we've already refined a node, we now add it before in
renderNodeDestructive. That way it's only added once before being
attempted. This is similar to how Fiber works where in ChildFiber we
match the node once to create the instance and then later do we attempt
to actually render it and it's only the second part that's ever retried.

This unfortunately means that we now have to refine the node down to
element/lazy/thenables twice. To avoid refining the type too I move that
to be done lazily.
2024-07-09 15:44:01 -04:00
Sebastian MarkbågeandGitHub df783f9ea1 Add unknown location information to component stacks (#30290)
This is the same change as in #30289 but for the main runtime - e.g.
parent stacks in errorInfo.componentStack, appended stacks to
console.error coming from React itself and when we add virtual frames to
owner stacks.

Since we don't add location information these frames look weird to some
stack parsers - such as the native one. This is an existing issue when
you want to use some off-the-shelf parsers to parse production component
stacks for example.

While we won't add Error objects to logs ourselves necessarily, some
third party could want to do the same thing we do in DevTools and so we
should provide the same capability to just take this trace and print it
using an Error object.
2024-07-08 11:54:14 -04:00
3db98c9177 [Fizz] Track Current debugTask and run it for onError Callbacks (#30182)
Stacked on #30174.

This tracks the current debugTask on the Task so that when an error is
thrown we can use it to run the `onError` (and `onShellError` and
`onFatalError`) callbacks within the Context of that task. Ideally it
would be associated with the error object but neither console.error [nor
reportError](https://crbug.com/350426235) reports this as the async
stack so we have to manually restore it.

That way when you inspect Fizz using node `--inspect` we show the right
async stack.

<img width="616" alt="Screenshot 2024-07-01 at 10 52 29 PM"
src="https://github.com/facebook/react/assets/63648/db68133e-124e-4509-8241-c67160db94fc">

This is equivalent to how we track the task that created the parent
server component or the Fiber:


https://github.com/facebook/react/blob/6d2a97a7113dfac2ad45067001b7e49a98718324/packages/react-reconciler/src/ReactChildFiber.js#L1985

Then use them when invoking the error callbacks:


https://github.com/facebook/react/blob/6d2a97a7113dfac2ad45067001b7e49a98718324/packages/react-reconciler/src/ReactFiberThrow.js#L104-L108

---------

Co-authored-by: Sebastian Silbermann <silbermann.sebastian@gmail.com>
2024-07-02 19:46:18 -04:00
Sebastian MarkbågeandGitHub cfb8945f51 [Fizz] Implement debugInfo (#30174)
Stacked on #30170.

This lets us track Server Component parent stacks in Fizz which also
lets us track the correct owner stack for lazy.

In Fiber we're careful not to make any DEV only fibers but since the
ReactFizzComponentStack data structures just exist for debug meta data
anyway we can just expand on that.
2024-07-02 18:26:32 -04:00
Sebastian MarkbågeandGitHub e60063d9e7 [Fizz] Include a component stack in prod but only lazily generate it (#30132)
When we added component stacks to Fizz in prod it severely slowed down
common cases where you intentionally are throwing error for purposes of
client rendering. Our parent component stack generation is very slow
since call components with fake errors to generate them.

Therefore we disabled them in prod but included them in prerenders.
https://github.com/facebook/react/pull/27850

However, we still kept generating data structures for them and the code
still exists there for the prerenders. We could stop generating the data
structures which are not completely free but also not crazy bad.

What we can do instead is just lazily generate the component stacks.
This is in fact what plain stacks do anyway. This doesn't work as well
in Fiber because the data structures are live but on the server they're
immutable so it's fine to do it later as well.

That way you can choose to not read this getter for intentionally thrown
errors - after inspecting the Error object - yet still get component
stacks in prod for other errors.
2024-07-02 16:05:20 -04:00
Sebastian MarkbågeandGitHub 6d2a97a711 [Fizz] Gate legacyContext field on disableLegacyContext (#30173)
We're running out of fields and this one we can avoid at runtime in any
modern builds.
2024-07-01 15:30:00 -04:00
Sebastian MarkbågeandGitHub 315109b02b [Fizz] Enable owner stacks for SSR (#30152)
Stacked on #30142.

This tracks owners and their stacks in DEV in Fizz. We use the
ComponentStackNode as the data structure to track this information -
effectively like ReactComponentInfo (Server) or Fiber (Client). They're
the instance.

I then port them same logic from ReactFiberComponentStack,
ReactFiberOwnerStack and ReactFiberCallUserSpace to Fizz equivalents.

This gets us both owner stacks from `captureOwnerStack()`, as well as
appended to console.errors logged by Fizz, while rendering and in
onError.
2024-07-01 10:27:52 -04:00
Sebastian MarkbågeandGitHub e6783e7cc9 [Fizz] Run console.createTask during SSR when available (#30142)
Same as #30140 but for Fizz.

This is rarely used but it does allow seeing component stacks when
inspecting the Node.js server running Fizz using `--inspect` and the
Chrome DevTools.

<img width="504" alt="Screenshot 2024-06-29 at 4 08 22 PM"
src="https://github.com/facebook/react/assets/63648/f89bdf89-2598-42b4-8623-3b87f03326c4">
2024-07-01 09:50:45 -04:00
Sebastian MarkbågeandGitHub 349a99a7a3 Badge Environment Name on Thrown Errors from the Server (#29846)
When we replay logs we badge them with e.g. `[Server]`. That way it's
easy to identify that the source of the log actually happened on the
Server (RSC). However, when we threw an error we didn't have any such
thing. The error was rethrown on the client and then handled just like
any other client error.

This transfers the `environmentName` in DEV to our restored Error
"sub-class" (conceptually) along with `digest`. That way you can read
`error.environmentName` to print this in your own UI.

I also updated our default for `onCaughtError` (and `onError` in Fizz)
to use the `printToConsole` helper that the Flight Client uses to log it
with the badge format. So by default you get the same experience as
console.error for caught errors:

<img width="810" alt="Screenshot 2024-06-10 at 9 25 12 PM"
src="https://github.com/facebook/react/assets/63648/8490fedc-09f6-4286-9332-fbe6b0faa2d3">

<img width="815" alt="Screenshot 2024-06-10 at 9 39 30 PM"
src="https://github.com/facebook/react/assets/63648/bdcfc554-504a-4b1d-82bf-b717e74975ac">

Unfortunately I can't do the same thing for `onUncaughtError` nor
`onRecoverableError` because they use `reportError` which doesn't have
custom formatting (unless we also prevented default on window.onerror).
However maybe that's ok because 1) you should always have an error
boundary 2) it's not likely that an RSC error can actually recover
because it's not going to be rendered again so shouldn't really happen
outside some parent conditionally rendering maybe.

The other problem with this approach is that the default is no longer
trivial - so reimplementing the default in user space is trickier and
ideally we shouldn't expose our default to be called.
2024-06-26 13:27:26 -04:00
Sebastian MarkbågeandGitHub 01a40570c3 [Flight/Fizz] Use Constructors for Large Request/Response Objects in Flight/Fizz (#29858)
We know from Fiber that inline objects with more than 16 properties in
V8 turn into dictionaries instead of optimized objects. The trick is to
use a constructor instead of an inline object literal. I don't actually
know if that's still the case or not. I haven't benchmarked/tested the
output. Better safe than sorry.

It's unfortunate that this can have a negative effect for Hermes and JSC
but it's not as bad as it is for V8 because they don't deopt into
dictionaries. The time to construct these objects isn't a concern - the
time to access them frequently is.

We have to beware the Task objects in Fizz. Those are currently on 16
fields exactly so we shouldn't add anymore ideally.

We should ideally have a lint rule against object literals with more
than 16 fields on them. It might not help since sometimes the fields are
conditional.
2024-06-11 15:55:29 -04:00
Sebastian MarkbågeandGitHub d6cfa0f295 [Fiber] Use Owner/JSX Stack When Appending Stacks to Console (#29206)
This one should be fully behind the `enableOwnerStacks` flag.

Instead of printing the parent Component stack all the way to the root,
this now prints the owner stack of every JSX callsite. It also includes
intermediate callsites between the Component and the JSX call so it has
potentially more frames. Mainly it provides the line number of the JSX
callsite. In terms of the number of components is a subset of the parent
component stack so it's less information in that regard. This is usually
better since it's more focused on components that might affect the
output but if it's contextual based on rendering it's still good to have
parent stack. Therefore, I still use the parent stack when printing DOM
nesting warnings but I plan on switching that format to a diff view
format instead (Next.js already reformats the parent stack like this).

__Follow ups__

- Server Components show up in the owner stack for client logs but logs
done by Server Components don't yet get their owner stack printed as
they're replayed. They're also not yet printed in the server logs of the
RSC server.

- Server Component stack frames are formatted as the server and added to
the end but this might be a different format than the browser. E.g. if
server is running V8 and browser is running JSC or vice versa. Ideally
we can reformat them in terms of the client formatting.

- This doesn't yet update Fizz or DevTools. Those will be follow ups.
Fizz still prints parent stacks in the server side logs. The stacks
added to user space `console.error` calls by DevTools still get the
parent stacks instead.

- It also doesn't yet expose these to user space so there's no way to
get them inside `onCaughtError` for example or inside a custom
`console.error` override.

- In another follow up I'll use `console.createTask` instead and
completely remove these stacks if it's available.
2024-05-25 11:58:17 -04:00
Sebastian MarkbågeandGitHub 84239da896 Move createElement/JSX Warnings into the Renderer (#29088)
This is necessary to simplify the component stack handling to make way
for owner stacks. It also solves some hacks that we used to have but
don't quite make sense. It also solves the problem where things like key
warnings get silenced in RSC because they get deduped. It also surfaces
areas where we were missing key warnings to begin with.

Almost every type of warning is issued from the renderer. React Elements
are really not anything special themselves. They're just lazily invoked
functions and its really the renderer that determines there semantics.

We have three types of warnings that previously fired in
JSX/createElement:

- Fragment props validation.
- Type validation.
- Key warning.

It's nice to be able to do some validation in the JSX/createElement
because it has a more specific stack frame at the callsite. However,
that's the case for every type of component and validation. That's the
whole point of enableOwnerStacks. It's also not sufficient to do it in
JSX/createElement so we also have validation in the renderers too. So
this validation is really just an eager validation but also happens
again later.

The problem with these is that we don't really know what types are valid
until we get to the renderer. Additionally, by placing it in the
isomorphic code it becomes harder to do deduping of warnings in a way
that makes sense for that renderer. It also means we can't reuse logic
for managing stacks etc.

Fragment props validation really should just be part of the renderer
like any other component type. This also matters once we add Fragment
refs and other fragment features. So I moved this into Fiber. However,
since some Fragments don't have Fibers, I do the validation in
ChildFiber instead of beginWork where it would normally happen.

For `type` validation we already do validation when rendering. By
leaving it to the renderer we don't have to hard code an extra list.
This list also varies by context. E.g. class components aren't allowed
in RSC but client references are but we don't have an isomorphic way to
identify client references because they're defined by the host config so
the current logic is flawed anyway. I kept the early validation for now
without the `enableOwnerStacks` since it does provide a nicer stack
frame but with that flag on it'll be handled with nice stacks anyway. I
normalized some of the errors to ensure tests pass.

For `key` validation it's the same principle. The mechanism for the
heuristic is still the same - if it passes statically through a parent
JSX/createElement call then it's considered validated. We already did
print the error later from the renderer so this also disables the early
log in the `enableOwnerStacks` flag.

I also added logging to Fizz so that key warnings can print in SSR logs.

Flight is a bit more complex. For elements that end up on the client we
just pass the `validated` flag along to the client and let the client
renderer print the error once rendered. For server components we log the
error from Flight with the server component as the owner on the stack
which will allow us to print the right stack for context. The factoring
of this is a little tricky because we only want to warn if it's in an
array parent but we want to log the error later to get the right debug
info.

Fiber/Fizz has a similar factoring problem that causes us to create a
fake Fiber for the owner which means the logs won't be associated with
the right place in DevTools.
2024-05-23 12:48:57 -04:00
94eed63c49 (Land #28798) Move Current Owner (and Cache) to an Async Dispatcher (#28912)
Rebasing and landing https://github.com/facebook/react/pull/28798

This PR was approved already but held back to give time for the sync.
Rebased and landing here without pushing to seb's remote to avoid
possibility of lost updates

---------

Co-authored-by: Sebastian Markbage <sebastian@calyptus.eu>
2024-04-25 10:40:40 -07:00
Sebastian MarkbågeandGitHub 9f2eebd807 [Fiber/Fizz] Support AsyncIterable as Children and AsyncGenerator Client Components (#28868)
Stacked on #28849, #28854, #28853. Behind a flag.

If you're following along from the side-lines. This is probably not what
you think it is.

It's NOT a way to get updates to a component over time. The
AsyncIterable works like an Iterable already works in React which is how
an Array works. I.e. it's a list of children - not the value of a child
over time.

It also doesn't actually render one component at a time. The way it
works is more like awaiting the entire list to become an array and then
it shows up. Before that it suspends the parent.

To actually get these to display one at a time, you have to opt-in with
`<SuspenseList>` to describe how they should appear. That's really the
interesting part and that not implemented yet.

Additionally, since these are effectively Async Functions and uncached
promises, they're not actually fully "supported" on the client yet for
the same reason rendering plain Promises and Async Functions aren't.
They warn. It's only really useful when paired with RSC that produces
instrumented versions of these. Ideally we'd published instrumented
helpers to help with map/filter style operations that yield new
instrumented AsyncIterables.

The way the implementation works basically just relies on unwrapThenable
and otherwise works like a plain Iterator.

There is one quirk with these that are different than just promises. We
ask for a new iterator each time we rerender. This means that upon retry
we kick off another iteration which itself might kick off new requests
that block iterating further. To solve this and make it actually
efficient enough to use on the client we'd need to stash something like
a buffer of the previous iteration and maybe iterator on the iterable so
that we can continue where we left off or synchronously iterate if we've
seen it before. Similar to our `.value` convention on Promises.

In Fizz, I had to do a special case because when we render an iterator
child we don't actually rerender the parent again like we do in Fiber.
However, it's more efficient to just continue on where we left off by
reusing the entries from the thenable state from before in that case.
2024-04-22 13:25:05 -04:00
Sebastian MarkbågeandGitHub 368202181e Warn for Child Iterator of all types but allow Generator Components (#28853)
This doesn't change production behavior. We always render Iterables to
our best effort in prod even if they're Iterators.

But this does change the DEV warnings which indicates which are valid
patterns to use.

It's a footgun to use an Iterator as a prop when you pass between
components because if an intermediate component rerenders without its
parent, React won't be able to iterate it again to reconcile and any
mappers won't be able to re-apply. This is actually typically not a
problem when passed only to React host components but as a pattern it's
a problem for composability.

We used to warn only for Generators - i.e. Iterators returned from
Generator functions. This adds a warning for Iterators created by other
means too (e.g. Flight or the native Iterator utils). The heuristic is
to check whether the Iterator is the same as the Iterable because that
means it's not possible to get new iterators out of it. This case used
to just yield non-sense like empty sets in DEV but not in prod.

However, a new realization is that when the Component itself is a
Generator Function, it's not actually a problem. That's because the
React Element itself works as an Iterable since we can ask for new
generators by calling the function again. So this adds a special case to
allow the Generator returned from a Generator Function's direct child.
The principle is “don’t pass iterators around” but in this case there is
no iterator floating around because it’s between React and the JS VM.

Also see #28849 for context on AsyncIterables.

Related to this, but Hooks should ideally be banned in these for the
same reason they're banned in Async Functions.
2024-04-21 12:51:45 -04:00
Josh StoryandGitHub c8a035036d [Fizz] hoistables should never flush before the preamble (#28802)
Hoistables should never flush before the preamble however there is a
surprisingly easy way to trigger this to happen by suspending in the
shell of the app. This change modifies the flushing behavior to not emit
any hoistables before the preamble has written. It accomplishes this by
aborting the flush early if there are any pending root tasks remaining.
It's unfortunate we need this extra condition but it's essential that we
don't emit anything before the preamble and at the moment I don't see a
way to do that without introducing a new condition.

There is a test that began to fail with this update. It turns out that
in node the root can be blocked during a resume even for a component
inside a Suspense boundary if that boundary was part of the prerender.
This means that with the current heuristic in this PR boundaries cannot
be flushed during resume until the root is unblocked. This is not ideal
but this is already how Edge works because the root blocks the stream in
that case. This just makes Node deopt in a similar way to edge. We
should improve this but we ought to do so in a way that works for edge
too and it needs to be more comprehensive.
2024-04-11 15:13:04 -07:00
Sebastian MarkbågeandGitHub d50323eb84 Flatten ReactSharedInternals (#28783)
This is similar to #28771 but for isomorphic. We need a make over for
these dispatchers anyway so this is the first step. Also helps flush out
some internals usage that will break anyway.

It flattens the inner mutable objects onto the ReactSharedInternals.
2024-04-08 19:23:23 -04:00
Andrew ClarkandGitHub bfd8da807c Make class prop resolution faster (#28766)
`delete` causes an object (in V8, and maybe other engines) to deopt to a
dictionary instead of a class. Instead of `assign` + `delete`, manually
iterate over all the properties, like the JSX runtime does.

To avoid copying the object twice I moved the `ref` prop removal to come
before handling default props. If we already cloned the props to remove
`ref`, then we can skip cloning again to handle default props.
2024-04-05 13:06:39 -04:00
Andrew ClarkandGitHub 48b4ecc901 Remove defaultProps support (except for classes) (#28733)
This removes defaultProps support for all component types except for
classes. We've chosen to continue supporting defaultProps for classes
because lots of older code relies on it, and unlike function components,
(which can use default params), there's no straightforward alternative.

By implication, it also removes support for setting defaultProps on
`React.lazy` wrapper. So this will not work:

```js
const MyClassComponent = React.lazy(() => import('./MyClassComponent'));
// MyClassComponent is not actually a class; it's a lazy wrapper. So
// defaultProps does not work.
MyClassComponent.defaultProps = { foo: 'bar' };
```

However, if you set the default props on the class itself, then it's
fine.

For classes, this change also moves where defaultProps are resolved.
Previously, defaultProps were resolved by the JSX runtime. This change
is only observable if you introspect a JSX element, which is relatively
rare but does happen.

In other words, previously `<ClassWithDefaultProp />.props.aDefaultProp`
would resolve to the default prop value, but now it does not.
2024-04-04 10:59:19 -04:00
Sebastian MarkbågeandGitHub 583eb6770d Emit Server Error Prefix in the .stack Property Too (#28738)
Follow up to #28684.

V8 includes the message in the stack and printed errors include just the
stack property which is assumed to contain the message. Without this,
the prefix doesn't get printed in the console.

<img width="578" alt="Screenshot 2024-04-03 at 6 32 04 PM"
src="https://github.com/facebook/react/assets/63648/d98a2db4-6ebc-4805-b669-59f449dfd21f">

A possible alternative would be to use a nested error with a `cause`
like #28736 but that would need some more involved serializing since
this prefix is coming from the server. Perhaps as a separate attribute.
2024-04-03 21:52:54 -04:00
Andrew ClarkandGitHub 3761acb42b Classes consume ref prop during SSR, too (#28731)
Same as #28719 but for SSR.
2024-04-03 12:55:57 -04:00
Jan KassensandGitHub 7a2609eedc Cleanup enableBigIntSupport flag (#28711)
Cleanup enableBigIntSupport flag
2024-04-03 09:25:02 -04:00