Updates Fizz to handle Hoistables (Resources and Elements) in a way that
better aligns with Suspense fallbacks
1. Hoistable Elements inside a fallback (regardless of how deep and how
many additional boundaries are intermediate) will be ignored. The
reasoning is fallbacks are transient and since there is not good way to
clean up hoistables because they escape their Suspense container its
better to not emit them in the first place. SSR fallbacks are already
not full fidelity because they never hydrate so this aligns with that
somewhat.
2. Hoistable stylesheets in fallbacks will only block the reveal of a
parent suspense boundary if the fallback is going to flush with that
completed parent suspense boundary. Previously if you rendered a
stylesheet Resource inside a fallback any parent suspense boundaries
that completed after the shell flushed would include that resource in
the set required to resolve before the boundary reveal happens on the
client. This is not a semantic change, just a performance optimization
3. preconnect and preload hoistable queues are gone, if you want to
optimize resource loading you shoudl use `ReactDOM.preconnect` and
`ReactDOM.preload`. `viewport` meta tags get their own queue because
they need to go before any preloads since they affect the media state.
In addition to those functional changes this PR also refactors the
boundary resource tracking by moving it to the task rather than using
function calls at the start of each render and flush. Tasks also now
track whether they are a fallback task
supercedes prior work here: https://github.com/facebook/react/pull/27534
DiffTrain build for [554fc49f41](https://github.com/facebook/react/commit/554fc49f41465d914b15dc8eb2ec094f37824f7e)
Before, we used to reset the thenable state and extract the previous
state very early so that it's only the retried task that can possibly
consume it. This is nice because we can't accidentally consume that
state for any other node.
However, it does add a lot of branches of code that has to pass this
around. It also adds extra bytes on the stack per node. Even though it's
mostly just null.
This changes it so that where ever we can create a thenable state (e.g.
entering a component with hooks) we first extract this from the task.
The principle is that whatever could've created the thenable state in
the first place, must always be rerendered so it'll take the same code
paths to get there and so we'll always consume it.
DiffTrain build for [382190c595](https://github.com/facebook/react/commit/382190c595126837ee7e43b4fa953f4edd30e01c)
This refactors the Flight render loop to behave more like Fizz with
similar naming conventions. So it's easier to apply similar techniques
across both. This is not necessarily better/faster - at least not yet.
This doesn't yet implement serialization by writing segments to chunks
but we probably should do that since the built-in parts that
`JSON.stringify` gets us isn't really much anymore (except serializing
strings). When we switch to that it probably makes sense for the whole
thing to be recursive.
Right now it's not technically fully recursive because each recursive
render returns the next JSON value to encode. So it's kind of like a
trampoline. This means we can't have many contextual things on the
stack. It needs to use the Server Context `__POP` trick. However, it
does work for things that are contextual only for one sequence of server
component abstractions in a row. Since those are now recursive.
An interesting observation here is that `renderModel` means that
anything can suspend while still serializing the outer siblings.
Typically only Lazy or Components would suspend but in principle a Proxy
can suspend/postpone too and now that is left serialized by reference to
a future value. It's only if the thing that we rendered was something
that can reduce to Lazy e.g. an Element that we can serialize it as a
lazy.
Similarly to how Suspense boundaries in Fizz can catch errors, anything
that can be reduced to Lazy can also catch an error rather than bubbling
it. It only errors when the Lazy resolves. Unlike Suspense boundaries
though, those things don't render anything so they're otherwise going to
use the destructive form. To ensure that throwing in an Element can
reuse the current task, this must be handled by `renderModel`, not for
example `renderElement`.
DiffTrain build for [b123b9c4f0](https://github.com/facebook/react/commit/b123b9c4f054a7def7ed84e350ccd46cc86672a6)
If this is a client reference we shouldn't dot into it, which would
throw in the proxy.
Interestingly our client references don't really have a `name`
associated with them for debug information so a component type doesn't
show up in error logs even though it seems like it should.
DiffTrain build for [0ac3ea471f](https://github.com/facebook/react/commit/0ac3ea471fbcb7d79bc7d36179e960c72c779e76)
If we end up client rendering a boundary due to an error after we have
already injected a postponed hole in that boundary we'll end up trying
to target a missing segment. Since we never insert segments for an
already errored boundary into the HTML. Normally an errored prerender
wouldn't be used but if it is, such as if it was an intentional client
error it triggers this case. Those should really be replaced with
postpones though.
This is a bit annoying since we eagerly build up the postponed path. I
took the easy route here and just cleared out the suspense boundary
itself from having any postponed slots. However, this still creates an
unnecessary replay path along the way to the boundary. We could probably
walk the path and remove any empty parent nodes.
What is worse is that if this is the only thing that postponed, we'd
still generate a postponed state even though there's actually nothing to
resume. Since this is a bit of an edge case already maybe it's fine.
In my test I added a check for the `error` event on `window` since this
error only surfaces by throwing an ignored error. We should really do
that globally for all tests. Our tests should fail by default if there's
an error logged to the window.
DiffTrain build for [f9dddcbbb1](https://github.com/facebook/react/commit/f9dddcbbb1c0b73f974e78b9488927b778630682)
Historically React would produce component stacks for dev builds only.
There is a cost to tracking component stacks and given the prod builds
try to optimize runtime performance these stacks were left out. More
recently React added production component stacks to Fiber in because it
can be immensely helpful in tracking down hard to debug production
issues. Fizz was not updated to have a similar behavior.
With the advent of prerendering however stacks for production in Fizz
are more relevant because prerendering is not really a dev-time task. If
you want the ability to reason about errors or postpones that happen
during a prerender having component stacks to interrogate is helpful and
these component stacks need to be available in production otherwise you
are really never going to see them. (it is possible that you could do
dev-mode prerenders but we don't expect this to be a common dev mode
workflow)
To better support the prerender use case and to make error logging in
Fizz more useful the following changes have been made
1. `onPostpone` now accepts a second `postponeInfo` argument which will
contain a componentStack. Postpones always originate from a component
render so the stack should be consistently available. The type however
will indicate the stack is optional so we can remove them in the future
if we decide the overhead is the wrong tradeoff in certain cases
2. `onError` now accepts a second `errorInfo` argument which may contain
a componentStack. If an error originated from a component a stack will
be included in the following cases.
This change entails tracking the component hierarchy in prod builds now.
While this isn't cost free it is implemented in a relatively lean
manner. Deferring the most expensive work (reifying the stack) until we
are actually in an error pathway.
In the course of implementing this change a number of simplifications
were made to the code which should make the stack tracking more
resilient. We no longer use a module global to curry the stack up to
some handler. This was delicate because you needed to always reset it
properly. We now curry the stack on the task itself.
Another change made was to track the component stack on SuspenseBoundary
instances so that we can provide the stack when aborting suspense
boundaries to help you determine which ones were affected by an abort.
DiffTrain build for [63310df2b2](https://github.com/facebook/react/commit/63310df2b243b6c3b2f01e8b121e7d115e839cfb)
`onHeaders` can throw however for now we can assume that headers are
optimistic values since the only things we produce for them are preload
links. This is a pragmatic decision because React could concievably have
headers in the future which were not optimistic and thus non-optional
however it is hard to imagine what these headers might be in practice.
If we need to change this behavior to be fatal in the future it would be
a breaking change.
This commit adds error logging when `onHeaders` throws and ensures the
request can continue to render successfully.
DiffTrain build for [ee68446ff1](https://github.com/facebook/react/commit/ee68446ff198755bd38202ac9139275b657968b0)
When we postpone during a render we inject a new segment synchronously
which we postpone. That gets assigned an ID so we can refer to it
immediately in the postponed state.
When we do that, the parent segment may complete later even though it's
also synchronous. If that ends up not having any content in it, it'll
inline into the child and that will override the child's segment id
which is not correct since it was already assigned one.
To fix this, we simply opt-out of the optimization in that case which is
unfortunate because we'll generate many more unnecessary empty segments.
So we should come up with a new strategy for segment id assignment but
this fixes the bug.
Co-authored-by: Josh Story <story@hey.com>
DiffTrain build for [0e352ea01c](https://github.com/facebook/react/commit/0e352ea01c0209b6c5e23f0e857af4e01c783024)
In order to make Haste work with React's artifacts, It is important to
keep headers in this format:
```
/**
* ...
...
* ...
*/
```
For optimization purposes, Closure compiler will actually modify these
headers by removing * prefixes, which is expected.
We should pass sources to the compiler without license headers, with
these changes the current flow will be:
1. Apply top-level definitions. For UMD-bundles, for example, or
DEV-only bundles (e. g. `if (__DEV__) { ...`)
2. Apply licence headers for artifacts with sourcemaps: oss-production
and oss-profiling bundles, they don't need to preserve the header format
to comply with Haste. We need to apply these headers before passing
sources to Closure, so it can build correct mappings for sourcemaps.
3. Pass these sources to closure compiler for minification and
sourcemaps building.
4. Apply licence headers for artifacts without sourcemaps: dev bundles,
fb bundles. This way the header style will be preserved and not changed
by Closure.
DiffTrain build for [c47c306a7a](https://github.com/facebook/react/commit/c47c306a7a23d3c796b148d303764e2832da480c)
I introduced a bug in a recent change to how bootstrap scripts are
handled. Rather than clearing out the bootstrap script state from
ResumableState on completion of the prerender I did it during the
flushing phase which comes later after the postponed state has likely
been serialized. We should freeze these objects in dev so this is not
possible to do easily in test (nor in actual code in real systems).
This fixes the bug by eliminating the bootstrap config during
getPostponedState which is before the state can be serialized.
DiffTrain build for [7468903294](https://github.com/facebook/react/commit/746890329452cbec8685eb3466847c5f17d9dc77)
Previously it was possible to postpone in the shell during a prerender
and then during a resume the bootstrap scripts would not be emitted
leading to no hydration on the client. This change moves the bootstrap
configuration to `ResumableState` where it can be serialized after
postponing if it wasn't flushed as part of the static shell.
DiffTrain build for [7508dcd5cc](https://github.com/facebook/react/commit/7508dcd5cc245e376860d65402972e418199264d)
## Summary
There's a bug with the existing stack comparison algorithm in
`describeNativeComponentFrame` — specifically how it attempts to find a
common root frame between the control and sample stacks. This PR
attempts to fix that bug by injecting a frame that can have a guaranteed
string in it for us to search for in both stacks to find a common root.
## Brief Background/How it works now
Right now `describeNativeComponentFrame` does the following to leverage
native browser/VM stack frames to get details (e.g. script path, row and
col #s) for a single component:
1. Throwing and catching a control error in the function
2. Calling the component which should eventually throw an error (most of
the time), that we'll catch as our sample error.
3. Diffing the stacks in the control and sample errors to find the line
which should represent our component call.
## What's broken
To account for potential stack trace truncation, the stack diffing
algorithm first attempts to find a common "root" frame by inspecting the
earliest frame of the sample stack and searching for an identical frame
in the control stack starting from the bottom. However, there are a
couple of scenarios which I've hit that cause the above approach to not
work correctly.
First, it's possible that for render passes of extremely large component
trees to have a lot of repeating internal react function calls, which
can result in an incorrect common or "root" frame found. Here's a small
example from a stack trace using React Fizz for SSR.
Our control frame can look like this:
```
Error:
at Fake (...)
at construct (native)
at describeNativeComponentFrame (...)
at describeClassComponentFrame (...)
at getStackByComponentStackNode (...)
at getCurrentStackInDEV (...)
at renderNodeDestructive (...)
at renderElement (...)
at renderNodeDestructiveImpl (...) // <-- Actual common root frame with the sample stack
at renderNodeDestructive (...)
at renderElement (...)
at renderNodeDestructiveImpl (...) // <-- Incorrectly chosen common root frame
at renderNodeDestructive (...)
```
And our sample stack can look like this:
```
Error:
at set (...)
at PureComponent (...)
at call (native)
at apply (native)
at ErrorBoundary (...)
at construct (native)
at describeNativeComponentFrame (...)
at describeClassComponentFrame (...)
at getStackByComponentStackNode (...)
at getCurrentStackInDEV (...)
at renderNodeDestructive (...)
at renderElement (...)
at renderNodeDestructiveImpl (...) // <-- Root frame that's common in the control stack
```
Here you can see that the earliest trace in the sample stack, the
`renderNodeDestructiveImpl` call, can exactly match with multiple
`renderNodeDestructiveImpl` calls in the control stack (including file
path and line + col #s). Currently the algorithm will chose the
earliest/last frame with the `renderNodeDestructiveImpl` call (which is
the second last frame in our control stack), which is incorrect. The
actual matching frame in the control stack is the latest or first frame
(when traversing from the top) with the `renderNodeDestructiveImpl`
call. This leads to the rest of the stack diffing associating an
incorrect frame (`at getStackByComponentStackNode (...)`) for the
component.
Another issue with this approach is that it assumes all VMs will
truncate stack traces at the *bottom*, [which isn't the case for the
Hermes
VM](https://github.com/facebook/hermes/blob/df07cf713a84a4434c83c08cede38ba438dc6aca/lib/VM/JSError.cpp#L688-L699)
which **truncates stack traces in the middle**, placing a
```
at renderNodeDestructiveImpl (...)
... skipping {n} frames
at renderNodeDestructive (...)
```
line in the middle of the stack trace for all stacks that contain more
than 100 traces. This causes stack traces for React Native apps using
the Hermes VM to potentially break for large component trees. Although
for this specific case with Hermes, it's possible to account for this by
either manually grepping and removing the `... skipping` line and
everything below it (see draft PR: #26999), or by implementing the
non-standard `prepareStackTrace` API which Hermes also supports to
manually generate a stack trace that truncates from the bottom ([example
implementation](https://github.com/facebook/react/compare/main...KarimP:react:component-stack-hermes-fix)).
## The Fix
I found different ways to go about fixing this. The first was to search
for a common stack frame starting from the top/latest frame. It's a
relatively small change ([see
implementation](https://github.com/facebook/react/compare/main...KarimP:react:component-stack-fix-2)),
although it is less performant by being n^2 (albeit with `n`
realistically being <= 5 here). It's also a bit more buggy for class
components given that different VMs insert a different amount of
additional lines for new/construct calls...
Another fix would be to actually implement a [longest common
substring](https://en.wikipedia.org/wiki/Longest_common_substring)
algorithm, which can also be roughly n^2 time (assuming the longest
common substring between control and sample will be most of the sample
frame).
The fix I ended up going with was have the lines that throw the control
error and the lines that call/instantiate the component be inside a
distinct method under an object property
(`"DescribeNativeComponentFrameRoot"`). All major VMs (Safari's
JavaScriptCore, Firefox's SpiderMonkey, V8, Hermes, and Bun) should
display the object property name their stack trace. I've also set the
`name` and `displayName` properties for method as well to account for
minification, any advanced optimizations (e.g. key crushing), and VM
inconsistencies (both Bun and Safari seem to exclusively use the value
under `displayName` and not `name` in traces for methods defined under
an object's own property...).
We can then find this "common" frame by simply finding the line that has
our special method name (`"DescribeNativeComponentFrameRoot"`), and the
rest of the code to determine the actual component line works as
expected. If by any chance we don't find a frame with our special method
name in either control or sample stack traces, we then revert back to
the existing approach mentioned above by searching for the last line of
the sample frame in the control frame.
## How did you test this change?
1. There are bunch of existing tests that ensure a properly formatted
component trace is logged for certain scenarios, so I ensured the
existing full test suite passed
2. I threw an error in a component that's deep in the component
hierarchy of a large React app (facebook) to ensure there's stack trace
truncation, and ensured the correct component stack trace was logged for
Chrome, Safari, and Firefox, and with and without minification.
3. Ran a large React app (facebook) on the Hermes VM, threw an error in
a component that's deep in the component hierarchy, and ensured that
component frames are generated despite stack traces being truncated in
the middle.
DiffTrain build for [88b00dec47](https://github.com/facebook/react/commit/88b00dec4778ffa230cceca81af3328f49e1efd9)
<!--
Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.
Before submitting a pull request, please make sure the following is
done:
1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
10. If you haven't already, complete the CLA.
Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->
## Summary
This PR updates the Rollup build pipeline to generate sourcemaps for
production build artifacts like `react-dom.production.min.js`.
It requires the Rollup v3 changes that were just merged in #26442 .
Sourcemaps are currently _only_ generated for build artifacts that are
_truly_ "production" - no sourcemaps will be generated for development,
profiling, UMD, or `shouldStayReadable` artifacts.
The generated sourcemaps contain the bundled source contents right
before that chunk was minified by Closure, and _not_ the original source
files like `react-reconciler/src/*`. This better reflects the actual
code that is running as part of the bundle, with all the feature flags
and transformations that were applied to the source files to generate
that bundle. The sourcemaps _do_ still show comments and original
function names, thus improving debuggability for production usage.
Fixes#20186 .
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->
This allows React users to actually debug a readable version of the
React bundle in production scenarios. It also allows other tools like
[Replay](https://replay.io) to do a better job inspecting the React
source when stepping through.
## How did you test this change?
- Generated numerous sourcemaps with various combinations of the React
bundle selections
- Viewed those sourcemaps in
https://evanw.github.io/source-map-visualization/ and confirmed via the
visualization that the generated mappings appear to be correct
I've attached a set of production files + their sourcemaps here:
[react-sourcemap-examples.zip](https://github.com/facebook/react/files/11023466/react-sourcemap-examples.zip)
You can drag JS+sourcemap file pairs into
https://evanw.github.io/source-map-visualization/ for viewing.
Examples:
- `react.production.min.js`:

- `react-dom.production.min.js`:

- `use-sync-external-store/with-selector.production.min.js`:

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
If you leave this empty, your PR will very likely be closed.
-->
DiffTrain build for [2c8a139a59](https://github.com/facebook/react/commit/2c8a139a593e0294c3a6953d74b451bd05fdcfca)
Adds a new option to `react-dom/server` entrypoints.
`onHeaders: (headers: Headers) => void` (non node envs)
`onHeaders: (headers: { Link?: string }) => void` (node envs)
When any `renderTo...` or `prerender...` function is called and this
option is provided the supplied function will be called sometime on or
before completion of the render with some preload link headers.
When provided during a `renderTo...` the callback will usually be called
after the first pass at work. The idea here is we want to get a set of
headers to start the browser loading well before the shell is ready. We
don't wait for the shell because if we did we may as well send the
preloads as tags in the HTML.
When provided during a `prerender...` the callback will be called after
the entire prerender is complete. The idea here is we are not responding
to a live request and it is preferable to capture as much as possible
for preloading as Headers in case the prerender was unable to finish the
shell.
Currently the following resources are always preloaded as headers when
the option is provided
1. prefetchDNS and preconnects
2. font preloads
3. high priority image preloads
Additionally if we are providing headers when the shell is incomplete
(regardless of whether it is render or prerender) we will also include
any stylesheet Resources (ones with a precedence prop)
There is a second option `maxHeadersLength?: number` which allows you to
specify the maximum length of the header content in unicode code units.
This is what you get when you read the length property of a string in
javascript. It's improtant to note that this is not the same as the
utf-8 byte length when these headers are serialized in a Response. The
utf8 representation may be the same size, or larger but it will never be
smaller.
If you do not supply a `maxHeadersLength` we defaul to `2000`. This was
chosen as half the value of the max headers length supported by commonly
known web servers and CDNs. many browser and web server can support
significantly more headers than this so you can use this option to
increase the headers limit. You can also of course use it to be even
more conservative. Again it is important to keep in mind there is no
direct translation between the max length and the bytelength and so if
you want to stay under a certain byte length you need to be potentially
more aggressive in the maxHeadersLength you choose.
Conceptually `onHeaders` could be called more than once as new headers
are discovered however if we haven't started flushing yet but since most
APIs for the server including the web standard Response only allow you
to set headers once the current implementation will only call it one
time
DiffTrain build for [2983249dd2](https://github.com/facebook/react/commit/2983249dd2bb1a295f27939e36ab0de9e4bfab76)
When we postpone a prerender in the shell, we should just leave an empty
prelude and resume from the root. While preserving any options passed
in.
Since we haven't flushed anything we can't assume we've already emitted
html/body tags or any resources tracked in the resumable state. This
introduces a resetResumableState function to reset anything we didn't
flush.
This is a bit hacky. Ideally, we probably shouldn't have tracked it as
already happened until it flushed or something like that.
Basically, it's like restarting the prerender with the same options and
then immediately aborting. When we add the preload headers, we'd track
those as preload() being emitted after the reset and so they get readded
to the resumable state in that case.
DiffTrain build for [05fbd1aab0](https://github.com/facebook/react/commit/05fbd1aab036ecbd14469d6376024439bc931f68)
Float methods can hang on to a reference to a Request after the request
is closed due to AsyncLocalStorage. If a Float method is called at this
point we do not want to attempt to flush anything. This change updates
the closing logic to also call `stopFlowing` which will ensure that any
checks against the destination properly reflect that we cannot do any
writes. In addition it updates the enqueueFlush logic to existence check
the destination inside the work function since it can change across the
work scheduling gap if it is async.
fixes: https://github.com/facebook/react/issues/27540
DiffTrain build for [601e5c3850](https://github.com/facebook/react/commit/601e5c38505ebc0ee099d8666b2f7a8b03159ac4)
<!--
Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.
Before submitting a pull request, please make sure the following is
done:
1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
10. If you haven't already, complete the CLA.
Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->
## Summary
Changes `before before` to `before` in error messages.
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->
I want to improve error logs
## How did you test this change?
<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
If you leave this empty, your PR will very likely be closed.
-->
DiffTrain build for [67cc9ba878](https://github.com/facebook/react/commit/67cc9ba8789335b9da0838a446137c1be62adbcd)
This is the same problem as we had with keyPath before where if the
element itself suspends, we have to restore the replay node to what it
was before, however, if something below the element suspends we
shouldn't pop it because that will pop it back up the stack.
Instead of passing replay as an argument to every renderElement
function, I use a hack to compare if the node is still the same as the
one we tried to render, then that means we haven't stepped down into the
child yet. Maybe this is not quite correct because in theory you could
have a recursive node that just renders itself over and over until some
context bails out.
This solves an issue where if you suspended in an element it would retry
trying to replay from that element but using the postponed state from
the root.
DiffTrain build for [09fbee89d6](https://github.com/facebook/react/commit/09fbee89d62bc1c4a00e64474346cb9bc87682cb)
The `enableCustomElementPropertySupport` flag changes React's handling
of custom elements in a way that is more useful that just treating every
prop as an attribute. However when server rendering we have no choice
but to serialize props as attributes. When this flag is on and React
supports more prop types on the client like functions and objects the
server implementation needs to be a bit more naunced in how it renders
these components. With this flag on `false`, function, and object props
are omitted entirely and `true` is normalized to `""`. There was a bug
however in the implementation which caused children more complex than a
single string to be omitted because it matched the object type filter.
This change reorganizes the code a bit to put these filters in the
default prop handline case, leaving children, style, and innerHTML to be
handled via normal logic.
fixes: https://github.com/facebook/react/issues/27286
DiffTrain build for [bb778528d1](https://github.com/facebook/react/commit/bb778528d1ca22b44dad832f0258aaa4c0e6d4a4)
Adds a second argument to useDeferredValue called initialValue:
```js
const value = useDeferredValue(finalValue, initialValue);
```
During the initial render of a component, useDeferredValue will return
initialValue. Once that render finishes, it will spawn an additional
render to switch to finalValue.
This same sequence should occur whenever the hook is hidden and revealed
again, i.e. by a Suspense or Activity, though this part is not yet
implemented.
When initialValue is not provided, useDeferredValue has no effect during
initial render, but during an update, it will remain on the previous
value, then spawn an additional render to switch to the new value. (This
is the same behavior that exists today.)
During SSR, initialValue is always used, if provided.
This feature is currently behind an experimental flag. We plan to ship
it in a non-breaking release.
DiffTrain build for [be67db46b6](https://github.com/facebook/react/commit/be67db46b60d94f9fbefccf2523429af25873e5b)
The way we collect component stacks right now are pretty fragile.
We expect that we'll call captureBoundaryErrorDetailsDev whenever an
error happens. That resets lastBoundaryErrorComponentStackDev to null
but if we don't, it just lingers and we don't set it to anything new
then which leaks the previous component stack into the next time we have
an error. So we need to reset it in a bunch of places.
This is still broken with erroredReplay because it has the inverse
problem that abortRemainingReplayNodes can call
captureBoundaryErrorDetailsDev more than one time. So the second
boundary won't get a stack.
We probably should try to figure out an alternative way to carry along
the stack. Perhaps WeakMap keyed by the error object.
This also fixes an issue where we weren't invoking the onShellReady
event if we error a replay. That event is a bit weird for resuming
because we're probably really supposed to just invoke it immediately if
we have already flushed the shell in the prerender which is always atm.
Right now, it gets invoked later than necessary because you could have a
resumed hole ready before a sibling in the shell is ready and that's
blocked.
DiffTrain build for [0fba3ecf73](https://github.com/facebook/react/commit/0fba3ecf73900a1b54ed6d3b0617462ac92d2fef)
The `resumeElement` function wasn't actually doing the correct thing
because it was resuming the element itself but what the child slot means
is that we're supposed to resume in the direct child of the element.
This is difficult to check for since it's all the possible branches that
the element can render into, so instead we just check this in
renderNode. It means the hottest path always checks the task which is
unfortunate.
And also, resuming using the correct nextSegmentId.
Fixes two bugs surfaced by this test.
---------
Co-authored-by: Josh Story <josh.c.story@gmail.com>
DiffTrain build for [a6ed60a8eb](https://github.com/facebook/react/commit/a6ed60a8eb0626e5f84d0bdbb62c0b61219150d3)
Refactors Resources to have a more compact and memory efficient
struture. Resources generally are just an Array of chunks. A resource is
flushed when it's chunks is length zero. A resource does not have any
other state.
Stylesheets and Style tags are different and have been modeled as a unit
as a StyleQueue. This object stores the style rules to flush as part of
style tags using precedence as well as all the stylesheets associated
with the precedence. Stylesheets still need to track state because it
affects how we issue boundary completion instructions. Additionally
stylesheets encode chunks lazily because we may never write them as html
if they are discovered late.
The preload props transfer is now maximally compact (only stores the
props we would ever actually adopt) and only stores props for
stylesheets and scripts because other preloads have no resource
counterpart to adopt props into. The ResumableState maps that track
which keys have been observed are being overloaded. Previously if a key
was found it meant that a resource already exists (either in this render
or in a prior prerender). Now we discriminate between null and object
values. If map value is null we can assume the resource exists but if it
is an object that represents a prior preload for that resource and the
resource must still be constructed.
DiffTrain build for [49eba01930](https://github.com/facebook/react/commit/49eba01930e9e1f331b34967fca65d5a0ba62846)
This fixes so that you can postpone in a fallback. This postpones the
parent boundary. I track the fallbacks in a separate replay node so that
when we resume, we can replay the fallback itself and finish the
fallback and then possibly later the content. By doing this we also
ensure we don't complete the parent too early since now it has a render
task on it.
There is one case that this surfaces that isn't limited to
prerender/resume but also render/hydrateRoot. I left todos in the tests
for this.
If you postpone in a fallback, and suspend in the content but eventually
don't postpone in the content then we should be able to just skip
postponing since the content rendered and we no longer need the
fallback. This is a bit of a weird edge case though since fallbacks are
supposed to be very minimal.
This happens because in both cases the fallback starts rendering early
as soon as the content suspends. This also ensures that the parent
doesn't complete early by increasing the blocking tasks. Unfortunately,
the fallback will irreversibly postpone its parent boundary as soon as
it hits a postpone.
When you suspend, the same thing happens but we typically deal with this
by doing a "soft" abort on the fallback since we don't need it anymore
which unblocks the parent boundary. We can't do that with postpone right
now though since it's considered a terminal state.
I think I'll just leave this as is for now since it's an edge case but
it's an annoying exception in the model. Makes me feel I haven't quite
nailed it just yet.
DiffTrain build for [bff6be8eb1](https://github.com/facebook/react/commit/bff6be8eb1d77980c13f3e01be63cb813a377058)
We currently abort a stream either it's explicitly told to abort (e.g.
by an abortsignal). In this case we still finish writing what we have as
well as instructions for the client about what happened so it can
trigger fallback cases and log appropriately.
We also abort a request if the stream itself cancels. E.g. if you can't
write anymore. In this case we should not write anything to the outgoing
stream since it's supposed to be closed already now. However, we should
still abort the request so that more work isn't performed and so that we
can log the reason for it to the onError callback.
We should also not do any work after aborting.
There we need to stop the "flow" of bytes - so I call stopFlowing in the
cancel case before aborting.
The tests were testing this case but we had changed the implementation
to only start flowing at initial read (pull) instead of start like we
used to. As a result, it was no longer covering this case. We have to
call reader.read() in the tests to start the flow so that we need to
cancel it.
We also were missing a final assertion on the error logs and since we
were tracking them explicitly the extra error was silenced.
DiffTrain build for [d9e00f795b](https://github.com/facebook/react/commit/d9e00f795b77676fb14f2a3c6f421f48f73bec2a)
The key is that instead of storing different tags of resumable points,
we just store if a replay node has any resumable slots and if that's at
the root `number` or if it has resumable slots by index.
This is a simpler and more compact format because we don't have to
separate the three Resume forms.
This helps deal with Postpone in fallbacks because it doesn't just
double all the cases.
DiffTrain build for [47fed6961f](https://github.com/facebook/react/commit/47fed6961f79e8c8388f5f11fe1a531b194bb4af)
To support MPA-style form submissions, useFormState sends down a key
that represents the identity of the hook on the page. It's based on the
key path of the component within the React tree; for deeply nested
hooks, this keypath can become very long. We can hash the key to make it
shorter.
Adds a method called createFastHash to the Stream Config interface.
We're not using this for security or obfuscation, only to generate a
more compact key without sacrificing too much collision resistance.
- In Node.js builds, createFastHash uses the built-in crypto module.
- In Bun builds, createFastHash uses Bun.hash. See:
https://bun.sh/docs/api/hashing#bun-hash
I have not yet implemented createFastHash in the Edge, Browser, or FB
(Hermes) stream configs because those environments do not have a
built-in hashing function that meets our requirements. (We can't use the
web standard `crypto` API because those methods are async, and yielding
to the main thread is too costly to be worth it for this particular use
case.) We'll likely use a pure JS implementation in those environments;
for now, they just return the original string without hashing it. I'll
address this in separate PRs.
DiffTrain build for [2b3d582683](https://github.com/facebook/react/commit/2b3d5826836ac59f8446281976762d594e55d97e)
## Summary
Currently `ReactFizzServer.abort` allows you to pass in the a `reason`
error, which then gets passed to the `onError` handler for each task
that ends up getting aborted. This adds in the ability to pass down that
same `reason` error to `ReactDOMServerFB.abortStream` as well.
## How did you test this change?
Added a test case to ReactDOMServerFB-test.internal.js
DiffTrain build for [1f4936660d](https://github.com/facebook/react/commit/1f4936660dcaba2e5722013f0ad12d7369543a9f)
Moves writing queues to renderState.
We shouldn't need the resource tracking's value. We just need to know if
that resource has already been emitted. We can use a Set for this. To
ensure that set is directly serializable we can just use a
dictionary-like object with no value.
See individual commits for special cases.
DiffTrain build for [b775564d35](https://github.com/facebook/react/commit/b775564d35d6fe9f9acead0f380bbc86bb6bdd2c)
Originally the intension was to have React assign an ID to a user
rendered DOM node inside a `fallback` while it was loading. If there
already were an explicit `id` defined on the DOM element we would reuse
that one instead. That's why this was a DOM Config option and not just
built in to Fizz.
This became tricky since it can load late and so we'd have to transfer
it down and detect it only once it finished rendering and if there is no
DOM element it doesn't work anyway. So instead, what we do in practice
is to always use a `<template>` tag with the ID. This has the downside
of an extra useless node and shifting child CSS selectors.
Maybe we'll get around to fixing this properly but it might not be worth
it.
This PR just gets rid of the SuspenseBoundaryID concept and instead we
just use the same ID number as the root segment ID of the boundary to
refer to the boundary to simplify the implementation.
This also solves the problem that SuspenseBoundaryID isn't currently
serializable (although that's easily fixable by itself if necessary).
DiffTrain build for [2807d781a0](https://github.com/facebook/react/commit/2807d781a08db8e9873687fccc25c0f12b4fb3d4)
Based on #27385.
When we error or abort during replay, that doesn't actually error the
component that errored because that has already rendered. The error only
affects any child that is not yet completed. Therefore the error kind of
gets thrown at the resumable point.
The resumable point might be a hole in the replay path, in which case
throwing there errors the parent boundary just the same as if the replay
component errored. If the hole is inside a deeper Suspense boundary
though, then it's that Suspense boundary that gets client rendered. I.e.
the child boundary. We can still finish any siblings.
In the shell all resumable points are inside a boundary since we must
have finished the shell. Therefore if you error in the root, we just
simply just turn all incomplete boundaries into client renders.
DiffTrain build for [925c66a647](https://github.com/facebook/react/commit/925c66a6472a61e7174dabf6c697a9a320ebc170)
The idea for this check is that we shouldn't flush anything before we
flush the shell. That may or may not hold true in future formats like
RN.
It is a problem for resuming because with resuming it's possible to have
root tasks that are used for resuming but the shell was already flushed
so we can have completed boundaries before the shell has fully resumed.
What matters is whether the parent has already flushed or not.
It's not technically necessary to bail early because there won't be
anything in partialBoundaries or completedBoundaries because nothing
gets added there unless the parent has already flushed.
It's not exactly slow to have to check the length of three arrays so
it's probably not a big deal.
Flush partials in an early preamble needs further consideration
regardless.
DiffTrain build for [df061b3966](https://github.com/facebook/react/commit/df061b396644bb7c27ea76bb27ae09784ce6444b)
This forks Task into ReplayTask and RenderTask.
A RenderTask is the normal mode and it has a segment to write into.
A ReplayTask doesn't have a segment to write into because that has
already been written but instead it has a ReplayState which keeps track
of the next set of paths to follow. Once we hit a "Resume" node we
convert it into a RenderTask and continue rendering from there.
We can resume at either an Element position or a Slot position. An
Element pointing to a component doesn't mean we resume that component,
it means we resume in the child position directly below that component.
Slots are slots inside arrays.
Instead of statically forking most paths, I kept using the same path and
checked for the existence of a segment or replay state dynamically at
runtime.
However, there's still quite a bit of forking here like retryRenderTask
and retryReplayTask. Even in the new forks there's a lot of duplication
like resumeSuspenseBoundary, replaySuspenseBoundary and
renderSuspenseBoundary. There's opportunity to simplify this a bit.
DiffTrain build for [a5fc797db1](https://github.com/facebook/react/commit/a5fc797db14c6e05d4d5c4dbb22a0dd70d41f5d5)
The permalink option of useFormState controls which page the form is
submitted to during an MPA form submission (i.e. a submission that
happens before hydration, or when JS is disabled). If the same
useFormState appears on the resulting page, and the permalink option
matches, it should receive the form state from the submission despite
the fact that the keypaths do not match.
So the logic for whether a form state instance is considered a match is:
- Both instances must be passed the same action signature
- If a permalink is provided, the permalinks must match.
- If a permalink is not provided, the keypaths must match.
Currently, if there are multiple matching useFormStates, they will all
match and receive the form state. We should probably only match the
first one, and/or warn when this happens. I've left this as a TODO for
now, pending further discussion.
DiffTrain build for [caa716d50b](https://github.com/facebook/react/commit/caa716d50bdeef3a1ac5e3e0cfcc14f4d91f2028)
Typically we assign IDs lazily when flushing to minimize the ids we have
to assign and we try to maximize inlining.
When we prerender we could always flush into a buffer before returning
but that's not actually what we do right now. We complete rendering
before returning but we don't actually run the flush path until someone
reads the resulting stream. We assign IDs eagerly when something
postpone so that we can refer to those ids in the holes without flushing
first.
This leads to some interesting conditions that needs to consider this.
This PR also deals with rootSegmentId which is the ID of the segment
that contains the body of a Suspense boundary. The boundary needs to
know this in addition to the Suspense Boundary's ID to know how to
inject the root segment into the boundary.
Why is the suspense boundary ID and the rootSegmentID just not the same
ID? Why don't segments and suspense boundary not share ID namespace?
That's a good question and I don't really remember. It might not be a
good reason and maybe they should just be the same.
DiffTrain build for [a6e4791b11](https://github.com/facebook/react/commit/a6e4791b11816374d015eb4531a82e6cf209c7f2)
During an MPA form submission, useFormState should only reuse the form
state if same action is passed both times. (We also compare the key
paths.)
We compare the identity of the inner closure function, disregarding the
value of the bound arguments. That way you can pass an inline Server
Action closure:
```js
function FormContainer({maxLength}) {
function submitAction(prevState, formData) {
'use server'
if (formData.get('field').length > maxLength) {
return { errorMsg: 'Too many characters' };
}
// ...
}
return <Form submitAction={submitAction} />
}
```
DiffTrain build for [95c9554bc7](https://github.com/facebook/react/commit/95c9554bc72813b0ee2b028774bb7cf0482887ba)
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_`)
DiffTrain build for [612b2b6601](https://github.com/facebook/react/commit/612b2b6601abb844248c384d1e288bb824b180b7)
This field was not being initialized. Although the property is part of
the Flow type, the type error wasn't caught because the constructor
itself is not covered by Flow, which is unfortunate. (I assume this is
related to the dev-only componentStack property.)
DiffTrain build for [69be472c11](https://github.com/facebook/react/commit/69be472c11231056d297a7b73dd0b121905606d3)
Currently, if a component suspends, the keyPath has already been
modified to include the identity of the component itself; the path is
set before the component body is called (akin to the begin phase in
Fiber). An accidental consequence is that when the promise resolves and
component is retried, the identity gets appended to the keyPath again,
leading to a duplicate node in the path.
To address this, we should only modify contexts after any code that may
suspend. For maximum safety, this should occur as late as possible:
right before the recursive renderNode call, before the children are
rendered.
I did not add a test yet because there's no feature that currently
observes it, but I do have tests in my other WIP PR for useFormState:
#27321
DiffTrain build for [d07921eeda](https://github.com/facebook/react/commit/d07921eeda62613bfcf52ecdb66322db26393567)
There's a subtle difference if you suspend before the first array or
after. In Fiber, we don't deal with this because we just suspend the
parent and replay it if lazy() or Usable are used in its child slots. In
Fizz we try to optimize this a bit more and enable resuming inside the
component.
Semantically, it's different if you suspend/postpone before the first
child array or inside that child array. Because when you resume the
inner result might be another array and either that's part of the parent
path or part of the inner slot.
There might be more clever way of structuring this but I just use -1 to
indicate that we're not yet inside the array and is in the root child
position. If that renders an element, then that's just the same as the 0
slot.
We need to also encode this in the resuming. I called that resuming the
element or resuming the slot.
DiffTrain build for [7a3cb8f9cf](https://github.com/facebook/react/commit/7a3cb8f9cf43591afc74722ece9e3216ccc98128)
When Float was first developed the internal implementation and external
interface were the same. This is problematic for a few reasons. One, the
public interface is typed but it is also untrusted and we should not
assume that it is actually respected. Two, the internal implementations
can get called from places other than the the public interface and
having to construct an options argument that ends up being destructured
to process the request is computationally wasteful and may limit JIT
optimizations to some degree. Lastly, the wire format was not as
compressed as it could be and it was untyped.
This refactor aims to address that by separating the public interface
from the internal implementations so we can solve these challenges and
also make it easier to change Float in the future
* The internal dispatcher method preinit is now preinitStyle and
preinitScript.
* The internal dispatcher method preinitModule is now
preinitModuleScript in anticipation of different implementations for
other module types in the future.
* The wire format is explicitly typed and only includes options if they
are actually used omitting undefined and nulls.
* Some function arguments are not options even if they are optional. For
instance precedence can be null/undefined because we deafult it to
'default' however we don't cosnider this an option because it is not
something we transparently apply as props to the underlying instance.
* Fixes a problem with keying images in flight where srcset and sizes
were not being taken into account.
* Moves argument validation into the ReactDOMFloat file where it is
shared with all runtimes that expose these methods
* Fixes crossOrigin serialization to use empty string except when
'use-credentials'
DiffTrain build for [bbc8530ed7](https://github.com/facebook/react/commit/bbc8530ed7a67859583a7c990ac51cd39c7746e5)