[Secure Ecmascript](https://github.com/tc39/proposal-ses) would freeze
the prototype of intrinsics. Since `ReactPromise` inherits the prototype
from `Promise`, it also copies over the writable definition.
Using `defineProperty` on an inherited property is compatible with SES
though. That's also closer to how classes are specced in JS.
---------
Co-authored-by: Hanzo Dev <[email protected]>
Adds SSR support for nested parentEnter/parentExit View Transitions.
Fizz now emits vt-parent-enter/vt-parent-exit annotations during
streaming, and the client picks them up on hydration, so nested
enter/exit animations work for Suspense reveals.
Ran into this test failure as part of
https://github.com/react/react/pull/36917 - it seems that the added code
was just enough to increase stack size and fail the deep tree recovery
test in CI. Looking into that, there appears to be a gap here with
retries, including a TODO test case for the scenario.
Fizz recovers from stack overflows in extremely deep trees by catching
the first overflow in the `renderNode` trampoline and spawning a
continuation task. That continuation is retried via `retryRenderTask →
retryNode`, which has no trampoline above it. So if the remaining tree
still doesn't fit in one fresh stack, the overflow was treated as a
fatal error instead of recovering again.
This fix re-schedules the task when a retried render overflows but
`task.node` advanced (proving forward progress was made). If this is a
real in-component overflow, `task.node` doesn't advance and we still
fail.
The existing test used `n={1000}`, which only required one recovery
round and didn't catch this gap in source mode. It's updated to
`n={1200}`, which reliably requires multiple recovery rounds.
## Summary
Newer V8 versions (Node.js ≥ ~22) name the stack frames of static
methods on the `Promise` constructor `Promise.all`, `Promise.race`,
etc., where older versions named them `Function.all`, `Function.race`,
etc. The `isPromiseCreationInternal` and `isPromiseAwaitInternal`
allowlists in `ReactFlightServer` only matched the old spellings.
## How did you test this change?
- `yarn test --no-watchman ReactFlightAsyncDebugInfo` — 18/18 pass on
both Node 20.19.0 and Node 24.16.0 (previously, "can track async
information when awaited" failed on Node 24)
- `yarn test --silent --no-watchman packages/react-server/src/__tests__
packages/react-server-dom-webpack` — 243/243 pass on both Node versions,
default and `-r=experimental` channels
- `yarn test-www --silent --no-watchman
packages/react-server/src/__tests__` with `__VARIANT__` true and false —
pass
- `yarn flow dom-node`, `yarn prettier`, `yarn linc` — pass
Co-authored-by: Hanzo Dev <[email protected]>
Follow-up to #36903
The previous change stopped `onAllReady` from firing after a fatal shell
error by assigning `noop` to `request.onAllReady` inside `fatalError`.
This mutated a completion callback on the request as a side channel,
mirroring the existing `request.onShellError = noop` in `completeShell`.
This refactor removes both noop assignments and instead guards the calls
at the point where they would happen, so the callbacks on the request
stay the ones the caller passed in.
For `onAllReady`, `erroredTask` now returns right after `fatalError` in
the root (`boundary === null`) case, so it no longer falls through to
the `allPendingTasks === 0` check that calls `completeAll`. This matches
the sibling `finishAbortedTask`, which already returns after a fatal
root error. Later task completions cannot reach `completeAll` either,
because `performWork` bails out once the request status is past `OPEN`.
For `onShellError`, `fatalError` now decides whether to call it based on
the same condition that governed the assignment: the shell is complete
exactly when `request.pendingRootTasks === 0`, and `completeShell` only
runs at that point, so guarding the call is equivalent to the previous
nulling out. `onFatalError` still always fires because the error is
always fatal to the request.
This is a behavior-preserving refactor of the fix landed in #36903; the
tests added there continue to pass unchanged.
Co-authored-by: Hanzo Dev <[email protected]>
A Node.js `Buffer` carries a `toJSON` method, so Flight serializes it
through that method instead of as binary, and it is deserialized as a
plain `{type: 'Buffer', data: [...]}` object rather than a `Buffer` or
`Uint8Array`. The warning React emits in this case reads "Uint8Array
objects are not supported", which is misleading on two counts: the value
is actually a `Buffer`, reported as `Uint8Array` only because that is
its `Object.prototype.toString` tag, and a real `Uint8Array` is in fact
supported, since it has no `toJSON` and serializes as binary. This makes
the message especially confusing when binary data such as a font is
passed as a serialized value.
The first commit adds a test that pins down the current behavior,
capturing both the misleading warning and the deserialization to a plain
`{type: 'Buffer', data: [...]}` object. The second commit replaces the
warning, for values that are `ArrayBuffer.isView`, with one that names
the actual cause and the fix: the data is serialized through `toJSON`
instead of as binary, so a `Uint8Array` or `ArrayBuffer` should be
passed to send binary data. The new branch only fires for a typed array
that also carries a `toJSON`, which in practice is a Node `Buffer`; a
plain `Uint8Array` or `ArrayBuffer` has no `toJSON`, never reaches this
branch, and continues to serialize as binary.
`getPostponedState` snapshots `request.nextSegmentId` at `onAllReady`,
before the Fizz stream is flowing. At this point we have visited all
Suspense boundaries and know which ones suspended by user code or not.
However, only when the stream is flowing are we counting the size of
each boundary. When we detect large boundaries, we suspend them i.e. we
outline them instead of keeping them inline. This results in more
segments being written while the postponed state holds a stale count.
We now keep a reference to the returned postponed state and mutate the
segment IDs when we outline. That way serializing `postponed` after the
`prelude` has flushed writes the latest postponed state.
The current API design means that you can potentially serialize stale
postponed state. We're considering a redesign to make these issues
impossible (e.g. https://github.com/react/react/pull/36815).
For now, the postponed state should only be serialized or passed onto a
`resume` once the `prelude` has flushed e.g.:
```js
const { createWriteStream, writeFileSync } = require('node:fs');
const { createWriteStream } = require('node:stream');
const { prelude, postponed } = prenderToNodeStream(...)
// serializing `postponed` now would write a stale state.
// serialize prelude
const destination = createWriteStream('prelude.html')
prelude.pipe(destination)
await finished(prelude)
// now we can serialize postponed
writeFileSync('postponed.json', JSON.stringify(postponed))
```
---------
Co-authored-by: Sebastian Sebbie Silbermann <[email protected]>
Move the toJSON handling out of the JSON.stringify replacer path and
into an explicit recursive resolution step that uses v8's optimized
single-arg JSON.stringify call.
This has been pulled out of the original implementation in
https://github.com/react/react/pull/36053 on the advice of
https://github.com/react/react/pull/36181 (thanks @unstubbable)
### NB: \_\_proto\_\_
The only difference is surrounding the treatment of `{}` vs
`Object.create(null)`. https://github.com/react/react/pull/36053 uses
the latter, which avoids `__proto__` issues, but is slower in
microbenchmarks due to v8 semantics.
https://github.com/react/react/pull/36181 uses the former (`{}`) which
is faster in micro benchmarks but doesn't special-case `__proto__`
according to spec. This PR does both (`{}` and special case), with no
measurable performance difference that I could produce.
---
The rest of this PR's description is reproduced from
https://github.com/react/react/pull/36181:
---
### Problem
When serializing a Flight chunk, `emitChunk` currently calls
`JSON.stringify(value, task.toJSON)`. The `task.toJSON` replacer is
called for every key-value pair in the serialized JSON. While the logic
inside the replacer is lightweight, the C++ to JavaScript boundary
crossing on every node adds up — V8's `JSON.stringify` is implemented in
C++, and calling back into JavaScript for every property incurs overhead
that scales with the number of keys in the output.
### Change
Replace the replacer with a two-step process:
1. `resolveModel()` recursively walks the rendered value, calling
`renderModel()` on each child — doing the same transformation the
replacer used to do, but entirely in JavaScript without C++ boundary
crossings.
2. `JSON.stringify()` is called with no replacer, staying entirely in
C++.
The `resolveModel` walk also replicates `JSON.stringify`'s `toJSON`
semantics for `Date` objects.
### Results
Measured using the Flight SSR benchmark fixture (#36180) on a dashboard
app with ~25 components, 200 product rows (~325KB Flight payload).
Tested across Node 20, 22, and 24.
- **`bench:bare`** (in-process, no script injection): Flight+Fizz sync
median improves by **~4-5%** consistently across all three Node
versions.
- **`bench:server`** (HTTP, c=1): Flight+Fizz sync throughput improves
by **~3-6%** across Node versions. Async results vary between runs but
trend positive.
### Future opportunity
While the immediate performance improvement is moderate, this change
also sets up a potential future optimization: a Flight mode that renders
to an object instead of a stream ([#36143
(comment)](https://github.com/facebook/react/issues/36143#issuecomment-4155701790)).
Since `resolveModel()` already produces a plain JS object tree before
`JSON.stringify` is called, this intermediate representation could
potentially be passed to the SSR client without the
serialization-deserialization roundtrip that the current stream-based
approach requires.
closes#36181
## Summary
Mostly changing the casting syntax from `(x: Y)` to `x as Y`, as the old
syntax was deprecated and always causing an error in Flow
## How did you test this change?
`yarn flow-ci`
## Summary
Notable changes:
- Only $FlowFixMe, $FlowExpectedError suppression comments are supported
- All suppression comments need error code
- A lot of new invalid-compare and constant-condition errors suppressed.
These errors reveal potential logical bugs
## How did you test this change?
`yarn flow-ci`
# Allow Pending Work to Specialize Abort Reasons
## Summary
Fizz currently reports every unfinished task using the same request-wide
abort reason. This makes it possible to observe that a render did not
finish, but not to understand why any individual suspended slot remained
incomplete.
This change allows suspended tasks to report a more specific rejection
reason when the wakeable they are blocked on rejects after abort begins
and before Fizz finalizes that task. Tasks that do not reject during
this window continue to report the general abort reason.
This is primarily motivated by partial prerendering, where aborting is
not merely an exceptional termination mechanism. It is the API used to
intentionally finish a prerender while leaving some work unresolved.
## Motivation
For an ordinary server render, a request abort generally means that the
result is no longer needed. Reporting the same abort reason for every
unfinished task is usually sufficient.
For a partial prerender, the meaning is different. The caller
intentionally aborts in order to produce a partial result. The
unfinished work is then useful information: it identifies which parts of
the tree prevented the prerender from completing.
Today, all of those tasks receive the same abort reason:
```text
slot A -> prerender aborted
slot B -> prerender aborted
slot C -> prerender aborted
```
That says which work was incomplete, but not whether different slots
should be interpreted differently.
For example, an application may have:
- A slow API that is permitted to miss the prerender deadline and should
not produce actionable logging.
- Other work that is expected to finish during prerendering and should
be reported when it does not.
- A data source that can provide additional telemetry about why it did
not finish once it learns that the prerender has been aborted.
With a single request-wide abort reason, `onError` cannot distinguish
these cases.
## Proposed Behavior
When abort begins, Fizz still associates a general abort reason with the
request. That reason remains the fallback for every unfinished task.
However, if a task is suspended on a wakeable and that wakeable rejects
during the interval between:
1. The request beginning to abort.
2. Fizz finalizing that task as aborted.
then Fizz reports the wakeable's rejection reason for that task instead
of the general abort reason.
Conceptually:
```text
slot A -> rejected during abort with TimeoutError("optional recommendations timed out")
slot B -> still pending when abort finishes -> Error("prerender deadline reached")
slot C -> rejected during abort with QueryError("inventory lookup canceled")
```
A rejection that arrives after its task has already been finalized is
ignored.
## Intended Usage
The canonical usage is for the caller to use the same `AbortSignal` both
to terminate the prerender and to notify data sources that may still be
blocking suspended work. A data source can reject with a more specific
error whose `cause` preserves its relationship to the overall abort.
```js
const controller = new AbortController();
const abortReason = new Error('prerender deadline reached');
const result = prerender(<App signal={controller.signal} />, {
signal: controller.signal,
onError(error) {
if (error === abortReason) {
// This task was unfinished but did not provide a more specific reason.
return;
}
if (error instanceof Error && error.cause === abortReason) {
// This task reported a specialized failure caused by the prerender abort.
// For example, suppress an expected optional timeout or record telemetry.
return;
}
// Interpret an ordinary rendering error.
},
});
controller.abort(abortReason);
```
An interested data source can observe that signal and reject pending
work with an operation-specific reason that records the abort as its
cause:
```js
signal.addEventListener(
'abort',
() => {
reject(
new Error('optional recommendations timed out', {
cause: signal.reason,
}),
);
},
{once: true},
);
```
If this rejection arrives before Fizz finishes aborting the suspended
task, `onError` receives the operation-specific error instead of the
general abort reason. Work that does not provide a specialized rejection
continues to report `abortReason` directly.
This allows applications to:
- Suppress logging for intentionally optional or deadline-limited work.
- Surface unfinished work that should be investigated.
- Include operation-specific telemetry or context in aborted-slot
reporting.
- Retain an explicit causal relationship between a specialized error and
the request-wide abort.
## Causality And Scope
Fizz does not attempt to prove that a wakeable rejected because of the
abort signal.
The precise behavior is temporal:
- If a suspended wakeable rejects after abort begins and before its task
is finalized, its rejection specializes that task's abort reason.
- If it does not reject during that interval, the task receives the
general abort reason.
- If it rejects after finalization, the rejection is ignored for Fizz
error reporting.
Using the same `AbortSignal` to notify data sources is the intended
protocol, but Fizz cannot distinguish a rejection caused by that signal
from any unrelated rejection that happens to occur during the abort
window.
Likewise, `signal.aborted` in `onError` lets callers distinguish errors
observed before abort initiation from errors observed after it began. It
does not independently prove causality for an arbitrary rejection.
## Implementation
Previously, a suspended task attached the same ping callback for both
fulfillment and rejection:
```js
wakeable.then(ping, ping);
```
That is correct during ordinary rendering because retrying the task
allows a rejected wakeable to throw through the normal render path,
preserving regular error handling and stack construction.
During abort, however, retrying general work is intentionally
suppressed. To preserve a rejection that arrives during the abort
window, the task now stores distinct fulfillment and rejection ping
callbacks:
```js
wakeable.then(ping.resolve, ping.reject);
```
Before abort begins, `ping.reject` retains existing behavior by
scheduling the task for retry.
After abort begins, `ping.reject` attempts to claim the still-pending
aborted task from its owning abort set. If successful, Fizz finalizes
that task immediately using the rejection reason. The later scheduled
abort finish processes only tasks that remain in their abort sets, using
the general abort reason.
This avoids adding another top-level property to `Task`, whose
production shape is already at the current field-count threshold, while
also covering suspension mechanisms such as `React.lazy` that cannot be
handled by inspecting `use()` thenable state.
## Tests
The tests cover:
- A rejected suspended task reporting a specialized reason while
unrelated pending work still reports the general abort reason.
- Specialization for `React.lazy`, ensuring this is not limited to
`use()` suspension.
- A rejection arriving after abort finalization being ignored.
- The prerender scheduling window in both static Browser and Node APIs,
where abort listeners can reject pending work before abort completion.
Stacked on #36580
When a task calls `abort()` while it is rendering, Fizz intentionally
leaves that task alone during the synchronous abort sweep so it can
unwind normally. If the task then suspends before reaching a normal
abort check, however, it currently remains pending and does not report
the abort reason.
This change completes an aborted task once it has unwound back to the
retry loop. Instead of treating it as an ordinary render error, it is
routed through the existing abort task completion path so prerenders
continue to postpone aborted work correctly and replay tasks use aborted
resume semantics.
If the task suspended through `use()`, preserve its thenable state
before completing the abort. This allows DEV async debug info to replay
the suspended call site and include it in the owner stack, even though
the task began aborting before it suspended.
Add coverage for render, prerender, and resumed replay tasks that
suspend after initiating an abort, including a real-timer test verifying
the suspended call site is retained in DEV owner stacks.
Stacked on #36584
`abort()` currently performs both the synchronous transition into an
aborted request and the reporting/completion of every unfinished task in
the same call. This change splits those phases. Aborting now
synchronously marks the request as aborted, captures the abort reason,
claims pending tasks so already scheduled work cannot continue rendering
them, and captures any DEV async debug information needed at the point
of abort. Reporting and completing the claimed tasks is then performed
from a scheduled `finishAbort()` callback.
This split does not yet allow a promise rejected by an abort listener to
replace the abort reason: work remains blocked once the request has been
aborted, and tests assert that abort-time rejections still report the
original abort reason. It establishes the task boundary needed for a
follow-up change to selectively process rejected suspended work before
completing the remaining aborted tasks.
This is observable for streaming renders because abort cleanup may now
happen after already available output is read. A Suspense boundary that
was previously converted to client rendering before it could be
serialized may instead be emitted as pending first and receive its
client-render instruction when the scheduled abort completion runs.
The scheduled finish must also preserve abort-during-render behavior in
renderers whose scheduler executes synchronously. The request tracks its
currently executing task, and both abort phases leave that task alone so
it can unwind through its normal abort path rather than being completed
twice or reporting an internal control-flow value.
Stacked on #36583
Fizz previously identified a task that was rendering during an abort by
marking its blocked Segment as `RENDERING`. This does not work for
resumed replay tasks because they do not have a Segment, so an abort
during replay could eagerly abort the task before it unwound and then
report the internal `null` throw instead of the abort reason.
Track the task currently executing on the Request so aborting can leave
the in-flight task to unwind through its normal error path for both
render and replay tasks. Since this replaces the only purpose of the
Segment `RENDERING` status, remove that status and its associated
bookkeeping.
When resumed work unwinds after aborting, use the request's abort reason
in the replay catch paths so aborting while replaying a prerendered tree
or while rendering a resumed segment reports the meaningful abort reason
instead of the internal control-flow value.
Previously Fizz represented an active abort using the `ABORTING` request
status. This is ambiguous because aborting a task can synchronously
fatal the request, transitioning it to `CLOSING` or `CLOSED` while
another task is still unwinding from the same abort. Once that happened,
the in-flight task no longer observed that the request was aborted and
could fail to report its abort error.
This change removes the `ABORTING` status and instead tracks whether the
request was aborted independently on the Request. The existing
`fatalError` field continues to store the abort reason. As a result,
tasks that were rendering when an abort occurred continue to observe the
abort even if another aborted task has already fataled the request,
allowing all relevant unfinished task errors to be reported. DEV stalled
replays temporarily mask the aborted state so they can continue to
reconstruct suspended call sites as before.
This also establishes explicit abort state on the Request for follow-up
work that delays abort completion and allows rejected suspended work to
provide more specific abort errors.
There are parts of Fizz that need to schedule work regardless of whether
the primary rendering pathway is drive externally through performWork.
Historically scheduleWork and later s cheduleMicrotask were noops in
this bundle but it makes it hard to reason about the code because you
cannot be assured that calling scheduleWork will actually result in the
function
ever executing. Now we model this explicitly through config. For builds
that drive work through external calls to performaWork we simply omit
any work scheduling in startWork or pi
ngTask. Now that this is modeled explicitly we can implement
scheduleMicrotask and scheduleWork to actually provide a guarantee that
the callbacks will get invoked.
For now I've implemented these two for the fb build as synchronous
however it is likely that queueMicrotask and setTimeout or similar are
preferred.
Stacked on #36574
Normally, a fatal error transitions the request to CLOSED or CLOSING,
which prevents later aborted root tasks from reporting their errors.
Errors inside Suspense boundaries can still be reported, but other
pending root tasks are hidden once the first one fatally errors.
That behavior is useful for a normal fatal render error, where
subsequent work does not need to be processed. During an abort, however,
the abort reason is already the source of failure for every unfinished
task. Treating the first root task visited during abort cleanup as the
only observable fatal error privileges arbitrary task ordering and hides
useful information about the unfinished render.
Continue logging errors for pending tasks aborted after the request has
already fatally errored, while still only failing the shell once.
Aborting is a gate you can only pass through once. A request that is
already aborting, already completed, or already fataled cannot be
aborted a second time. Previously this was generally functionally true
but you could contrive sequences where an onError would fire after a
render fataled. This change makes it more explicit that this is not
semantically correct by bailing out of abort if the request is in a
status that cannot be aborted.
The Flight Server emits Text and TypedArray rows as two chunks: a header
that gives the row's id, type, and content length, followed by the
content itself. These two chunks were pushed into
`completedRegularChunks` (and `completedDebugChunks` in DEV) as separate
items, so when the destination signaled backpressure between them, the
flush would write the header and then break out before reaching the
content. The content chunk was left stranded at the head of the queue.
Async work running while the destination was paused appended new rows to
`completedImportChunks` / `completedHintChunks`, and the next drain
flushed those queues first — splicing the newly-arrived bytes into the
position the Flight Client expects to read as the original row's
content. From there the Flight Client read rows from the wrong byte
offsets and the model failed to deserialize.
This only surfaced on the Node stream path.
`createFakeWritableFromReadableStreamController`, used by
`renderToReadableStream`, always returns `true` from `write()`, so the
flush loop never saw backpressure.
The fix pushes a `NEXT_TWO_CHUNKS_ARE_ATOMIC` sentinel ahead of each
`headerChunk` / `contentChunk` pair in `completedRegularChunks` and
`completedDebugChunks`. The flush loops detect the sentinel and write
the two chunks that follow it together before re-checking backpressure,
so backpressure can still break between rows but never within one.
### Alternatives considered
- **Pushing the pair as a `[headerChunk, contentChunk]` tuple.** Simpler
but allocates an array per row. The required `isArray` branch in the
flush hot path is likely comparable with the symbol check. This also
violates the opaque `Chunk` type boundary.
- **Concatenating header and content into one chunk.** Bad for memory —
typed-array content can be large.
- **Storing atomic groups in a separate queue.** Conceptually wrong and
risks breaking reference-ordering assumptions between rows.
- **Ignoring backpressure until the regulars queue is empty.** Defeats
the point of backpressure.
- **Wrapping the tuple behind a host-config API** (`writeAdjacentChunks`
/ `isAdjacentChunks` / `chunksToAdjacentChunks` /
`getAdjacentChunksLength`). Keeps the implementation opaque but adds
four exports per host config. Also has the tuple overhead.
- **Begin/end sentinels for variable-length atomic groups.** Not needed
— only Text and TypedArray rows use this pattern, and both are pairs.
When a rejected `Promise` is instrumented in userspace while setting the
rejection reason in the wrong field (e.g. `error` instead of `reason`),
React will throw undefined (because `usable.reason` doesn't exist). This
makes it incredibly hard to find the actual rejection reason.
React is now throwing a generic error if we couldn't find the rejection
reason. That will produce a callstack pointing into the problematic
Promise from where you can hopefully extract the real rejection reason
(alongside fixing the bad instrumentation).
We're doing this in prod since this is unlikely to surface in dev. We're
only doing runtime type-checking for the rejected case. That should be
hit rarely and therefore hopefully have negligible runtime impact.
`ensureSuspendableThenableStateDEV` patches `then` in fulfilled
thenables to avoid triggering a custom thenable's `then` in an
unexpected state. However, we weren't doing the same for rejected
thenables.
This affected `ReactPromise`, the type used for thenables passed from
server to client. if a `ReactPromise` passed to `use` was pending during
the render but became rejected between the abort and
`pushSuspendedCallSiteOnComponentStack`, then `ReactPromise#then` would
crash. (see the added test for a reprouction)
This is because we were putting the ReactPromise into an invalid state:
a `PendingChunk` expects to have a `value: null | Array<...>`, but we
were deleting `value` altogether, and tgus hitting `TypeError: can't
access property "push" of undefined` here:
https://github.com/facebook/react/blob/32abdbca5a984c9d5e9940d3727fc420e2b118f7/packages/react-client/src/ReactFlightClient.js#L309
Bypassing the suspended thenable's `then` avoids this crash.
<!--
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
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->
- Adds a new react-flight-server-fb package providing RSC Flight
bindings for Meta's internal bundler stack
- Unlike webpack/turbopack integrations, this uses no manifest. Module
metadata is self-contained in ClientReference objects and sent over the
wire as-is
- Registers dom-browser-fb and dom-node-fb host configs for Rollup
builds targeting FB_WWW_DEV and FB_WWW_PROD
Key design differences from other bundler
- No build-time manifest
- Module IDs use Haste module names (e.g. `"MyComponent"`), with named
exports encoded as `"Module#export"`, rather than file paths resolved
through a manifest
- Client-side loading uses `Bootloader.handlePayload()` +
`JSResource().load()`
- `resolveClientReferenceMetadata` and `resolveClientReference` are
pass-throughs
## 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.
-->
E2E integration test is set up on Meta's internal system.
The prior fix for finishedTask reentrancy solved an observed failure.
This change adds a bit of defensive bookeeping to protect against other
theoretical reentrant task finishing that might fail in simlar ways but
where we don't have a clear demonstration of the bug.
It is possible for the fallback tasks from a Suspense boundary to
trigger an early `completeAll` call which is later repeated due to
`finishedTask` reentrancy. For node.js in particular this might be
problematic since we invoke a callback on each `completeAll` call but in
general it just isn't the right semantics since the call is running
slightly earlier than the completion of the last `finishedTask`
invocation. This change ensures that any reentrant `finishedTask` calls
(due to soft aborting fallback tasks) omit the `completeAll` call by
temporarily incrementing the total pending tasks.
<!--
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
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->
I just fixed typos as followings.
- `succesful` → `successful`
- `becuase` → `because`
- `enought` → `enough`
- `defualt` → `default`
## 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.
-->
This PR only includes test case description, dummy strings for test, and
comments updates, so it has no impact on runtime behavior.
Therefore, I manually reviewed changed texts to ensure correctness.
When `requireModule` triggers a reentrant `readChunk` on the same module
chunk, the reentrant call can fail and set `chunk.reason` to an error.
After the outer `requireModule` succeeds, the chunk transitions to
initialized but retains the stale error as `reason`.
When the Flight response stream later closes, it iterates all chunks and
expects `reason` on initialized chunks to be a `FlightStreamController`.
Since the stale `reason` is an `Error` object instead, calling
`chunk.reason.error()` crashes with `TypeError: chunk.reason.error is
not a function`.
The reentrancy can occur when module evaluation synchronously triggers
`readChunk` on the same chunk — for example, when code called during
evaluation tries to resolve the client reference for the module that is
currently being initialized. In Fizz SSR, `captureOwnerStack()` can
trigger this because it constructs component stacks that resolve lazy
client references via `readChunk`. The reentrant `requireModule` call
returns the module's namespace object, but since the module is still
being evaluated, accessing the export binding throws a TDZ (Temporal
Dead Zone) `ReferenceError`. This sets the chunk to the errored state,
and the `ReferenceError` becomes the stale `chunk.reason` after the
outer call succeeds.
This scenario is triggered in Next.js when a client module calls an
instrumented API like `Math.random()` in module scope, which
synchronously invokes `captureOwnerStack()`.
When flushing the shell, stylesheets with precedence are emitted in the
`<head>` which blocks paint regardless. Outlining a boundary solely
because it has suspensey CSS provides no benefit during the shell flush
and causes a higher-level fallback to be shown unnecessarily (e.g.
"Middle Fallback" instead of "Inner Fallback").
This change passes a flushingInShell flag to hasSuspenseyContent so the
host config can skip stylesheet-only suspensey content when flushing the
shell. Suspensey images (used for ViewTransition animation reveals)
still trigger outlining during the shell since their motivation is
different.
When flushing streamed completions the behavior is unchanged — suspensey
CSS still causes outlining so the parent content can display sooner
while the stylesheet loads.
When the Flight Client resolves chunk references during model parsing,
it calls `transferReferencedDebugInfo` to propagate debug info entries
from referenced chunks to the parent chunk. Debug info on chunks is
later moved to their resolved values, where it is used by React DevTools
to show performance tracks and what a component was suspended by.
Debug chunks themselves (specifically `ReactComponentInfo`,
`ReactAsyncInfo`, `ReactIOInfo`, and their outlined references) are
metadata that is never rendered. They don't need debug info attached to
them. Without this fix, debug info entries accumulate on outlined debug
chunks via their references to other debug chunks (e.g. owner chains and
props deduplication paths). Since each outlined chunk's accumulated
entries are copied to every chunk that references it, this creates
exponential growth in deep component trees, which can cause the dev
server to hang and run out of memory.
This generalizes the existing skip of `transferReferencedDebugInfo` for
Element owner/stack references (which already recognizes that references
to debug chunks don't need debug info transferred) to all references
resolved during debug info resolution. It adds an
`isInitializingDebugInfo` flag set in `initializeDebugChunk` and
`resolveIOInfo`, which propagates through all nested
`initializeModelChunk` calls within the same synchronous stack. For the
async path, `waitForReference` captures the flag at call time into
`InitializationReference.isDebug`, so deferred fulfillments also skip
the transfer.
Database libraries like Gel/EdgeDB can create very long linear chains of
async sequences through temporal async sequencing in connection pools.
The recursive traversal of `node.previous` chains in `visitAsyncNode`
causes stack overflow on these deep chains.
The fix converts the `previous` chain traversal from recursive to
iterative. We collect the chain into an array, then process from deepest
to shallowest.
The `awaited` traversal remains recursive since its depth is bounded by
promise dependency depth, not by the number of event loop turns. Each
`awaited` branch still benefits from the iterative `previous` handling
within its own traversal.
I've verified that this fixes the
[repro](https://github.com/jere-co/next-debug) provided in #35246.
closes#35246
Allows Server Components to import Context from a `"use client'` module
and render its Provider.
Only tricky part was that I needed to add `REACT_CONTEXT_TYPE` handling
in mountLazyComponent so lazy-resolved Context types can be rendered.
Previously only functions, REACT_FORWARD_REF_TYPE, and REACT_MEMO_TYPE
were handled.
Tested in the Flight fixture.
ty bb claude
Closes https://github.com/facebook/react/issues/35340
---------
Co-authored-by: Sophie Alpert <[email protected]>
Currently, IO that finished before the request started is not considered
IO:
https://github.com/facebook/react/blob/da33fa5028edd00cf9697b153efe2b3f5e3f643d/packages/react-server/src/ReactFlightServer.js#L5338-L5343
This leads to loss of debug info when a flight stream is deserialized
and serialized again.
We can solve this by allowing "when the the request started" to be set
to a point in the past, when the original stream started by doing
```js
const startTime = performance.now() + performance.timeOrigin
// ... stuff happens and time passes...
ReactServer.renderToReadableStream(..., { startTime })
```
This fixes security vulnerabilities in Server Functions.
---------
Co-authored-by: Sebastian Markbåge <[email protected]>
Co-authored-by: Josh Story <[email protected]>
Co-authored-by: Janka Uryga <[email protected]>
Co-authored-by: Sebastian Sebbie Silbermann <[email protected]>
When `renderModelDestructive` unwraps a lazy element and subsequently
calls `renderModelDestructive` again with the resolved model, we should
preserve the parent connection so that cyclic references can be
serialized properly. This can occur in an advanced scenario where the
result from the Flight Client is serialized again with the Flight
Server, e.g. for slicing a precomputed payload into multiple parts.
Note: The added test only fails when run with `--prod`. In dev mode, the
component info outlining prevents the issue from occurring.
Requires full error message in assert helpers.
Some of the error messages we asset on add a native javascript stack
trace, which would be a pain to add to the messages and maintain. This
PR allows you to just add `\n in <stack>` placeholder to the error
message to denote a native stack trace is present in the message.
---
Note: i vibe coded this so it was a pain to backtrack this to break this
into a stack, I tried and gave up, sorry.