Compare commits

...

1174 Commits

Author SHA1 Message Date
Joe Savona
330d28ebb5 [compiler] Enable validateExhaustiveMemoizationDependencies by default
Enables `@validateExhaustiveMemoizationDependencies` feature flag by default, and disables it in select tests that failed due to the change. Some of our tests intentionally use incorrect memo dependencies in order to test edge cases.
2025-11-25 12:07:42 -08:00
Joseph Savona
d39a1d6b63 [compiler] Distingush optional/extraneous deps (#35204)
In ValidateExhaustiveDependencies, I previously changed to allow
extraneous dependencies as long as they were non-reactive. Here we make
that more precise, and distinguish between values that are definitely
referenced in the memo function but optional as dependencies vs values
that are not even referenced in the memo function. The latter now error
as extraneous even if they're non-reactive. This also turned up a case
where constant-folded primitives could show up as false positives of the
latter category, so now we track manual deps which quality for constant
folding and don't error on them.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35204).
* #35213
* #35201
* __->__ #35204
2025-11-25 12:06:25 -08:00
Joseph Savona
16e16ec6ff [compiler] Script to enable a feature by default and update tests (#35202)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35202).
* #35201
* __->__ #35202
2025-11-24 12:21:35 -08:00
Joseph Savona
9599e7a787 [compiler] Adjustments to exhaustive deps messages, disable the lint rule (#35192)
Similar to ValidateHookUsage, we implement this check in the compiler
for safety but (for now) continue to rely on the existing rule for
actually reporting errors to users.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35192).
* #35201
* #35202
* __->__ #35192
2025-11-24 12:20:12 -08:00
Joseph Savona
67c1487ffd [compiler] Allow extraneous non-reactive locals (#35190)
The existing exhaustive-deps rule allows omitting non-reactive
dependencies, even if they're not memoized. Conceptually, if a value is
non-reactive then it cannot semantically change. Even if the value is a
new object, that object represents the exact same value and doesn't
necessitate redoing downstream computation. Thus its fine to exclude
nonreactive dependencies, whether they're a stable type or not.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35190).
* #35201
* #35202
* #35192
* __->__ #35190
2025-11-24 12:18:49 -08:00
Joseph Savona
454e01e603 [compiler] Allow manual dependencies to have different optionality than inferred deps (#35186)
Since adding this validation we've already changed our inference to use
knowledge from manual memoization to inform when values are frozen and
which values are non-nullable. To align with that, if the user chooses
to use different optionality btw the deps and the memo block/callback,
that's fine. The key is that eg `x?.y` will invalidate whenever `x.y`
would, so from a memoization correctness perspective its fine. It's not
our job to be a type checker: if a value is potentially nullable, it
should likely use a nullable property access in both places but
TypeScript/Flow can check that.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35186).
* #35201
* #35202
* #35192
* #35190
* __->__ #35186
2025-11-24 12:17:03 -08:00
Joseph Savona
c9a8cf3411 [compiler] Allow nonreactive stable types as extraneous deps (#35185)
When checking ValidateExhaustiveDeps internally, this seems to be the
most common case that it flags. The current exhaustive-deps rule allows
extraneous deps if they are a set of stable types. So here we reuse our
existing isStableType() util in the compiler to allow this case.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35185).
* #35201
* #35202
* #35192
* #35190
* #35186
* __->__ #35185
2025-11-24 12:15:06 -08:00
Joseph Savona
fca172e3f3 [compiler] Ignore ESLint suppressions when ValidateMemoDeps enabled (#35184)
With `ValidateExhaustiveMemoDependencies` we can now check exhaustive
dependencies for useMemo and useCallback within the compiler, without
relying on the separate exhaustive-deps rule. Until now we've bailed out
of any component/hook that suppresses this rule, since the suppression
_might_ affect a memoization value. Compiling code with incorrect memo
deps can change behavior so this wasn't safe. The downside was that a
suppression within a useEffect could prevent memoization, even though
non-exhaustive deps for effects do not cause problems for memoization
specifically.

So here, we change to ignore ESLint suppressions if we have both the
compiler's hooks validation and memo deps validations enabled.

Now we just have to test out the new validation and refine before we can
enable this by default.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35184).
* #35201
* #35202
* #35192
* #35190
* #35186
* #35185
* __->__ #35184
2025-11-24 12:12:49 -08:00
emily8rown
fd524fe02a [DevTools] hotkey to start/stop profiling (#35160)
## Summary

The built-in browser profiler supports starting/stopping with Cmd+E. For
Symmetry this adds the same hotkey for react devtools profiler.

## How did you test this change?
yarn build:\<browser name\> 
yarn run test:\<browser name\>

<img width="483" height="135" alt="Screenshot 2025-11-17 at 14 30 34"
src="https://github.com/user-attachments/assets/426939aa-15da-4c21-87a4-e949e6949482"
/>

firefox:

https://github.com/user-attachments/assets/6f225b90-828f-4e79-a364-59d6bc942f83

edge:

https://github.com/user-attachments/assets/5b2e9242-f0e8-481b-99a2-2dd78099f3ac

chrome:

https://github.com/user-attachments/assets/790aab02-2867-4499-aec1-e32e38c763f9

---------

Co-authored-by: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com>
2025-11-21 11:37:10 -05:00
Joseph Savona
40b4a5bf71 [compiler] ValidateExhaustiveDeps disallows unnecessary non-reactive deps (#34472)
Just to be consistent, we disallow unnecessary deps even if they're
known to be non-reactive.
2025-11-20 19:30:35 -08:00
Joseph Savona
df75af4edc [compiler] Auto-fix for non-exhaustive deps (#34471)
Records more information in DropManualMemoization so that we know the
full span of the manual dependencies array (if present). This allows
ValidateExhaustiveDeps to include a suggestion with the correct deps.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34471).
* #34472
* __->__ #34471
2025-11-20 19:28:08 -08:00
Joseph Savona
bcc3fd8b05 [compiler] Implement exhaustive dependency checking for manual memoization (#34394)
The compiler currently drops manual memoization and rewrites it using
its own inference. If the existing manual memo dependencies has missing
or extra dependencies, compilation can change behavior by running the
computation more often (if deps were missing) or less often (if there
were extra deps). We currently address this by relying on the developer
to use the ESLint plugin and have `eslint-disable-next-line
react-hooks/exhaustive-deps` suppressions in their code. If a
suppression exists, we skip compilation.

But not everyone is using the linter! Relying on the linter is also
imprecise since it forces us to bail out on exhaustive-deps checks that
only effect (ahem) effects — and while it isn't good to have incorrect
deps on effects, it isn't a problem for compilation.

So this PR is a rough sketch of validating manual memoization
dependencies in the compiler. Long-term we could use this to also check
effect deps and replace the ExhaustiveDeps lint rule, but for now I'm
focused specifically on manual memoization use-cases. If this works, we
can stop bailing out on ESLint suppressions, since the compiler will
implement all the appropriate checks (we already check rules of hooks).

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34394).
* #34472
* #34471
* __->__ #34394
2025-11-20 19:26:26 -08:00
Joseph Savona
50e7ec8a69 [compiler] Deprecate noEmit, add outputMode (#35112)
This deprecates the `noEmit: boolean` flag and adds `outputMode:
'client' | 'client-no-memo' | 'ssr' | 'lint'` as the replacement.
OutputMode defaults to null and takes precedence if specified, otherwise
we use 'client' mode for noEmit=false and 'lint' mode for noEmit=true.

Key points:
* Retrying failed compilation switches from 'client' mode to
'client-no-memo'
* Validations are enabled behind
Environment.proto.shouldEnableValidations, enabled for all modes except
'client-no-memo'. Similar for dropping manual memoization.
* OptimizeSSR is now gated by the outputMode==='ssr', not a feature flag
* Creation of reactive scopes, and related codegen logic, is now gated
by outputMode==='client'
2025-11-20 15:12:40 -08:00
Joseph Savona
4cf770d7e1 [compiler][poc] Quick experiment with SSR-optimization pass (#35102)
Just a quick poc:
* Inline useState when the initializer is known to not be a function.
The heuristic could be improved but will handle a large number of cases
already.
* Prune effects
* Prune useRef if the ref is unused, by pruning 'ref' props on primitive
components. Then DCE does the rest of the work - with a small change to
allow `useRef()` calls to be dropped since function calls aren't
normally eligible for dropping.
* Prune event handlers, by pruning props whose names start w "on" from
primitive components. Then DCE removes the functions themselves.

Per the fixture, this gets pretty far.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35102).
* #35112
* __->__ #35102
2025-11-20 15:02:38 -08:00
Jorge Cabiedes
7d67591041 [compiler] Remove useState argument constraint. no-derived-computations-in-effects (#35174)
Summary:
I missed this conditional messing things up for undefined useState()
calls. We should be tracking them.

I also missed a test that expect an error was not throwing.

Test Plan:
Update broken test

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35174).
* __->__ #35174
* #35173
2025-11-20 10:45:17 -08:00
Jorge Cabiedes
7ee974de92 [compiler] Prevent innaccurate derivation recording on FunctionExpressions on no-derived-computation-in-effects (#35173)
Summary:
The operands of a function expression are the elements passed as
context. This means that it doesn't make sense to record mutations for
them.

The relevant mutations will happen in the function body, so we need to
prevent FunctionExpression type instruction from running the logic for
effect mutations.

This was also causing some values to depend on themselves in some cases
triggering an infinite loop. Also added n invariant to prevent this
issue

Test Plan:
Added fixture test

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35173).
* #35174
* __->__ #35173
2025-11-20 10:44:45 -08:00
Sebastian "Sebbie" Silbermann
8ac5f4eb36 Fix form status reset when component state is updated (#34075)
Co-authored-by: Vordgi <sasha2822222@gmail.com>
2025-11-19 18:22:07 +01:00
Sebastian Markbåge
eb89912ee5 Add expertimental optimisticKey behind a flag (#35162)
When dealing with optimistic state, a common problem is not knowing the
id of the thing we're waiting on. Items in lists need keys (and single
items should often have keys too to reset their state). As a result you
have to generate fake keys. It's a pain to manage those and when the
real item comes in, you often end up rendering that with a different
`key` which resets the state of the component tree. That in turns works
against the grain of React and a lot of negatives fall out of it.

This adds a special `optimisticKey` symbol that can be used in place of
a `string` key.

```js
import {optimisticKey} from 'react';
...
const [optimisticItems, setOptimisticItems] = useOptimistic([]);
const children = savedItems.concat(
  optimisticItems.map(item =>
    <Item key={optimisticKey} item={item} />
  )
);
return <div>{children}</div>;
```

The semantics of this `optimisticKey` is that the assumption is that the
newly saved item will be rendered in the same slot as the previous
optimistic items. State is transferred into whatever real key ends up in
the same slot.

This might lead to some incorrect transferring of state in some cases
where things don't end up lining up - but it's worth it for simplicity
in many cases since dealing with true matching of optimistic state is
often very complex for something that only lasts a blink of an eye.

If a new item matches a `key` elsewhere in the set, then that's favored
over reconciling against the old slot.

One quirk with the current algorithm is if the `savedItems` has items
removed, then the slots won't line up by index anymore and will be
skewed. We might be able to add something where the optimistic set is
always reconciled against the end. However, it's probably better to just
assume that the set will line up perfectly and otherwise it's just best
effort that can lead to weird artifacts.

An `optimisticKey` will match itself for updates to the same slot, but
it will not match any existing slot that is not an `optimisticKey`. So
it's not an `any`, which I originally called it, because it doesn't
match existing real keys against new optimistic keys. Only one
direction.
2025-11-18 16:29:18 -05:00
Ricky
0972e23908 [compiler] Consider setter from useOptimistic non-reactive (#35141)
Closes https://github.com/facebook/react/issues/35138
2025-11-18 10:50:43 -05:00
Sebastian "Sebbie" Silbermann
194c12d949 [DevTools] Name root "Transition" when focusing on Activity (#35108) 2025-11-18 10:16:58 +01:00
Sebastian "Sebbie" Silbermann
7f1a085b28 [DevTools] Show list of named Activities in Suspense tab (#35092) 2025-11-18 09:52:44 +01:00
Joseph Savona
ea4899e13f [compiler][snap] Support pattern of files to test as CLI argument (#35148)
I've been trying out LLM agents for compiler development, and one thing
i found is that the agent naturally wants to run `yarn snap <pattern>`
to test a specific fixture, and I want to be able to tell it (directly
or in rules/skills) to do this in order to get the debug output from all
the compiler passes. Agents can figure out our current testfilter.txt
file system but that's just tedious. So here we add support for `yarn
snap -p <pattern>`. If you pass in a pattern with an extension, we
target that extension specifically. If you pass in a .expect.md file, we
look at that specific fixture. And if the pattern doesn't have
extensions, we search for `<pattern>{.js,.jsx,.ts,.tsx}`. When patterns
are enabled we automatically log as in debug mode (if there is a single
match), and disable watch mode.

Open to feedback!
2025-11-17 12:09:09 -08:00
Joseph Savona
b946a249b5 [compiler] Improve setState-in-effects rule to account for ref-gated conditionals (#35147)
Conditionally calling setState in an effect is sometimes necessary, but
should generally follow the pattern of using a "previous vaue" ref to
manually compare and ensure that the setState is idempotent. See fixture
for an example.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35147).
* #35148
* __->__ #35147
2025-11-17 12:07:43 -08:00
Joseph Savona
d6b1a0573b [compiler] Extract reusable logic for control dominators (#35146)
The next PR needs to check if a block is controlled by a value derived
from a ref.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35146).
* #35148
* #35147
* __->__ #35146
2025-11-17 12:05:52 -08:00
Joseph Savona
b315a0f713 [compiler] Fix for destructuring with mixed declaration/reassignment (#35144)
Destructing statements that start off as declarations can end up
becoming reassignments if the variable is a scope declaration, so we
have existing logic to handle cases where some parts of a destructure
need to be converted into new locals, with a reassignment to the hoisted
scope variable afterwards. However, there is an edge case where all of
the values are reassigned, in which case we don't need to rewrite and
can just set the instruction kind to reassign.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35144).
* #35148
* #35147
* #35146
* __->__ #35144
2025-11-17 11:34:49 -08:00
Sebastian "Sebbie" Silbermann
7df96b0c1a [Flight] Complete list of Node.js' internal Promise awaits (#35161) 2025-11-17 19:28:26 +01:00
Sebastian "Sebbie" Silbermann
45bc3c9f04 [Flight] Reduce risk of maximum call stack exceeded when emitting async sequence (#35159) 2025-11-17 18:54:13 +01:00
Hendrik Liebau
fb2177c153 [Flight] Fix pending chunks count for streams & async iterables in DEV (#35143)
In DEV, we need to prevent the response from being GC'd while there are
still pending chunks for ReadableSteams or pending results for
AsyncIterables.

Co-authored-by: Sebastian "Sebbie" Silbermann <silbermann.sebastian@gmail.com>
2025-11-14 23:52:11 +01:00
Joseph Savona
647e13366c [compiler] fix bad rebase from sapling (#35145)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35145).
* #35144
* __->__ #35145
2025-11-14 14:50:38 -08:00
Joseph Savona
19b769fa5f [compiler] Fix for inferring props-derived-value as mutable (#35140)
Fix for the repro from the previous PR. A `Capture x -> y` effect should
downgrade to `ImmutableCapture` when the source value is maybe-frozen.
MaybeFrozen represents the union of a frozen value with a non-frozen
value.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35140).
* __->__ #35140
* #35139
2025-11-14 12:14:34 -08:00
Joseph Savona
dbf2538355 [compiler] Repro for false positive mutation of a value derived from props (#35139)
Repro from the compiler WG (Thanks Cody!) of a case where the compiler
incorrectly thinks a value is mutable.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35139).
* #35140
* __->__ #35139
2025-11-14 12:14:23 -08:00
Eliot Pontarelli
21f282425c [compiler] Allow ref access in callbacks passed to event handler props (#35062)
## Summary

Fixes #35040. The React compiler incorrectly flags ref access within
event handlers as ref access at render time. For example, this code
would fail to compile with error "Cannot access refs during render":

```tsx
  const onSubmit = async (data) => {
    const file = ref.current?.toFile(); // Incorrectly flagged as error
  };

  <form onSubmit={handleSubmit(onSubmit)}>
```
This is a false positive because any built-in DOM event handler is
guaranteed not to run at render time. This PR only supports built-in
event handlers because there are no guarantees that user-made event
handlers will not run at render time.

## How did you test this change?

I created 4 test fixtures which validate this change:
* allow-ref-access-in-event-handler-wrapper.tsx - Sync handler test
input
* allow-ref-access-in-event-handler-wrapper.expect.md - Sync handler
expected output
* allow-ref-access-in-async-event-handler-wrapper.tsx - Async handler
test input
* allow-ref-access-in-async-event-handler-wrapper.expect.md - Async
handler expected output

All linters and test suites also pass.
2025-11-14 09:00:33 -08:00
Jorge Cabiedes
257b033fc7 [Compiler] Avoid capturing global setStates for no-derived-computations lint (#35135)
Summary:
This only matters when enableTreatSetIdentifiersAsStateSetters=true

This pattern is still bad. But Right now the validation can only
recommend to move stuff to "calculate in render"

A global setState should not be moved to render, not even conditionally
and you can't remove state without crossing Component boundaries, which
makes this a different kind of fix.

So while we are only suggesting "calculate in render" as a fix we should
disallow the lint from throwing in this case IMO

Test Plan:
Added a fixture

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35135).
* __->__ #35135
* #35134
2025-11-13 22:56:06 -08:00
Jorge Cabiedes
de97ef9ad5 [Compiler] Don't count a setState in the dependency array of the effect it is called on as a usage (#35134)
Summary:
The validation only allows setState declaration as a usage outside of
the effect.

Another edge case is that if you add the setState being validated in the
dependency array you also make the validation opt out since it counts as
a usage outside of the effect.

Added a bit of logic to consider the effect's deps when creating the
cache for setState usages within the effect

Test Plan:
Added a fixture

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35134).
* #35135
* __->__ #35134
2025-11-13 22:52:23 -08:00
Hendrik Liebau
93fc57400b [Flight] Fix broken byte stream parsing caused by buffer detachment (#35127)
This PR fixes a critical bug where `ReadableStream({type: 'bytes'})`
instances passed through React Server Components (RSC) would stall after
reading only the first chunk or the first few chunks in the client. This
issue was masked by using `web-streams-polyfill` in tests, but manifests
with native Web Streams implementations.

The root cause is that when a chunk is enqueued to a
`ReadableByteStreamController`, the spec requires the underlying
ArrayBuffer to be synchronously transferred/detached. In the React
Flight Client's chunk parsing, embedded byte stream chunks are created
as views into the incoming RSC stream chunk buffer using `new
Uint8Array(chunk.buffer, offset, length)`. When embedded byte stream
chunks are enqueued, they can detach the shared buffer, leaving the RSC
stream parsing in a broken state.

The fix is to copy embedded byte stream chunks before enqueueing them,
preventing buffer detachment from affecting subsequent parsing. To not
affect performance too much, we use a zero-copy optimization: when a
chunk ends exactly at the end of the RSC stream chunk, or when the row
spans into the next RSC chunk, no further parsing will access that
buffer, so we can safely enqueue the view directly without copying.

We now also enqueue embedded byte stream chunks immediately as they are
parsed, without waiting for the full row to complete.

To simplify the logic in the client, we introduce a new `'b'` protocol
tag specifically for byte stream chunks. The server now emits `'b'`
instead of `'o'` for `Uint8Array` chunks from byte streams (detected via
`supportsBYOB`). This allows the client to recognize byte stream chunks
without needing to track stream IDs.

Tests now use the proper Jest environment with native Web Streams
instead of polyfills, exposing and validating the fix for this issue.
2025-11-13 21:23:02 +01:00
Sebastian "Sebbie" Silbermann
093b3246e1 [react-dom] Batch updates from resize until next frame (#35117) 2025-11-13 13:30:21 +01:00
Nathan
3a495ae722 [compiler] source location validator (#35109)
@josephsavona this was briefly discussed in an old thread, lmk your
thoughts on the approach. I have some fixes ready as well but wanted to
get this test case in first... there's some things I don't _love_ about
this approach, but end of the day it's just a tool for the test suite
rather than something for end user folks so even if it does a 70% good
enough job that's fine.

### refresher on the problem
when we generate coverage reports with jest (istanbul), our coverage
ends up completely out of whack due to the AST missing a ton of (let's
call them "important") source locations after the compiler pipeline has
run.

At the moment to get around this, we've been doing something a bit
unorthodox and also running our test suite with istanbul running before
the compiler -- which results in its own set of issues (for eg, things
being memoized differently, or the compiler completely bailing out on
the instrumented code, etc).

before getting in fixes, I wanted to set up a test case to start
chipping away on as you had recommended.

### how it works

The validator basically:
1. Traverses the original AST and collects the source locations for some
"important" node types
- (excludes useMemo/useCallback calls, as those are stripped out by the
compiler)
3. Traverses the generated AST and looks for nodes with matching source
locations.
4. Generates errors for source locations missing nodes in the generated
AST

### caveats/drawbacks

There are some things that don't work super well with this approach. A
more natural test fit I think would be just having some explicit
assertions made against an AST in a test file, as you can just bake all
of the assumptions/nuance in there that are difficult to handle in a
generic manner. However, this is maybe "good enough" for now.

1. Have to be careful what you put into the test fixture. If you put in
some code that the compiler just removes (for eg, a variable assignment
that is unused), you're creating a failure case that's impossible to
fix. I added a skip for useMemo/useCallback.
2. "Important" locations must exactly match for validation to pass.
- Might get tricky making sure things are mapped correctly when a node
type is completely changed, for eg, when a block statement arrow
function body gets turned into an implicit return via the body just
being an expression/identifier.
- This can/could result in scenarios where more changes are needed to
shuttle the locations through due to HIR not having a 1:1 mapping all
the babel nuances, even if some combination of other data might be good
enough even if not 10000% accurate. This might be the _right_ thing
anyways so we don't end up with edge cases having incorrect source
locations.
2025-11-12 19:02:46 -08:00
Ricky
bbe3f4d322 [flags] disableLegacyMode in native-fb (#35120)
this is failing test too because we need the legacy mode in the react
package until we fix the tests
2025-11-12 15:38:58 -05:00
Sebastian "Sebbie" Silbermann
1ea46df8ba [DevTools] Batch updates when updating component filters (#35093) 2025-11-11 23:20:22 +01:00
Sebastian "Sebbie" Silbermann
8c15edd57c [DevTools] Send root unmount as a regular removal operation (#35107) 2025-11-11 23:08:54 +01:00
Jorge Cabiedes
5e94655cbb [compiler] _exp version of ValidateNoDerivedComputationsInEffects take precedence over stable version when enabled (#35099)
Summary:
We should only run one version of the validation. I think it makes sense
that if the exp version is enable it takes precedence over the stable
one

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35099).
* __->__ #35099
* #35100
2025-11-11 10:16:20 -08:00
Jorge Cabiedes
db8273c12f [compiler] Update test snap to include fixture comment (#35100)
Summary:
I missed this test case failing and now having @loggerTestOnly after
landing some other PRs good to know they're not land blocking

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35100).
* #35099
* __->__ #35100
2025-11-11 10:16:04 -08:00
Ricky
04ee54cd12 [tests] add more portal activity tests (#35095)
I copied some tests from
[`Activity-test.js`](1d68bce19c/packages/react-reconciler/src/__tests__/Activity-test.js)
and made them portal specific just to confirm my understanding of how
Portals + Activity interact is correct. Seems good to include them.
2025-11-11 12:47:56 -05:00
Jorge Cabiedes
100fc4a8cf [compiler] Prevent local state source variables from depending on other state (#35044)
Summary:
When a local state is created sometimes it uses a `prop` or even other
local state for its initial value.

This value is only relevant on first render so we shouldn't consider it
part of our data flow

Test Plan:
Added tests
2025-11-10 12:29:34 -08:00
Jorge Cabiedes
92ac4e8b80 [compiler] Don't validate when effect cleanup function depends on effect localized setState state derived values (#35020)
Summary:
If we are using a clean up function in an effect and that clean up
function depends on a value that is used to set the state we are
validating for we shouldn't throw an error since it is a valid use case
for an effect.

Test Plan:
added test

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35020).
* #35044
* __->__ #35020
2025-11-10 12:28:19 -08:00
Jorge Cabiedes
f76c3617e0 [compiler] Switch to track setStates by aliasing and id instead of identifier names (#34973)
Summary:
This makes the setState usage logic much more robust. We no longer rely
on identifierName.

Now we track when a setState is loaded into a new promoted identifier
variable and track this in a map `setStateLoaded` map.

For other types of instructions we consider the setState to be being
used. In this case we record its usage into the `setStateUsages` map.



Test Plan:
We expect no changes in behavior for the current tests

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34973).
* #35044
* #35020
* __->__ #34973
* #34972
2025-11-10 12:16:27 -08:00
Jorge Cabiedes
7296120396 [compiler] Update ValidateNoDerivedComputationsInEffects_exp to log the error instead of throwing (#34972)
Summary:
TSIA

Simple change to log errors in Pipeline.ts instead of throwing in the
validation

Test Plan:
updated snap tests

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34972).
* #35044
* #35020
* #34973
* __->__ #34972
2025-11-10 12:16:13 -08:00
Jorge Cabiedes
6347c6d373 [compiler] Fix false negatives and add data flow tree to compiler error for no-deriving-state-in-effects (#34995)
Summary:
Revamped the derivationCache graph.

This fixes a bunch of bugs where sometimes we fail to track from which
props/state we derived values from.

Also, it is more intuitive and allows us to easily implement a Data Flow
Tree.

We can print this tree which gives insight on how the data is derived
and should facilitate error resolution in complicated components

Test Plan:
Added a test case where we were failing to track derivations. Also
updated the test cases with the new error containing the data flow tree

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34995).
* #35044
* #35020
* #34973
* #34972
* __->__ #34995
* #34967
2025-11-10 12:09:13 -08:00
Jorge Cabiedes
01fb328632 [compiler] Prevent overriding a derivationEntry on effect mutation and instead update typeOfValue and fix infinite loops (#34967)
Summary:
With this we are now comparing a snapshot of the derivationCache with
the new changes every time we are done recording the derivations
happening in the HIR.

We have to do this after recording everything since we still do some
mutations on the cache when recording mutations.



Test Plan:
Test the following in playground:
```
// @validateNoDerivedComputationsInEffects_exp

function Component({ value }) {
  const [checked, setChecked] = useState('');

  useEffect(() => {
    setChecked(value === '' ? [] : value.split(','));
  }, [value]);

  return (
    <div>{checked}</div>
  )
}
```

This no longer causes an infinite loop.

Added a test case in the next PR in the stack

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34967).
* #35044
* #35020
* #34973
* #34972
* #34995
* __->__ #34967
2025-11-10 12:08:05 -08:00
Sebastian "Sebbie" Silbermann
ce4054ebdd [DevTools] Measure when reconnecting Suspense (#35098) 2025-11-10 20:55:31 +01:00
Sebastian "Sebbie" Silbermann
21c1d51acb [DevTools] Don't attempt to draw bounding box if inspected element is not a Suspense (#35097) 2025-11-10 20:01:59 +01:00
Facebook Community Bot
be48396dbd Remove Dead Code in WWW JS
Differential Revision: D86593830

Pull Request resolved: https://github.com/facebook/react/pull/35085
2025-11-10 16:34:01 +00:00
Andrew Clark
5268492536 Fix: Activity should hide portal contents (#35091)
This PR updates the behavior of Activity so that when it is hidden, it
hides the contents of any portals contained within it.

Previously we had intentionally chosen not to implement this behavior,
because it was thought that this concern should be left to the userspace
code that manages the portal, e.g. by adding or removing the portal
container from the DOM. Depending on the use case for the portal, this
is often desirable anyway because the portal container itself is not
controlled by React.

However, React does own the _contents_ of the portal, and we can hide
those elements regardless of what the user chooses to do with the
container. This makes the hiding/unhiding behavior of portals with
Activity automatic in the majority of cases, and also benefits from
aligning the DOM mutations with the rest of the React's commit phase
lifecycle.

The reason we have to special case this at all is because usually we
only hide the direct DOM children of the Activity boundary. There's no
reason to go deeper than that, because hiding a parent DOM element
effectively hides everything inside of it. Portals are the exception,
because they don't exist in the normal DOM hierarchy; we can't assume
that just because a portal has a parent in the React tree that it will
also have that parent in the actual DOM.

So, whenever an Activity boundary is hidden, we must search for and hide
_any_ portal that is contained within it, and recursively hide its
direct children, too.

To optimize this search, we use a new subtree flag, PortalStatic, that
is set only on fiber paths that contain a HostPortal. This lets us skip
over any subtree that does not contain a portal.
2025-11-10 10:42:26 -05:00
Sebastian Markbåge
c83be7da9f [Fizz] Simplify createSuspenseBoundary path (#35087)
Small follow up to #35068.

Since this is now a single argument we can simplify the creation
branching a bit and make sure it's const.
2025-11-09 15:19:43 -05:00
Sebastian Markbåge
6362b5c711 [DevTools] Special case the selected root outline (#35071)
When I moved the outline to above all other rects, I thought it was
clever to unify with the root so that the outline was also used for the
root selection. But the root outline is not drawn like the other rects.
It's outside the padding and doesn't have the 1px adjustment which leads
the overlay to be slightly inside the other rect instead of above it.

This goes back to just having the selected root be drawn by the root
element.

Before:

<img width="652" height="253" alt="Screenshot 2025-11-07 at 11 39 28 AM"
src="https://github.com/user-attachments/assets/334237d1-f190-4995-94cc-9690ec0f7ce1"
/>

After:

<img width="674" height="220" alt="Screenshot 2025-11-07 at 11 44 01 AM"
src="https://github.com/user-attachments/assets/afaa86d8-942a-44d8-a1a5-67c7fb642c0d"
/>
2025-11-09 15:03:31 -05:00
Sebastian "Sebbie" Silbermann
5a9921b839 [DevTools] Apply Activity slice filter when double clicking Activity (#34908) 2025-11-08 18:09:44 +01:00
Andrew Clark
717e70843e Fix: Errors should not escape a hidden Activity (#35074)
If an error is thrown inside a hidden Activity, it should not escape
into the visible part of the UI. Conceptually, a hidden Activity
boundary is not part of the current UI; it's the same as an unmounted
tree, except for the fact that the state will be restored if it's later
revealed.

Fixes:
- https://github.com/facebook/react/issues/35073
2025-11-07 18:18:24 -05:00
Stian Jensen
a10ff9c857 Upgrade devtools dependency update-notifier to 5.x (#31655)
## Summary

This PR upgrades the dependency on update-notifier, used in
react-devtools, to 5.x
This is the latest non-ESM version, so upgrading to it should be
unproblematic (while updating to 6.x and beyond will have to wait).

Upgrading means we avoid installing a lot of outdated dependencies (as
can be seen from the diff in yarn.lock), and resolves part of
https://github.com/facebook/react/issues/28058

Changelog:
https://github.com/yeoman/update-notifier/releases

The most relevant breaking change seems to be that the minimum support
node version is increased from v6 to v10, but I couldn't find what is
currently React's official node version support.

## How did you test this change?

I ran the test-suite locally (`yarn test` in root folder), but I'm not
sure if that one actually covers devtools?

I also built and tested this version of devtools with some internal
company projects (both react and react-native based) – following
guidelines from
https://github.com/facebook/react/issues/28058#issuecomment-1943619292.
2025-11-07 18:43:42 +00:00
Sebastian Markbåge
fa50caf5f8 [Fizz] Unify preamble only fields to save a field (#35068)
Stacked on #35067.

Same idea of saving a field on the SuspenseBoundary in the common case.
The case where they can have a preamble is rare.
2025-11-07 09:19:19 -05:00
Sebastian Markbåge
1e986f514f [Fizz] Unify prerender only fields to save a field (#35067)
I need to regain a field because the SuspenseBoundary type is already at
16 fields in prod, after which it deopts v8.

There are two fields that are only used in prerender to track postpones.
These are ripe to be split into an optional object so that they only
take up one field when they're not used.
2025-11-07 09:18:28 -05:00
Sebastian Markbåge
38bdda1ca6 Don't skip content in visible offscreen trees for Gesture View Transitions (#35066)
Follow up to #35063.

I forgot there's another variant of this in the ApplyGesture path.
2025-11-06 20:59:08 -05:00
Jack Pope
a44e750e87 Store instance handles in an internal map behind flag (#35053)
We already append `randomKey` to each handle name to prevent external
libraries from accessing and relying on these internals. But more
libraries recently have been getting around this by simply iterating
over the element properties and using a `startsWith` check.

This flag allows us to experiment with moving these handles to an
internal map.

This PR starts with the two most common internals, the props object and
the fiber. We can consider moving additional properties such as the
container root and others depending on perf results.
2025-11-06 18:17:53 -05:00
Sebastian Markbåge
37b089a59c Don't skip content in visible offscreen trees for View Transitions (#35063)
Also, don't not skip hidden trees.

Memoized state is null when an Offscreen boundary (Suspense or Activity)
is visible.

This logic was inversed in a couple of View Transition checks which
caused pairs to be discovered or not discovered incorrectly for
insertion and deletion of Suspense or Activity boundaries.
2025-11-06 16:03:02 -05:00
Sebastian Markbåge
1a31a814f1 Escape View Transition Name Strings as base64 (#35060)
This is an alternative to #35059.

If the name needs escaping, then instead of escaping it, we just use a
base64 name. This wouldn't allow you to match on an escaped name in your
own CSS like you should be able to if browsers worked properly. But at
least it would provide matching name in current browsers which is
probably sufficient if you're using auto-generated names.

This also covers some cases where `CSS.escape()` isn't sufficient anyway
like when the name ends in a dot.
2025-11-06 16:02:06 -05:00
Alexander Kachkaev
5a2205ba28 Update bug report template for eslint plugin label (#34959)
## Summary

When creating https://github.com/facebook/react/issues/34957, I noticed
a reference to `eslint-plugin-react-compiler` instead of
`eslint-plugin-react-hooks`. Since the former is merged into the latter
(https://github.com/facebook/react/pull/32416,
https://github.com/facebook/react/pull/34228), I have decided to update
the issue template to avoid confusion.
2025-11-05 16:57:26 -05:00
Sebastian Markbåge
fa767dade6 Remove unstable_expectedLoadTime option (#35051)
Follow up to #35022.

It's now replaced by the `defer` option.

Sounds like nobody is actually using this option, including Meta, so we
can just delete it.
2025-11-05 15:52:21 -05:00
Sebastian Markbåge
0ba2f01f74 Rename <Suspense unstable_expectedLoadTime> to <Suspense defer> and implement in SSR (#35022)
We've long had the CPU suspense feature behind a flag under the terrible
API `unstable_expectedLoadTime={arbitraryNumber}`. We've known for a
long time we want it to just be `defer={true}` (or just `<Suspense
defer>` in the short hand syntax). So this adds the new name and warns
for the old name.

For only the new name, I also implemented SSR semantics in Fizz. It has
two effects here.
1) It renders the fallback before the content (similar to prerender)
allowing siblings to complete quicker.
2) It always outlines the result. When streaming this should really
happen naturally but if you defer a prerendered content it also implies
that it's expensive and should be outlined. It gives you a opt-in to
outlining similar to suspensey images and css but let you control it
manually.
2025-11-05 14:12:09 -05:00
Sebastian Markbåge
dd048c3b2d Clean up enablePostpone Experiment (#35048)
We're not shipping this and it's a lot of code to maintain that is
blocking my refactor of Fizz for SuspenseList.
2025-11-05 00:05:59 -05:00
Sebastian Markbåge
c308cb5905 Disable enablePostpone flag in experimental (#31042)
I don't think we're ready to land this yet since we're using it to run
other experiments and our tests. I'm opening this PR to indicate intent
to disable and to ensure tests in other combinations still work. Such as
enableHalt without enablePostpone. I think we'll also need to rewrite
some tests that depend on enablePostpone to preserve some coverage.

The conclusion after this experiment is that try/catch around these are
too likely to block these signals and consider them error. Throwing
works for Hooks and `use()` because the lint rule can ensure that
they're not wrapped in try/catch. Throwing in arbitrary functions not
quite ecosystem compatible. It's also why there's `use()` and not just
throwing a Promise. This might also affect the Catch proposal.

The "prerender" for SSR that's supporting "Partial Prerendering" is
still there. This just disables the `React.postpone()` API for creating
the holes.
2025-11-04 23:23:25 -05:00
Sebastian Markbåge
986323f8c6 [Fiber] SuspenseList with "hidden" tail row should "catch" suspense (#35042)
Normally if you suspend in a SuspenseList row above a Suspense boundary
in that row, it'll suspend the parent. Which can itself delay the commit
or resuspend a parent boundary. That's because SuspenseList mostly just
coordinates the state of the inner boundaries and isn't a boundary
itself.

However, for tail "hidden" and "collapsed" this is not quite the case
because the rows themselves can avoid being rendered.

In the case of "collapsed" we require at least one Suspense boundary
above to have successfully rendered before committing the list because
the idea of this mode is that you should at least always show some
indicator that things are still loading. Since we'd never try the next
one after that at all, this just works. Expect there was an unrelated
bug that meant that "suspend with delay" on a Retry didn't suspend the
commit. This caused a scenario were it'd allow a commit proceed when it
shouldn't. So I fixed that too. The counter intuitive thing here is that
we won't actually show a previous completed row if the loading state of
the next row is still loading.

For tail "hidden" it's a little different because we don't actually
require any loading indicator at all to be shown while it's loading. If
we attempt a row and it suspends, we can just hide it (and the rest) and
move to commit. Therefore this implements a path where if all the rest
of the tail are new mounts (we wouldn't be required to unmount any
existing boundaries) then we can treat the SuspenseList boundary itself
as "catching" the suspense. This is more coherent semantics since any
future row that we didn't attempt also wouldn't resuspend the parent.

This allows simple cases like `<SuspenseList>{list}</SuspenseList>` to
stream in each row without any indicator and no need for Suspense
boundaries.
2025-11-04 22:11:33 -05:00
Jordan Brown
8f8b336734 [eslint] Fix useEffectEvent checks in component syntax (#35041)
We were not recording uEE calls in component/hook syntax. Easy fix.

Added tests matching function component syntax for component syntax +
added one for hooks
2025-11-04 14:59:29 -05:00
Alex Hunt
d000261eef [Tracks] Annotate devtools.performanceIssue for Cascading Updates in DEV (#34961) 2025-11-04 17:07:31 +00:00
Hendrik Liebau
f646e8ffd8 [Flight] Fix hasReadable flag in Node.js clients' debug channel (#35039)
For Edge Flight servers, that use Web Streams, we're defining the
`debugChannel` option as:

```
debugChannel?: {readable?: ReadableStream, writable?: WritableStream, ...}
```

Whereas for Node.js Flight servers, that use Node.js Streams, we're
defining it as:

```
debugChannel?: Readable | Writable | Duplex | WebSocket
```

For the Edge Flight clients, there is currently only one direction of
the debug channel supported, so we define the option as:

```
debugChannel?: {readable?: ReadableStream, ...}
```

Consequently, for the Node.js Flight clients, we define the option as:

```
debugChannel?: Readable
```

The presence of a readable debug channel is passed to the Flight client
internally via the `hasReadable` flag on the internal `debugChannel`
option. For the Node.js clients, that flag was accidentally derived from
the public option `debugChannel.readable`, which is conceptually
incorrect, because `debugChannel` is a `Readable` stream, not an options
object with a `readable` property. However, a `Readable` also has a
`readable` property, which is a boolean that indicates whether the
stream is in a readable state. This meant that the `hasReadable` flag
was incidentally still set correctly. Regardless, this was confusing and
unintentional, so we're now fixing it to always set `hasReadable` to
`true` when a `debugChannel` is provided to the Node.js clients. We'll
revisit this in case we ever add support for writable debug channels in
Node.js (and Edge) clients.
2025-11-04 16:30:08 +01:00
Jack Pope
edd05f181b Add fragment handles to children of FragmentInstances (#34935)
This PR adds a `unstable_reactFragments?: Set<FragmentInstance>`
property to DOM nodes that belong to a Fragment with a ref (top level
host components). This allows you to access a FragmentInstance from a
DOM node.

This is flagged behind `enableFragmentRefsInstanceHandles`.

The primary use case to unblock is reusing IntersectionObserver
instances. A fairly common practice is to cache and reuse
IntersectionObservers that share the same config, with a map of
node->callbacks to run for each entry in the IO callback. Currently this
is not possible with Fragment Ref `observeUsing` because the key in the
cache would have to be the `FragmentInstance` and you can't find it
without a handle from the node. This works now by accessing
`entry.target.fragments`.

This also opens up possibilities to use `FragmentInstance` operations in
other places, such as events. We can do
`event.target.unstable_reactFragments`, then access
`fragmentInstance.getClientRects` for example. In a future PR, we can
assign an event's `currentTarget` as the Fragment Ref for a more direct
handle when the event has been dispatched by the Fragment itself.

The first commit here implemented a handle only on observed elements.
This is awkward because there isn't a good way to document or expose
this temporary property. `element.fragments` is closer to what we would
expect from a DOM API if a standard was implemented here. And by
assigning it to all top-level nodes of a Fragment, it can be used beyond
the cached IntersectionObserver callback.

One tradeoff here is adding extra work during the creation of
FragmentInstances as well as keeping track of adding/removing nodes.
Previously we only track the Fiber on creation but here we add a
traversal which could apply to a large set of top-level host children.
The `element.unstable_reactFragments` Set can also be randomly ordered.
2025-11-03 17:51:00 -05:00
Hendrik Liebau
67f7d47a9b [Flight] Fix debug info filtering to include later resolved I/O (#35036)
In #35019, we excluded debug I/O info from being considered for
enhancing the owner stack if it resolved after the defined `endTime`
option that can be passed to the Flight client. However, we should
include any I/O that was awaited before that end time, even if it
resolved later.
2025-11-03 22:59:40 +01:00
Hendrik Liebau
561ee24d4a [Fizz] Push halted await to the owner stack for late-arriving I/O info (#35019) 2025-11-01 16:03:09 +01:00
Sebastian Markbåge
488d88b018 Render children passed to "backwards" SuspenseList in reverse mount order (#35021)
Stacked on #35018.

This mounts the children of SuspenseList backwards. Meaning the first
child is mounted last in the DOM (and effect list). It's like calling
reverse() on the children.

This is meant to set us up for allowing AsyncIterable children where the
unknown number of children streams in at the end (which is the beginning
in a backwards SuspenseList). For consistency we do that with other
children too.

`unstable_legacy-backwards` still exists for the old mode but is meant
to be deprecated.

<img width="100" alt="image"
src="https://github.com/user-attachments/assets/5c2a95d7-34c4-4a4e-b602-3646a834d779"
/>
2025-10-31 13:33:23 -04:00
Sebastian Markbåge
26cf280480 Switch the default revealOrder to "forwards" and tail "hidden" on SuspenseList (#35018)
We have warned about this for a while now so we can make the switch.

Often when you reach for SuspenseList, you mean forwards. It doesn't
make sense to have the default to just be a noop. While "together" is
another useful mode that's more like a Group so isn't so associated with
the default as List. So we're switching it.

However, tail=hidden isn't as obvious of a default it does allow for a
convenient pattern for streaming in list of items by default.

This doesn't yet switch the rendering order of "backwards". That's
coming in a follow up.
2025-10-31 12:58:18 -04:00
Sebastian "Sebbie" Silbermann
c9ddee7e36 [DevTools] Reset forced states when changing component filters (#34929) 2025-10-31 12:57:11 +01:00
Sebastian Markbåge
6fb7754494 [DevTools] Render selected outline on top of every other rect (#35012)
When rects are close together (or overlapping) the outline can end up
being covered up by sibling rects or deeper rects. This renders the
selected outline on top of everything so it's always visible.

<img width="275" height="730" alt="Screenshot 2025-10-29 at 8 43 28 PM"
src="https://github.com/user-attachments/assets/69224883-f548-45ec-ada1-1a04ec17eaf5"
/>
<img width="266" height="737" alt="Screenshot 2025-10-29 at 8 58 53 PM"
src="https://github.com/user-attachments/assets/946f7dde-450d-49fd-9fbd-57487f67f461"
/>

Additionally, this makes it so that it's not part of the translucent
tree when things are hidden by the timeline. That way it's easier to see
what is selected inside a hidden tree.

<img width="498" height="196" alt="Screenshot 2025-10-29 at 8 45 24 PM"
src="https://github.com/user-attachments/assets/723107ab-a92c-42c2-8a7d-a548ac3332d0"
/>
<img width="571" height="735" alt="Screenshot 2025-10-29 at 8 59 06 PM"
src="https://github.com/user-attachments/assets/d653f1a7-4096-45c3-b92a-ef155d4742e6"
/>
2025-10-30 15:26:49 -04:00
Sebastian Markbåge
3a0ab8a7ee [DevTools] Synchronize Scroll Position Between Suspense Tab and Main Document (#34641)
It's annoying to have to try to find where it lines up with no hints.

This way when you hover over something it should be on screen.

The strategy I went with is that it scrolls to a percentage along the
scrollable axis but the two might not be exactly the same. Partially
because they have different aspect ratios but also because suspended
boundaries can shrink the document while the suspense tab needs to still
be able to show the boundaries that are currently invisible.
2025-10-29 21:49:35 -04:00
Sebastian Markbåge
0a5fb67ddf [DevTools] Sort suspense timeline by end time instead of just document order (#35011)
Right now it's possible for things like server environments to appear
before other content in the timeline just because it's in a different
document order.

Ofc the order in production is not guaranteed but we can at least use
the timing information we have as a hint towards the actual order.

Unfortunately since the end time of the RSC stream itself is always
after the content that resolved to produce it, it becomes kind of
determined by the chunking. Similarly since for a clean refresh, the
scripts and styles will typically load after the server content they
appear later. Similarly SSR typically finishes after the RSC parts.
Therefore a hack here is that I artificially delay everything with a
non-null environment (RSC) so that RSC always comes after client-side
(Suspense). This is also consistent with how we color things that have
an environment even if children are just Suspense.

To ensure that we never show a child before a parent, in the timeline,
each child has a minimum time of its parent.
2025-10-29 15:05:04 -04:00
Sebastian Markbåge
4f93170066 [Flight] Cache the value if we visit the same I/O or Promise multiple times along different paths (#35005)
We avoid visiting the same async node twice but if we see it again we
returned "null" indicating that there's no I/O there.

This means that if you have two different Promises both resolving from
the same I/O node then we only show one of them. However, in general we
treat that as two different I/O entries to allow for things like
batching to still show up separately.

This fixes that by caching the return value for multiple visits. So if
we found I/O (but no user space await) in one path and then we visit
that path through a different Promise chain, then we'll still emit it
twice.

However, if we visit the same exact Promise that we emitted an await on
then we skip it. Because there's no need to emit two awaits on the same
thing. It only matters when the path ends up informing whether it has
I/O or not.
2025-10-29 10:55:43 -04:00
Sebastian Markbåge
0fa32506da [Flight] Clone subsequent I/O nodes if it's resolved more than once (#35003)
IO tasks can execute more than once. E.g. a connection may fire each
time a new message or chunk comes in or a setInterval every time it
executes.

We used to treat these all as one I/O node and just updated the end time
as we go. Most of the time this was fine because typically you would
have a Promise instance whose end time is really the one that gets used
as the I/O anyway.

However, in a pattern like this it could be problematic:

```js
setTimeout(() => {
  function App() {
    return Promise.resolve(123);
  }
  renderToReadableStream(<App />);
});
```

Because the I/O's end time is before the render started so it should be
excluded from being considered I/O as part of the render. It happened
outside of render. But because the `Promise.resolve()` is inside render
its end time is after the render start so the promise is considered part
of the render. This is usually not a problem because the end time of the
I/O is still before the start of the render so even though the Promise
is valid it has no I/O source so it's properly excluded.

However, if the I/O's end time updates before we observe this then the
I/O can be considered part of the render. E.g. if this was a setInterval
it would be clearly wrong. But it turns out that even setTimeout can
sometimes execute more than once in the async_hooks because each run of
"process.nextTick" and microtasks respectively are ran in their own
before/after. When a micro task executes after this main body it'll
update the end time which can then turn the whole I/O as being inside
the render.

To solve this properly I create a new I/O node each time before() is
invoked so that each one gets to observe a different end time. This has
a potential CPU and memory allocation cost when there's a lot of them
like in a quick stream.
2025-10-28 13:27:35 -04:00
Ricky
fb0d96073c [tests] disableLegacyMode in test-renderer (#35002)
500 tests failed from not using async act. Will fix the tests and then
re-land this.
2025-10-28 12:53:30 -04:00
Michael H
b4455a6ee6 [react-dom] Include all Node.js APIs in Bun entrypoint for /server (#34193) 2025-10-27 23:06:45 +01:00
lauren
17b3765244 [generate-changelog] Refactor (#34993)
Just a light reorganization.
2025-10-27 18:04:48 -04:00
lauren
69f3e9d034 [generate-changelog] Add --format option (#34992)
Adds a new `--format` option which can be `text` (default), `csv`, or
`json`.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34992).
* #34993
* __->__ #34992
2025-10-27 17:54:09 -04:00
Ricky
dd53a946ec [rn] enabled disableLegacyMode everywhere (#34947)
Stacked on https://github.com/facebook/react/pull/34946

This should be a noop, now that the legacy renderers are not being
sync'd.
2025-10-27 17:48:33 -04:00
Ricky
90817f8810 [rn] delete the legacy renderers from the sync (#34946)
Now that RN is only on the New Architecture, we can stop stop syncing
the legacy React Native renderers.

In this diff, I just stop syncing them. In a follow up I'll delete the
code for them so only Fabric is left.

This will also allow us to remove the `enableLegacyMode` feature flag.
2025-10-27 17:38:56 -04:00
Hendrik Liebau
0d721b60c2 [Flight] Don't hang after resolving cyclic references (#34988) 2025-10-27 22:06:28 +01:00
lauren
d3d0ce329e [script] Add yarn generate-changelog (#34962)
(disclaimer: I used codex to write this script)

Adds a new `yarn generate-changelog` script to simplify the process of
writing changelogs. You can use it as follows:

```
$ yarn generate-changelog --help

Usage: yarn generate-changelog [--codex|--claude] [--debug] [<pkg@version> ...]

Options:
  --codex     Use Codex for commit summarization.                      [boolean]
  --claude    Use Claude for commit summarization.                     [boolean]
  --debug     Enable verbose debug logging.           [boolean] [default: false]
  -h, --help  Show help                                                [boolean]

Examples:
  generate-changelog --codex                Generate changelog for a single
  eslint-plugin-react-hooks@7.0.1           package using Codex.
  generate-changelog --claude react@19.3    Generate changelog entries for
  react-dom@19.3                            multiple packages using Claude.
  generate-changelog --codex                Generate changelog for all stable
                                            packages using recorded versions.
```

For example, if no args are passed, the script will print find all the
relevant commits affecting packages (defaults to `stablePackages` in
`ReactVersions.js`) and format them as a simple markdown list.

```
$ yarn generate-changelog

## eslint-plugin-react-hooks@7.0.0
* [compiler] improve zod v3 backwards compat (#34877) ([#34877](https://github.com/facebook/react/pull/34877) by [@henryqdineen](https://github.com/henryqdineen))
* [ESLint] Disallow passing effect event down when inlined as a prop (#34820) ([#34820](https://github.com/facebook/react/pull/34820) by [@jf-eirinha](https://github.com/jf-eirinha))
* Switch to `export =` to fix eslint-plugin-react-hooks types (#34949) ([#34949](https://github.com/facebook/react/pull/34949) by [@karlhorky](https://github.com/karlhorky))
* [eprh] Type `configs.flat` more strictly (#34950) ([#34950](https://github.com/facebook/react/pull/34950) by [@poteto](https://github.com/poteto))
* Add hint for Node.js cjs-module-lexer for eslint-plugin-react-hook types (#34951) ([#34951](https://github.com/facebook/react/pull/34951) by [@karlhorky](https://github.com/karlhorky))
* Add hint for Node.js cjs-module-lexer for eslint-plugin-react-hook types (#34953) ([#34953](https://github.com/facebook/react/pull/34953) by [@karlhorky](https://github.com/karlhorky))

// etc etc...
```

If `--codex` or `--claude` is passed, the script will attempt to use
them to summarize the commit(s) in the same style as our existing
CHANGELOG.md.

And finally, for debugging the script you can add `--debug` to see
what's going on.
2025-10-27 15:48:36 -04:00
Eugene Choi
ba0590f306 [playground] Upgrade playwright (#34991)
Some vulnerabilities were detected in older versions of Playwright,
upgrading for the playground.
2025-10-27 13:42:02 -04:00
Joseph Savona
408b38ef73 [compiler] Improve display of errors on multi-line expressions (#34963)
When a longer function or expression is identified as the source of an
error, we currently print the entire expression in our error message.
This is because we delegate to a Babel helper to print codeframes. Here,
we add some checking and abbreviate the result if it spans too many
lines.
2025-10-23 11:30:28 -07:00
Jorge Cabiedes
09056abde7 [Compiler] Improve error for calculate in render useEffect validation (#34580)
Summary:
Change error and update snapshots

The error now mentions what values are causing the issue which should
provide better context on how to fix the issue

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34580).
* __->__ #34580
* #34579
* #34578
* #34577
* #34575
* #34574
2025-10-23 11:05:55 -07:00
lauren
c91783c1f2 [eprh] Bump ReactVersions for next version (#34965)
This was outdated from previously.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34965).
* __->__ #34965
* #34964
2025-10-23 13:43:27 -04:00
lauren
e0654becf7 [eprh] Update changelog for 7.0.1 (#34964)
Add changelog entry for 7.0.1

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34964).
* #34965
* __->__ #34964
2025-10-23 13:43:16 -04:00
Timothy Lau
6160773f30 [playground] Refactor ConfigEditor to use <Activity> component (#34958)
## Summary

This PR addresses a pending TODO comment left in
https://github.com/facebook/react/pull/34499


eb2f784e75/compiler/apps/playground/components/Editor/ConfigEditor.tsx (L37)

This change removes the temporary workaround and replaces it with
`<Activity>`, as originally intended.

## How did you test this change?

- Updated the component to use `<Activity>` directly
- Verified the editor renders correctly in both development and
production builds.
- The `<Activity>` UI updates as expected.



https://github.com/user-attachments/assets/ce976123-da59-4579-b063-b308a9167b21
2025-10-23 11:13:18 -04:00
Karl Horky
eb2f784e75 Add hint for Node.js cjs-module-lexer for eslint-plugin-react-hook types (#34953)
<!--
  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
-->

Supersedes #34951

## Summary

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

Fix the runtime error with named imports and make the last remaining
[Are The Types
Wrong?](https://arethetypeswrong.github.io/?p=eslint-plugin-react-hooks%400.0.0-experimental-6b344c7c-20251022)
error with `eslint-plugin-react-hooks` go away, thanks to the hint from
Andrew Branch:

- https://github.com/facebook/react/issues/34801#issuecomment-3433478810

## 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.
-->

I tried adding this to `node_modules` and it fixed the failures when
importing named imports like `import { configs, meta, rules } from
'eslint-plugin-react-hooks'`:

```bash
➜  eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0

Oops! Something went wrong! :(

ESLint: 9.37.0

file:///Users/k/p/eslint-config-upleveled/index.js:13
import reactHooks, { configs } from 'eslint-plugin-react-hooks';
                     ^^^^^^^
SyntaxError: Named export 'configs' not found. The requested module 'eslint-plugin-react-hooks' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'eslint-plugin-react-hooks';
const { configs } = pkg;

    at ModuleJob._instantiate (node:internal/modules/esm/module_job:228:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:335:5)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:647:26)
    at async dynamicImportConfig (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:186:17)
    at async loadConfigFile (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:276:9)
    at async ConfigLoader.calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:589:23)
    at async #calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:743:23)
    at async directoryFilter (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/eslint/eslint-helpers.js:309:5)
    at async NodeHfs.<anonymous> (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@humanfs+core@0.19.1/node_modules/@humanfs/core/src/hfs.js:586:29)
    at async NodeHfs.walk (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@humanfs+core@0.19.1/node_modules/@humanfs/core/src/hfs.js:614:3)
➜  eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0
➜  eslint-config-upleveled git:(renovate/react-monorepo) # no error
```

The named imports identifiers `configs`, `meta`, and `rules` also
contain values, as a sanity check:

- https://github.com/facebook/react/pull/34951#issuecomment-3433555636

cc @poteto
2025-10-22 17:51:01 -04:00
Karl Horky
723b25c644 Add hint for Node.js cjs-module-lexer for eslint-plugin-react-hook types (#34951)
<!--
  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?
-->

Fix the runtime error with named imports and make the last remaining
[Are The Types
Wrong?](https://arethetypeswrong.github.io/?p=eslint-plugin-react-hooks%400.0.0-experimental-6b344c7c-20251022)
error with `eslint-plugin-react-hooks` go away, thanks to the hint from
@andrewbranch:

- https://github.com/facebook/react/issues/34801#issuecomment-3433478810

## 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.
-->

I tried adding this to `node_modules` and it fixed the failures when
importing named imports like `import { configs, meta, rules } from
'eslint-plugin-react-hooks'`:

```bash
➜  eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0

Oops! Something went wrong! :(

ESLint: 9.37.0

file:///Users/k/p/eslint-config-upleveled/index.js:13
import reactHooks, { configs } from 'eslint-plugin-react-hooks';
                     ^^^^^^^
SyntaxError: Named export 'configs' not found. The requested module 'eslint-plugin-react-hooks' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'eslint-plugin-react-hooks';
const { configs } = pkg;

    at ModuleJob._instantiate (node:internal/modules/esm/module_job:228:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:335:5)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:647:26)
    at async dynamicImportConfig (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:186:17)
    at async loadConfigFile (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:276:9)
    at async ConfigLoader.calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:589:23)
    at async #calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:743:23)
    at async directoryFilter (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/eslint/eslint-helpers.js:309:5)
    at async NodeHfs.<anonymous> (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@humanfs+core@0.19.1/node_modules/@humanfs/core/src/hfs.js:586:29)
    at async NodeHfs.walk (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@humanfs+core@0.19.1/node_modules/@humanfs/core/src/hfs.js:614:3)
➜  eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0
➜  eslint-config-upleveled git:(renovate/react-monorepo) # no error
```

The named imports identifiers `configs`, `meta`, and `rules` also
contain values, as a sanity check:

- https://github.com/facebook/react/pull/34951#issuecomment-3433555636

cc @poteto
2025-10-22 14:05:49 -04:00
lauren
bbb7a1fdf7 [eprh] Type configs.flat more strictly (#34950)
Addresses #34801 where `configs.flat` is possibly undefined as it was
typed as a record of arbitrary string keys.

<img width="990" height="125" alt="Screenshot 2025-10-22 at 1 16 44 PM"
src="https://github.com/user-attachments/assets/8b0d37b9-d7b0-4fc0-aa62-1b0968dae75f"
/>
2025-10-22 13:18:44 -04:00
Karl Horky
6b344c7c53 Switch to export = to fix eslint-plugin-react-hooks types (#34949)
<!--
  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?
-->

Resolve the type error with the types, according to [Are the types
wrong?](https://arethetypeswrong.github.io/?p=eslint-plugin-react-hooks%407.0.0),
as an additional

- Last attempt: https://github.com/facebook/react/pull/34746
- Original issue: https://github.com/facebook/react/issues/34745

## How did you test this change?

I edited `node_modules/eslint-plugin-react-hooks/index.d.ts` in my
`"module": "Node16"` + `"type": "module"` project and my error went
away:

- https://github.com/facebook/react/issues/34801#issuecomment-3433053067

cc @poteto @michaelfaith @andrewbranch 

<!--
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.
-->
2025-10-22 12:31:09 -04:00
lauren
71b3a03cc9 [forgive] Various fixes to prepare for internal sync (#34928)
Fixes a few small things:

- Update imports to reference root babel-plugin-react-compiler rather
than from `[...]/src/...`
- Remove unused cosmiconfig options parsing for now
- Update type exports in babel-plugin-react-compiler accordingly
2025-10-21 10:57:18 -04:00
Błażej Kustra
39c6545cef Fix indices of hooks in devtools when using useSyncExternalStore (#34547)
## Summary

This PR updates getChangedHooksIndices to account for the fact that
useSyncExternalStore internally mounts two hooks, while DevTools should
treat it as a single user-facing hook.

It introduces a helper isUseSyncExternalStoreHook to detect this case
and adjust iteration so the extra internal hook is skipped when counting
changes.

Before:


https://github.com/user-attachments/assets/0db72a4e-21f7-44c7-ba02-669a272631e5

After:


https://github.com/user-attachments/assets/4da71392-0396-408d-86a7-6fbc82d8c4f5

## How did you test this change?

I used this component to reproduce this issue locally (I followed
instructions in `packages/react-devtools/CONTRIBUTING.md`).

```ts
function Test() {
  // 1
  React.useSyncExternalStore(
    () => {},
    () => {},
    () => {},
  );
  // 2
  const [state, setState] = useState('test'); 
  return (
    <>
      <div
        onClick={() => setState(Math.random())}
        style={{backgroundColor: 'red'}}>
        {state}
      </div>
    </>
  );
}
```
2025-10-21 13:59:20 +01:00
Ruslan Lesiutin
613cf80f26 [DevTools] chore: add useSyncExternalStore examples to shell (#34932)
Few examples of using `useSyncExternalStore` that can be useful for
debugging hook tree reconstruction logic and hook names parsing feature.
2025-10-21 13:51:44 +01:00
Nathan
ea0c17b095 [compiler] loosen computed key restriction for compiler (#34902)
We have a whole ton of compiler errors due to us using a helper to
return breakpoints for CSS-in-js, which results in code like:

```
const styles = {
  [responsive.up('xl')]: { ... }
}
```

this results in TONS of bailouts due to `(BuildHIR::lowerExpression)
Expected Identifier, got CallExpression key in ObjectExpression`.

I was looking into what it would take to fix it and why we don't allow
it, and following the paper trail is seems like the gotchas have been
fixed with the new mutability aliasing model that is fully rolled out.
It looks like this is the same pattern/issue that was fixed (see
https://github.com/facebook/react/blob/main/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js
and the old bug in
d58c07b563/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md).

@josephsavona can you confirm if that's the case and if we're able to
drop this restriction now? (or alternatively, is there another case we
can ignore?)
2025-10-20 13:52:11 -07:00
Sebastian Markbåge
031595d720 [DevTools] Title color tweak (#34927)
<img width="521" height="365" alt="Screenshot 2025-10-20 at 11 53 50 AM"
src="https://github.com/user-attachments/assets/1a073c09-d440-4498-b2b3-c0dcb2272c96"
/>
2025-10-20 14:54:27 -04:00
Ruslan Lesiutin
3cde211b0c React DevTools 7.0.0 -> 7.0.1 (#34926)
Full list of changes:

* Text layout fixes for stack traces with badges
([eps1lon](https://github.com/eps1lon) in
[#34925](https://github.com/facebook/react/pull/34925))
* chore: read from build/COMMIT_SHA fle as fallback for commit hash
([hoxyq](https://github.com/hoxyq) in
[#34915](https://github.com/facebook/react/pull/34915))
* fix: dont ship source maps for css in prod builds
([hoxyq](https://github.com/hoxyq) in
[#34913](https://github.com/facebook/react/pull/34913))
* Lower case "rsc stream" debug info
([sebmarkbage](https://github.com/sebmarkbage) in
[#34921](https://github.com/facebook/react/pull/34921))
* BuiltInCallSite should have padding-left
([sebmarkbage](https://github.com/sebmarkbage) in
[#34922](https://github.com/facebook/react/pull/34922))
* Show the Suspense boundary name in the rect if there's no overlap
([sebmarkbage](https://github.com/sebmarkbage) in
[#34918](https://github.com/facebook/react/pull/34918))
* Don't attach filtered IO to grandparent Suspense
([eps1lon](https://github.com/eps1lon) in
[#34916](https://github.com/facebook/react/pull/34916))
* Infer name from stack if it's the generic "lazy" name
([sebmarkbage](https://github.com/sebmarkbage) in
[#34907](https://github.com/facebook/react/pull/34907))
* Use same Suspense naming heuristics when reconnecting
([eps1lon](https://github.com/eps1lon) in
[#34898](https://github.com/facebook/react/pull/34898))
* Assign a different color and label based on environment
([sebmarkbage](https://github.com/sebmarkbage) in
[#34893](https://github.com/facebook/react/pull/34893))
* Compute environment names for the timeline
([sebmarkbage](https://github.com/sebmarkbage) in
[#34892](https://github.com/facebook/react/pull/34892))
* Don't highlight the root rect if no roots has unique suspenders
([sebmarkbage](https://github.com/sebmarkbage) in
[#34885](https://github.com/facebook/react/pull/34885))
* Highlight the rect when the corresponding timeline bean is hovered
([sebmarkbage](https://github.com/sebmarkbage) in
[#34881](https://github.com/facebook/react/pull/34881))
* Repeat the "name" if there's no short description in groups
([sebmarkbage](https://github.com/sebmarkbage) in
[#34894](https://github.com/facebook/react/pull/34894))
* Tweak the rects design and create multi-environment color scheme
([sebmarkbage](https://github.com/sebmarkbage) in
[#34880](https://github.com/facebook/react/pull/34880))
* Adjust the rects size by one pixel smaller
([sebmarkbage](https://github.com/sebmarkbage) in
[#34876](https://github.com/facebook/react/pull/34876))
* Remove steps title from scrubber
([sebmarkbage](https://github.com/sebmarkbage) in
[#34878](https://github.com/facebook/react/pull/34878))
* Include some sub-pixel precision in rects
([sebmarkbage](https://github.com/sebmarkbage) in
[#34873](https://github.com/facebook/react/pull/34873))
* Don't pluralize if already plural
([sebmarkbage](https://github.com/sebmarkbage) in
[#34870](https://github.com/facebook/react/pull/34870))
* Don't try to load anonymous or empty urls
([sebmarkbage](https://github.com/sebmarkbage) in
[#34869](https://github.com/facebook/react/pull/34869))
* Add inspection button to Suspense tab
([sebmarkbage](https://github.com/sebmarkbage) in
[#34867](https://github.com/facebook/react/pull/34867))
* Don't select on hover ([sebmarkbage](https://github.com/sebmarkbage)
in [#34860](https://github.com/facebook/react/pull/34860))
* Don't highlight on timeline
([sebmarkbage](https://github.com/sebmarkbage) in
[#34861](https://github.com/facebook/react/pull/34861))
* The bridge event types should only be defined in one direction
([sebmarkbage](https://github.com/sebmarkbage) in
[#34859](https://github.com/facebook/react/pull/34859))
* Attempt at a better "unique suspender" text
([sebmarkbage](https://github.com/sebmarkbage) in
[#34854](https://github.com/facebook/react/pull/34854))
* Track whether a boundary is currently suspended and make transparent
([sebmarkbage](https://github.com/sebmarkbage) in
[#34853](https://github.com/facebook/react/pull/34853))
* Don't hide overflow rectangles
([sebmarkbage](https://github.com/sebmarkbage) in
[#34852](https://github.com/facebook/react/pull/34852))
* Measure text nodes ([sebmarkbage](https://github.com/sebmarkbage) in
[#34851](https://github.com/facebook/react/pull/34851))
* Don't measure fallbacks when suspended
([sebmarkbage](https://github.com/sebmarkbage) in
[#34850](https://github.com/facebook/react/pull/34850))
* Filter out built-in stack frames
([sebmarkbage](https://github.com/sebmarkbage) in
[#34828](https://github.com/facebook/react/pull/34828))
* Exclude Suspense boundaries in hidden Activity
([eps1lon](https://github.com/eps1lon) in
[#34756](https://github.com/facebook/react/pull/34756))
* Group consecutive suspended by rows by the same name
([sebmarkbage](https://github.com/sebmarkbage) in
[#34830](https://github.com/facebook/react/pull/34830))
* Preserve the original index when sorting suspended by
([sebmarkbage](https://github.com/sebmarkbage) in
[#34829](https://github.com/facebook/react/pull/34829))
* Don't show the root as being non-compliant
([sebmarkbage](https://github.com/sebmarkbage) in
[#34827](https://github.com/facebook/react/pull/34827))
* Ignore suspense boundaries, without visual representation, in the
timeline ([sebmarkbage](https://github.com/sebmarkbage) in
[#34824](https://github.com/facebook/react/pull/34824))
* Explicitly say which id to scroll to and only once
([sebmarkbage](https://github.com/sebmarkbage) in
[#34823](https://github.com/facebook/react/pull/34823))
* devtools: fix ellipsis truncation for key values
([sophiebits](https://github.com/sophiebits) in
[#34796](https://github.com/facebook/react/pull/34796))
* fix(devtools): remove duplicated "Display density" field in General
settings ([Anatole-Godard](https://github.com/Anatole-Godard) in
[#34792](https://github.com/facebook/react/pull/34792))
* Gate SuspenseTab ([hoxyq](https://github.com/hoxyq) in
[#34754](https://github.com/facebook/react/pull/34754))
* Release `<ViewTransition />` to Canary
([eps1lon](https://github.com/eps1lon) in
[#34712](https://github.com/facebook/react/pull/34712))
2025-10-20 18:39:28 +01:00
Sebastian "Sebbie" Silbermann
1d3664665b [DevTools] Text layout fixes for stack traces with badges (#34925) 2025-10-20 19:33:47 +02:00
Joseph Savona
2bcbf254f1 [compiler] Fix false positive for useMemo reassigning context vars (#34904)
Within a function expression local variables may use StoreContext for
local context variables, so the reassignment check here was firing too
often. We should only report an error for variables that are declared
outside the function, ie part of its `context`.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34904).
* #34903
* __->__ #34904
2025-10-20 08:42:04 -07:00
Ruslan Lesiutin
aaad0ea055 [DevTools] chore: read from build/COMMIT_SHA fle as fallback for commit hash (#34915)
This eliminates the gap in a reproducer for the React DevTools browser
extension from the source code that we submit to Firefox extension
stores.

We use the commit hash as part of the Backend version, here:

2cfb221937/packages/react-devtools-extensions/utils.js (L26-L38)

The problem is that we archive the source code for Mozilla extension
store reviews and there is no git. But since we still download the React
sources from the CI, we could reuse the hash from `build/COMMIT_HASH`
file.
2025-10-20 16:14:47 +01:00
Ruslan Lesiutin
02c80f0d87 [DevTools] fix: dont ship source maps for css in prod builds (#34913)
This has been causing some issues with the submission review on Firefox
store: we use OS-level paths in these source maps, which makes the build
artifact different from the one that's been submitted.

Also saves ~100Kb for main.js artifact.
2025-10-20 13:39:42 +01:00
Sebastian Markbåge
21272a680f Lower case "rsc stream" debug info (#34921)
This is an aesthetic thing. Most simple I/O entries are things like
"script", "stylesheet", "fetch" etc. which are all a single word and
lower case. The "RSC stream" name sticks out and draws unnecessary
attention to itself where as it's really the least interesting to look
at.

I don't love the name because I'm not sure how to explain it. It's
really mainly the byte size of the payload itself without considering
things like server awaits things which will have their own cause. So I'm
trying to communicate the download size of the stream of downloading the
`.rsc` file or the `"rsc stream"`.
2025-10-20 02:42:38 -04:00
Sebastian Markbåge
1440f4f42d [DevTools] BuiltInCallSite should have padding-left (#34922)
We don't normally show this but when we do, it should have the same
padding as other callsites.

<img width="313" height="241" alt="Screenshot 2025-10-19 at 10 46 22 PM"
src="https://github.com/user-attachments/assets/7f72149e-d748-4b71-8291-889038d676e7"
/>
2025-10-20 01:52:50 -04:00
Sebastian Markbåge
f6a4882859 [DevTools] Show the Suspense boundary name in the rect if there's no overlap (#34918)
This shows the title in the top corner of the rect if there's enough
space.

The complex bit here is that it can be noisy if too many boundaries
occupy the same space to overlap or partially overlap.

This uses an R-tree to store all the rects to find overlapping
boundaries to cut the available space to draw inside the rect. We use
this to compute the rectangle within the rect which doesn't have any
overlapping boundaries.

The roots don't count as overlapping. Similarly, a parent rect is not
consider overlapping a child. However, if two sibling boundaries occupy
the same space, no title will be drawn.

<img width="734" height="813" alt="Screenshot 2025-10-19 at 5 34 49 PM"
src="https://github.com/user-attachments/assets/2b848b9c-3b78-48e5-9476-dd59a7baf6bf"
/>

We might also consider drawing the "Initial Paint" title at the root but
that's less interesting. It's interesting in the beginning before you
know about the special case at the root but after that it's just always
the same value so just adds noise.
2025-10-19 22:17:45 -04:00
Sebastian "Sebbie" Silbermann
b485f7cf64 [DevTools] Don't attach filtered IO to grandparent Suspense (#34916) 2025-10-20 00:47:27 +02:00
Sebastian Markbåge
2cfb221937 [Flight] Allow passing DEV only startTime as an option (#34912)
When you use the `createFromFetch` API we assume that the start time of
the request is the same time as when you call `createFromFetch` but in
principle you could use it with a Promise that starts earlier and just
happens to resolve to a `Response`.

When you use `createFromReadableStream` that is almost definitely the
case. E.g. you might have started it way earlier and you don't call
`createFromReadableStream` until you get the headers back (the fetch
promise resolves).

This adds an option to pass in the start time for debug purposes if you
started the request before starting to parse it.
2025-10-19 16:38:33 -04:00
Sebastian Markbåge
58bdc0bb96 [Flight] Ignore bound-anonymous-fn resources as they're not considered I/O (#34911)
When you create a snapshot from an AsyncLocalStorage in Node.js, that
creates a new bound AsyncResource which everything runs inside of.


3437e1c4bd/lib/internal/async_local_storage/async_hooks.js (L61-L67)

This resource is itself tracked by our async debug tracking as I/O. We
can't really distinguish these in general from other AsyncResources
which are I/O.

However, by default they're given the name `"bound-anonymous-fn"` if you
pass it an anonymous function or in the case of a snapshot, that's
built-in:


3437e1c4bd/lib/async_hooks.js (L262-L263)

We can at least assume that these are non-I/O. If you want to ensure
that a bound resource is not considered I/O, you can ensure your
function isn't assigned a name or give it this explicit name.

The other issue here is that, the sequencing here is that we track the
callsite of the `.snapshot()` or `.bind()` call as the trigger. So if
that was outside of render for example, then it would be considered
non-I/O. However, this might miss stuff if you resolve promises inside
the `.run()` of the snapshot if the `.run()` call itself was spawned by
I/O which should be tracked. Time will tell if those patterns appear.
However, in cases like nested renders (e.g. Next.js's "use cache") then
restoring it as if it was outside the parent render is what you do want.
2025-10-19 14:56:56 -04:00
Sebastian Markbåge
bf11d2fb2f [DevTools] Infer name from stack if it's the generic "lazy" name (#34907)
Stacked on #34906.

Infer name from stack if it's the generic "lazy" name. It might be
wrapped in an abstraction. E.g. `next/dynamic`.

Also use the function name as a description of a resolved function
value.

<img width="310" height="166" alt="Screenshot 2025-10-18 at 10 42 05 AM"
src="https://github.com/user-attachments/assets/c63170b9-2b19-4f30-be7a-6429bb3ef3d9"
/>
2025-10-19 14:56:40 -04:00
Sebastian Markbåge
ec7d9a7249 Resolve the .default export of a React.lazy as the canonical value (#34906)
For debug purposes this is the value that the `React.lazy` resolves to.
It also lets us look at that value for descriptions like its name.
2025-10-19 14:56:25 -04:00
Sebastian "Sebbie" Silbermann
40c7a7f6ca [DevTools] Use same Suspense naming heuristics when reconnecting (#34898) 2025-10-18 12:54:05 +02:00
Sebastian Markbåge
3a669170e9 [DevTools] Assign a different color and label based on environment (#34893)
Stacked on #34892.

In the timeline scrubber each timeline entry gets a label and color
assigned based on the environment computed for that step.

In the rects, we find the timeline step that this boundary is part of
and use that environment to assign a color. This is slightly different
than picking from the boundary itself since it takes into account parent
boundaries.

In the "suspended by" section we color each entry individually based on
the environment that spawned the I/O.

<img width="790" height="813" alt="Screenshot 2025-10-17 at 12 18 56 AM"
src="https://github.com/user-attachments/assets/c902b1fb-0992-4e24-8e94-a97ca8507551"
/>
2025-10-17 19:03:15 -04:00
Sebastian Markbåge
a083344699 [DevTools] Compute environment names for the timeline (#34892)
Stacked on #34885.

This refactors the timeline to store not just an id but a complex object
for each step. This will later represent a group of boundaries.

Each timeline step is assigned an environment name. We pick the last
environment name (assumed to have resolved last) from the union of the
parent and child environment names. I.e. a child step is considered to
be blocked by the parent so if a child isn't blocked on any environment
name it still gets marked as the parent's environment name.

In a follow up, I'd like to reorder the document order timeline based on
environment names to favor loading everything in one environment before
the next.
2025-10-17 18:54:53 -04:00
Sebastian Markbåge
423c44b886 [DevTools] Don't highlight the root rect if no roots has unique suspenders (#34885)
Stacked on #34881.

We don't paint suspense boundaries if there are no suspenders. This does
the same with the root. The root is still selectable so you can confirm
but there's no affordance drawing attention to click the root.

This could happen if you don't use the built-ins of React to load things
like scripts and css. It would never happen in something like Next.js
where code and CSS is loaded through React-native like RSC.

However, it could also happen in the Activity scoped case when all
resources are always loaded early.
2025-10-17 18:53:30 -04:00
Sebastian Markbåge
f970d5ff32 [DevTools] Highlight the rect when the corresponding timeline bean is hovered (#34881)
Stacked on #34880.

In #34861 I removed the highlight of the real view when hovering the
timeline since it was disruptive to stepping through the visuals.

This makes it so that when we hover the timeline we highlight the rect
with the subtle hover effect added in #34880.

We can now just use the one shared state for this and don't need the CSS
psuedo-selectors.

<img width="603" height="813" alt="Screenshot 2025-10-16 at 3 11 17 PM"
src="https://github.com/user-attachments/assets/a018b5ce-dd4d-4e77-ad47-b4ea068f1976"
/>
2025-10-17 18:52:26 -04:00
Sebastian Markbåge
724e7bfb40 [DevTools] Repeat the "name" if there's no short description in groups (#34894)
It looks weird when the row is blank when there's no short description
for the entry in a group.

<img width="328" height="436" alt="Screenshot 2025-10-17 at 12 25 30 AM"
src="https://github.com/user-attachments/assets/12f5c55f-a37f-4b6d-913e-f763cec6b211"
/>
2025-10-17 18:52:07 -04:00
Sebastian Markbåge
ef88c588d5 [DevTools] Tweak the rects design and create multi-environment color scheme (#34880)
<img width="1011" height="811" alt="Screenshot 2025-10-16 at 2 20 46 PM"
src="https://github.com/user-attachments/assets/6dea3962-d369-4823-b44f-2c62b566c8f1"
/>

The selection is now clearer with a wider outline which spans the
bounding box if there are multi rects.

The color now gets darked changes on hover with a slight animation.

The colors are now mixed from constants defined which are consistently
used in the rects, the time span in the "suspended by" side bar and the
scrubber. I also have constants defined for "server" and "other" debug
environments which will be used in a follow up.
2025-10-17 18:51:02 -04:00
Hendrik Liebau
dc485c7303 [Flight] Fix detached ArrayBuffer error when streaming typed arrays (#34849)
Using `renderToReadableStream` in Node.js with binary data from
`fs.readFileSync` (or `Buffer.allocUnsafe`) could cause downstream
consumers (like compression middleware) to fail with "Cannot perform
Construct on a detached ArrayBuffer".

The issue occurs because Node.js uses an 8192-byte Buffer pool for small
allocations (< 4KB). When React's `VIEW_SIZE` was 2KB, files between
~2KB and 4KB would be passed through as views of pooled buffers rather
than copied into `currentView`. ByteStreams (`type: 'bytes'`) detach
ArrayBuffers during transfer, which corrupts the shared Buffer pool and
causes subsequent Buffer operations to fail.

Increasing `VIEW_SIZE` from 2KB to 4KB ensures all chunks smaller than
4KB are copied into `currentView` (which uses a dedicated 4KB buffer
outside the pool), while chunks 4KB or larger don't use the pool anyway.
Thus no pooled buffers are ever exposed to ByteStream detachment.

This adds 2KB memory per active stream, copies chunks in the 2-4KB range
instead of passing them as views (small CPU cost), and buffers up to 2KB
more data before flushing. However, it avoids duplicating large binary
data (which copying everything would require, like the Edge entry point
currently does in `typedArrayToBinaryChunk`).

Related issues:

- https://github.com/vercel/next.js/issues/84753
- https://github.com/vercel/next.js/issues/84858
2025-10-17 22:13:52 +02:00
Joseph Savona
c35f6a3041 [compiler] Optimize props spread for common cases (#34900)
As part of the new inference model we updated to (correctly) treat
destructuring spread as creating a new mutable object. This had the
unfortunate side-effect of reducing precision on destructuring of props,
though:

```js
function Component({x, ...rest}) {
  const z = rest.z;
  identity(z);
  return <Stringify x={x} z={z} />;
}
```

Memoized as the following, where we don't realize that `z` is actually
frozen:

```js
function Component(t0) {
  const $ = _c(6);
  let x;
  let z;
  if ($[0] !== t0) {
    const { x: t1, ...rest } = t0;
    x = t1;
    z = rest.z;
    identity(z);
...
```

#34341 was our first thought of how to do this (thanks @poteto for
exploring this idea!). But during review it became clear that it was a
bit more complicated than I had thought. So this PR explores a more
conservative alternative. The idea is:

* Track known sources of frozen values: component props, hook params,
and hook return values.
* Find all object spreads where the rvalue is a known frozen value.
* Look at how such objects are used, and if they are only used to access
properties (PropertyLoad/Destructure), pass to hooks, or pass to jsx
then we can be very confident the object is not mutated. We consider any
such objects to be frozen, even though technically spread creates a new
object.

See new fixtures for more examples.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34900).
* __->__ #34900
* #34887
2025-10-17 11:59:17 -07:00
Joseph Savona
adbc32de32 [compiler] More fbt compatibility (#34887)
In my previous PR I fixed some cases but broke others. So, new approach.
Two phase algorithm:

* First pass is forward data flow to determine all usages of macros.
This is necessary because many of Meta's macros have variants that can
be accessed via properties, eg you can do `macro(...)` but also
`macro.variant(...)`.
* Second pass is backwards data flow to find macro invocations (JSX and
calls) and then merge their operands into the same scope as the macro
call.

Note that this required updating PromoteUsedTemporaries to avoid
promoting macro calls that have interposing instructions between their
creation and usage. Macro calls in general are pure so it should be safe
to reorder them.

In addition, we're now more precise about `<fb:plural>`, `<fbt:param>`,
`fbt.plural()` and `fbt.param()`, which don't actually require all their
arguments to be inlined. The whole point is that the plural/param value
is an arbitrary value (along with a string name). So we no longer
transitively inline the arguments, we just make sure that they don't get
inadvertently promoted to named variables.

One caveat: we actually don't do anything to treat macro functions as
non-mutating, so `fbt.plural()` and friends (function form) may still
sometimes group arguments just due to mutability inference. In a
follow-up, i'll work to infer the types of nested macro functions as
non-mutating.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34887).
* #34900
* __->__ #34887
2025-10-17 11:37:28 -07:00
Joseph Savona
1324e1bb1f [compiler] Cleanup and enable validateNoVoidUseMemo (#34882)
This is a great validation, so let's enable by default. Changes:
* Move the validation logic into ValidateUseMemo alongside the new check
that the useMemo result is used
* Update the lint description
* Make the void memo errors lint-only, they don't require us to skip
compilation (as evidenced by the fact that we've had this validation
off)

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34882).
* #34855
* __->__ #34882
2025-10-16 13:08:57 -07:00
Joseph Savona
7f5ea1bf67 [compiler] More useMemo validation (#34868)
Two additional validations for useMemo:
* Disallow reassigning to values declared outside the useMemo callback
(always on)
* Disallow unused useMemo calls (part of the validateNoVoidUseMemo
feature flag, which in turn is off by default)

We should probably enable this flag though!

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34868).
* #34855
* #34882
* __->__ #34868
2025-10-16 13:05:18 -07:00
Damjan Petrovic
0e32da71c7 Add MIT license header to feature flag utility script (#34833)
Added the standard Meta Platforms, Inc. MIT license notice to the top of
the feature flag comparison script to ensure compliance with repository
licensing requirements and for code consistency.
**No functional or logic changes were made to the code.**
2025-10-16 14:20:21 -04:00
João Eirinha
2381ecc290 [ESLint] Disallow passing effect event down when inlined as a prop (#34820)
## Summary

Fixes https://github.com/facebook/react/issues/34793.

We are allowing passing down effect events when they are inlined as a
prop.

```
<Child onClick={useEffectEvent(...)} />
```

This seems like a case that someone not familiar with `useEffectEvent`'s
purpose could fall for so this PR introduces logic to disallow its
usage.

An alternative implementation would be to modify the name and function
of `recordAllUseEffectEventFunctions` to record all `useEffectEvent`
instances either assigned to a variable or not, but this seems clearer.
Or we could also specifically disallow its usage inside JSX. Feel free
to suggest any improvements.

## How did you test this change?

- Added a new test in
`packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js`.
All tests pass.
2025-10-16 14:18:01 -04:00
Ricky
5418d8bdc1 Fix changelog link (#34879)
Closes https://github.com/reactjs/react.dev/issues/8081
2025-10-16 13:40:26 -04:00
Henry Q. Dineen
ed1351c4fb [compiler] improve zod v3 backwards compat (#34877)
## Summary

When upgrading to `babel-plugin-react-compiler@1.0.0` in a project that
uses `zod@3` we are running into TypeScript errors like:

```
node_modules/babel-plugin-react-compiler/dist/index.d.ts:435:10 - error TS2694: Namespace '"/REDACTED/node_modules/zod/v3/external"' has no exported member 'core'.

435     }, z.core.$strip>>>;
             ~~~~
```

This problem seems to be related to
d6eb735938, which introduced zod v3/v4
compatibility. Since `zod` is bundled into the compiler source this does
not cause runtime issues and only manifests as TypeScript errors. My
proposed solution is this PR is to use zod's [subpath versioning
strategy](https://zod.dev/v4/versioning?id=versioning-in-zod-4) which
allows you to support v3 and v4 APIs on both major versions.

Changes in this PR include:

- Updated `zod` import paths to `zod/v4`
- Bumped min `zod` version to `^3.25.0` for zod which guarantees the
`zod/v4` subpath is available.
- Updated `zod-validation-error` import paths to
`zod-validation-error/v4`
- Bumped min `zod-validation-error ` version to `^3.5.0` 
- Updated `externals` tsup configuration where appropriate. 

Once the compiler drops zod v3 support we could optionally remove the
`/v4` subpath from the imports.

## How did you test this change?

Not totally sure the best way to test. I ran `NODE_ENV=production yarn
workspace babel-plugin-react-compiler run build --dts` and diffed the
`dist/` folder between my change and `v1.0.0` and it looks correct. We
have a `patch-package` patch to workaround this for now and it works as
expected.

```diff
diff --git a/node_modules/babel-plugin-react-compiler/dist/index.d.ts b/node_modules/babel-plugin-react-compiler/dist/index.d.ts
index 81c3f3d..daafc2c 100644
--- a/node_modules/babel-plugin-react-compiler/dist/index.d.ts
+++ b/node_modules/babel-plugin-react-compiler/dist/index.d.ts
@@ -1,7 +1,7 @@
 import * as BabelCore from '@babel/core';
 import { NodePath as NodePath$1 } from '@babel/core';
 import * as t from '@babel/types';
-import { z } from 'zod';
+import { z } from 'zod/v4';
 import { NodePath, Scope } from '@babel/traverse';
 
 interface Result<T, E> {
```

Co-authored-by: Henry Q. Dineen <henryqdineen@gmail.com>
2025-10-16 09:46:55 -07:00
Sebastian Markbåge
93f8593289 [DevTools] Adjust the rects size by one pixel smaller (#34876)
This ensures that the outline of a previous rectangle lines up on the
same pixel as the next rectangle so that they appear consecutive.

<img width="244" height="51" alt="Screenshot 2025-10-16 at 11 35 32 AM"
src="https://github.com/user-attachments/assets/75ffde6f-8cc6-49c1-8855-3953569546b4"
/>

I don't love this implementation. There's probably a smarter way. Was
trying to avoid adding another element.
2025-10-16 12:16:16 -04:00
Sebastian Markbåge
dc1becd893 [DevTools] Remove steps title from scrubber (#34878)
The hover now has a reach tooltip for the "environment" instead.
2025-10-16 12:16:04 -04:00
Sebastian "Sebbie" Silbermann
d8aa94b0f4 Only capture stacks for up to 10 frames for Owner Stacks (#34864) 2025-10-16 18:00:41 +02:00
Sebastian Markbåge
03ba0c76e1 [DevTools] Include some sub-pixel precision in rects (#34873)
Currently the sub-pixel precision is lost which can lead to things not
lining up properly and being slightly off or overlapping.

We need some sub-pixel precision.

Ideally we'd just keep the floating point as is. I'm not sure why the
operations is limited to integers. We don't send it as a typed array
anyway it seems which would ideally be more optimal. Even if we did, we
haven't defined a precision for the protocol. Is it 32bit integer?
64bit? If it's 64bit we can fit a float anyway. Ideally it would be more
variable precision like just pushing into a typed array directly with
the option to write whatever precision we want.
2025-10-16 10:50:41 -04:00
Sebastian Markbåge
4e00747378 [DevTools] Don't pluralize if already plural (#34870)
In a demo today, `cookies()` showed up as `cookieses`. While adorable,
is wrong.
2025-10-16 10:50:18 -04:00
Sebastian Markbåge
7bd8716acd [DevTools] Don't try to load anonymous or empty urls (#34869)
This triggers unnecessary fetches.
2025-10-16 10:49:37 -04:00
Sebastian Markbåge
7385d1f61a [DevTools] Add inspection button to Suspense tab (#34867)
Add inspection button to Suspense tab which lets you select only among
Suspense nodes. It highlights all the DOM nodes in the root of the
Suspense node instead of just the DOM element you hover. The name is
inferred.

<img width="1172" height="841" alt="Screenshot 2025-10-15 at 8 03 34 PM"
src="https://github.com/user-attachments/assets/f04d965b-ef6e-4196-9ba0-51626148fa1a"
/>
2025-10-16 10:49:23 -04:00
Joseph Savona
85f415e33b [compiler] Fix fbt for the ∞th time (#34865)
We now do a single pass over the HIR, building up two data structures:
* One tracks values that are known macro tags or macro calls.
* One tracks operands of macro-related instructions so that we can later
group them.

After building up these data structures, we do a pass over the latter
structure. For each macro call instruction, we recursively traverse its
operands to ensure they're in the same scope. Thus, something like
`fbt('hello' + fbt.param(foo(), "..."))` will correctly merge the fbt
call, the `+` binary expression, the `fbt.param()` call, and `foo()`
into a single scope.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34865).
* #34855
* __->__ #34865
2025-10-15 16:23:31 -07:00
Sebastian Markbåge
903366b8b1 [DevTools] Don't select on hover (#34860)
We should only persist a selection once you click. Currently, we persist
the selection if you just hover which means you lose your selection
immediately when just starting to inspect. That's not what Chrome
Elements tab does - it selects on click.
2025-10-15 13:43:55 -04:00
Sebastian Markbåge
0fbb9b3683 [DevTools] Don't highlight on timeline (#34861)
I find it very frustrating that the highlight covers up the content that
I'm trying to review when stepping through the timeline. It also
triggered on keyboard navigation due to the focus which was annoying.

We could highlight something in the rects instead potentially.
2025-10-15 13:43:43 -04:00
Joseph Savona
e096403c59 [compiler] Infer types for properties after holes in array patterns (#34847)
In InferTypes when we infer types for properties during destructuring,
we were breaking out of the loop when we encounter a hole in the array.
Instead we should just skip that element and continue inferring later
properties.

Closes #34748

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34847).
* #34855
* __->__ #34847
2025-10-15 09:45:06 -07:00
Sebastian Markbåge
1873ad7960 [DevTools] The bridge event types should only be defined in one direction (#34859)
This revealed that a lot of the event types were defined on the wrong
end of the bridge.

It was also a problem that events with the same name couldn't have
different arguments.
2025-10-15 11:42:03 -04:00
Sebastian Markbåge
77b2f909f6 [DevTools] Attempt at a better "unique suspender" text (#34854)
Nobody knows what this terminology means.

Also, this tooltip component sucks:

<img width="634" height="137" alt="Screenshot 2025-10-15 at 12 04 49 AM"
src="https://github.com/user-attachments/assets/a1c33650-7c7d-441f-8f8b-0ea7ebea9351"
/>
2025-10-15 10:26:46 -04:00
Sebastian Markbåge
6773248311 [DevTools] Track whether a boundary is currently suspended and make transparent (#34853)
This makes the rects that are currently in a suspended state appear
ghostly so that you can see where along the timeline you are in the
rects screen.

<img width="451" height="407" alt="Screenshot 2025-10-14 at 11 43 20 PM"
src="https://github.com/user-attachments/assets/f89e362b-a0d5-46e3-8171-564909715cd1"
/>
2025-10-15 10:26:07 -04:00
Sebastian Markbåge
5747cadf44 [DevTools] Don't hide overflow rectangles (#34852)
I get the wish to click the shadow but not all child boundaries are
within the bounds of the outer Suspense boundary's node.

Sometimes they overflow naturally and if we make it overflow hidden we
hide the boundaries. Maybe it would be ok if they're actually clipped by
the real DOM but right now it covers up boundaries that should be there.

Additionally, there's also a common case where the parent boundary
shrinks when suspending the children. That then causes the suspended
child boundaries to be clipped so that you can't restore them. Maybe the
virtual boundary shouldn't shrink in this case.
2025-10-15 10:25:46 -04:00
Sebastian Markbåge
751edd6e2c [DevTools] Measure text nodes (#34851)
We can't measure Text nodes directly but we can measure a Range around
them.

This is useful since it's common, at least in examples, to use text
nodes as children of a Suspense boundary. Especially fallbacks.
2025-10-15 10:24:45 -04:00
Sebastian Markbåge
6cfc9c1ff3 [DevTools] Don't measure fallbacks when suspended (#34850)
We already do this in the update pass. That's what
`shouldMeasureSuspenseNode` does.

We also don't update measurements when we're inside an offscreen tree.

However, we didn't check if the boundary itself was in a suspended state
when in the `measureUnchangedSuspenseNodesRecursively` path.

This caused boundaries to disappear when their fallback didn't have a
rect (including their timeline entries).
2025-10-15 10:12:26 -04:00
Eugene Choi
e7984651e4 [playground] Allow accordion tabs to open on error (#34844)
There was a bug where the other output passes (aside from the "Output"
tab) were unable to open on compiler error. This PR still allows for the
"Output" tab to automatically open on error, but also allows other tabs
to be opened.


https://github.com/user-attachments/assets/157bf5d6-c289-46fd-bafb-073c2e0ff52b
2025-10-14 15:07:27 -04:00
Sebastian Markbåge
5f2b571878 [DevTools] Filter out built-in stack frames (#34828)
Treat fake eval anonymous stacks as built-in. Hide built-in stack frames
unless they're used to call into a non-ignored stack frame.

The two main things to fix here is that 1) we're showing a linkified
stack for fake anonymous and 2) we're showing only built-ins when the
stack is completely internal. Meaning framework code is all noise.
2025-10-14 09:34:57 -04:00
Sebastian Markbåge
56e846921d [Flight] Exclude RSC Stream if the stream resolves in a task (#34838) 2025-10-14 14:28:47 +02:00
Sebastian Markbåge
19b71673b1 [Flight] Forward the current environment when forwarding I/O entries (#34836) 2025-10-14 13:57:48 +02:00
Sebastian "Sebbie" Silbermann
73507ec457 [DevTools] Exclude Suspense boundaries in hidden Activity (#34756) 2025-10-14 13:57:08 +02:00
Sebastian Markbåge
03a62b20fd [Flight] Look for moved debugInfo when logging component performance track (#34839) 2025-10-14 13:21:12 +02:00
Ruslan Lesiutin
b9ec735de2 [Perf Tracks]: Clear potentially large measures (#34803)
Fixes https://github.com/facebook/react/issues/34770.

We need to clear measures at some point, otherwise all these copies of
props that we end up recording will allocate too much memory in
Chromium. This adds `performance.clearMeasures(...)` calls to such cases
in DEV.

Validated that entries are still shown on Performance panel timeline.
2025-10-13 17:42:13 -04:00
Ruslan Lesiutin
47905a7950 Fix/add missing else branch for renders with no props change (#34837)
Stacked on https://github.com/facebook/react/pull/34822.
Fixes a bug introduced in https://github.com/facebook/react/pull/34370.

Just copying the lower else branch to the `properties.length` else
branch at the top.
2025-10-13 17:23:04 -04:00
Sebastian "Sebbie" Silbermann
7b971c0a55 Current behavior for excluding Component render with unchanged props from Components track (#34822)
If we rerender with the same props, the render time will not be
accounted for in the Components track. The attached test reproduces the
behavior observed in
https://codesandbox.io/p/sandbox/patient-fast-j94f2g:
<img width="1118" height="354" alt="CleanShot 2025-10-13 at 00 13 41@2x"
src="https://github.com/user-attachments/assets/4be10ee9-d529-4d98-9035-4f26f9587f52"
/>
2025-10-13 17:14:51 -04:00
Sebastian Markbåge
83ea655a0b [DevTools] Group consecutive suspended by rows by the same name (#34830)
Stacked on #34829.

This lets you get an overview more easily when there's lots of things
like scripts downloading. Pluralized the name. E.g. `script` ->
`scripts` or `fetch` -> `fetches`.

This only groups them consecutively when they'd have the same place in
the list anyway because otherwise it might cover up some kind of
waterfall effects.

<img width="404" height="225" alt="Screenshot 2025-10-13 at 12 06 51 AM"
src="https://github.com/user-attachments/assets/da204a8e-d5f7-4eb0-8c51-4cc5bfd184c4"
/>

Expanded:

<img width="407" height="360" alt="Screenshot 2025-10-13 at 12 07 00 AM"
src="https://github.com/user-attachments/assets/de3c3de9-f314-4c87-b606-31bc49eb4aba"
/>
2025-10-13 13:07:39 -04:00
Sebastian Markbåge
026abeaa5f [Flight] Respect displayName of Promise instances on the server (#34825)
This lets you assign a name to a Promise that's passed into first party
code from third party since it otherwise would have no other stack frame
to indicate its name since the whole creation stack would be in third
party.

We already respect the `displayName` on the client but it's more
complicated on the server because we don't only consider the exact
instance passed to `use()` but the whole await sequence and we can pick
any Promise along the way for consideration. Therefore this also adds a
change where we pick the Promise node for consideration if it has a name
but no stack. Where we otherwise would've picked the I/O node.

Another thing that this PR does is treat anonymous stack frames (empty
url) as third party for purposes of heuristics like "hasUnfilteredFrame"
and the name assignment. This lets you include these in the actual
generated stacks (by overriding `filterStackFrame`) but we don't
actually want them to be considered first party code in the heuristics
since it ends up favoring those stacks and using internals like
`Function.all` in name assignment.
2025-10-13 12:29:00 -04:00
Sebastian Markbåge
d7215b4970 [DevTools] Preserve the original index when sorting suspended by (#34829)
The index is both used as the key and for hydration purposes. Previously
we didn't preserve the index when sorting so the index didn't line up
which caused hydration to be the wrong slot when sorted.
2025-10-13 12:12:12 -04:00
Sebastian Markbåge
e2ce64acb9 [DevTools] Don't show the root as being non-compliant (#34827)
`isStrictModeNonCompliant` on the root just means that it supports
strict mode. It's inherited by other nodes.

It's not possible to opt-in to strict mode on the root itself but rather
right below it. So we should not mark the root as being non-compliant.

This lets you select the root in the suspense tab and it shouldn't show
as red with a warning.
2025-10-13 12:11:52 -04:00
Sebastian Markbåge
34b1567427 [DevTools] Ignore suspense boundaries, without visual representation, in the timeline (#34824)
This ignore a Suspense boundary from the timeline when it has no visual
representation. No rect. In effect, this is not blocking the user
experience.

Technically it could be an effect that mounts which can have a
side-effect which is visible.

It could also be a meta-data tag like `<title>` which is visible. We
could hoistables a virtual representation by giving them a virtual rect.
E.g. at the top of the page. This could be added after the fact.
2025-10-13 12:10:54 -04:00
Sebastian Markbåge
b467c6e949 [DevTools] Explicitly say which id to scroll to and only once (#34823)
This ensures that we don't scroll on changes to the timeline such as
when loading a new page or while the timeline is still loading.

We only auto scroll to a boundary when we perform an explicit operation
from the user.
2025-10-13 12:09:45 -04:00
Sebastian "Sebbie" Silbermann
93d4458fdc [Fiber] Ensure useEffectEvent reads latest values in forwardRef and memo() Components (#34831) 2025-10-13 17:58:43 +02:00
Sebastian Markbåge
1d68bce19c [Fiber] Don't unhide a node if a direct parent offscreen is still hidden (#34821)
If an inner Offscreen commits an unhide, but an outer Offscreen is still
hidden but they're controlling the same DOM node then we shouldn't
unhide the DOM node yet.

This keeps track of whether we're directly inside a hidden offscreen. It
might be better to just do the tree search instead of keeping the stack
state since it's a rare case. Although this hide/unhide path does
trigger a lot of times even when there's no change.

This was technically a bug with Suspense too but it doesn't appear
because a suspended Suspense boundary never commits its partial state.
If it did, it would trigger this same path. But it can happen with an
outer Activity and inner Suspense.
2025-10-12 19:50:06 -04:00
Hendrik Liebau
ead92181bd [Flight] Avoid unnecessary indirection when serializing debug info (#34797)
When a debug channel is hooked up, and we're serializing debug models,
if the result is an already outlined reference, we can emit it directly,
without also outlining the reference. This would create an unnecessary
indirection.

Before:

```
:N1760023808330.2688
0:D"$2"
0:D"$3"
0:D"$4"
0:"hi"

1:{"name":"Component","key":null,"env":"Server","stack":[],"props":{}}
2:{"time":3.0989999999999327}
3:"$1"
4:{"time":3.261792000000014}
```

After:

```
:N1760023786873.8916
0:D"$2"
0:D"$1"
0:D"$3"
0:"hi"

1:{"name":"Component","key":null,"env":"Server","stack":[],"props":{}}
2:{"time":2.4145829999999933}
3:{"time":2.5488749999999527}
```

Notice how the second debug info chunk is now directly referencing chunk
`1` in the debug channel, without outlining and referencing `"$1"` as
its own debug chunk `3`.

This not only simplifies the RSC payload, and reduces overhead. But more
importantly it helps the client resolve cyclic references when a model
has debug info that has a reference back to the model. The client is
currently not able to resolve such a cycle when those chunk indirections
are involved. Ideally, it would also be able to resolve them regardless,
but that requires more work. In the meantime, this fixes an immediate
issue.
2025-10-10 21:44:28 +02:00
Hendrik Liebau
d44659744f [Flight] Fix preload as attribute for stylesheets (#34760)
Follow-up to #34604. For a stylesheet, we need to render `<link
rel="preload" as="style" ...>`, and not `<link rel="preload"
as="stylesheet" ...>`.
([ref](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/preload#what_types_of_content_can_be_preloaded))

fixes vercel/next.js#84569
2025-10-10 21:40:56 +02:00
Sophie Alpert
8454a32f3c devtools: fix ellipsis truncation for key values (#34796)
before
<img width="349" height="73" alt="Screenshot 2025-10-09 at 11 38 03"
src="https://github.com/user-attachments/assets/93fec45d-4ef2-498f-9550-36ff807b63f9"
/>

after
<img width="349" height="73" alt="Screenshot 2025-10-09 at 11 38 39"
src="https://github.com/user-attachments/assets/cb279384-4229-4d56-a803-93c2df897754"
/>
2025-10-10 14:05:49 -04:00
Ian Duvall
06fcc8f380 [playground] Fix syntax error from crashing the Compiler playground (#34623)
<!--
  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?
-->

Fixes a syntax error causing the Compiler playground to crash. Resolves
https://github.com/facebook/react/issues/34622.

## 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.
-->

Tested locally and added a test.

<img width="1470" height="836" alt="Screenshot 2025-09-27 at 8 13 07 AM"
src="https://github.com/user-attachments/assets/29473682-94c3-49dc-9ee9-c2004062aaea"
/>
2025-10-09 12:02:55 -07:00
Anatole-Godard
91e5c3daf1 fix(devtools): remove duplicated "Display density" field in General settings (#34792)
<!--
  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 pull request fixes a small UI issue in the React Developer Tools
settings panel.
The “Display density” field was appearing twice in the General tab.

Fix : https://github.com/facebook/react/issues/34791
2025-10-09 10:38:23 -07:00
lauren
4b3e662e4c [compiler] Add VoidUseMemo rule to RecommendedLatest (#34783)
Adds a new error category VoidUseMemo which is only enabled in the
RecommendedLatest preset for now.
2025-10-08 15:55:13 -04:00
lauren
3e1b34dc51 [compiler] Setup RecommendedLatest preset (#34782)
Renames the `recommended` property on LintRule to `preset`, to allow
exporting rules for different presets. For now the `Recommended` and
`RecommendedLatest` presets are the same, but in the next PR I will
enable more rules for the latest preset.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34782).
* #34783
* __->__ #34782
2025-10-08 15:45:22 -04:00
lauren
7568e71854 [eprh] Prepare for 7.0.0 (#34757)
For 7.0.0:

Slim down presets to just 2 configurations:

- `recommended`: legacy and flat config with all recommended rules, and
- `recommended-latest`: legacy and flat config with all recommended
rules plus new bleeding edge experimental compiler rules

Removed:
- `recommended-latest-legacy`
- `flat/recommended`

Please see the README for new install instructions.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34757).
* #34783
* #34782
* __->__ #34757
2025-10-08 15:17:31 -04:00
Ruslan Lesiutin
9724e3e66e [DevTools] Gate SuspenseTab (#34754) 2025-10-08 05:47:50 -07:00
lauren
848e0e3a4f [eprh] Update plugin config to be compatible with flat and legacy (#34762)
This has been incredibly frustrating as [ESLint's own
docs](https://eslint.org/docs/latest/extend/plugins#backwards-compatibility-for-legacy-configs)
are clearly wrong (see #34679).

This PR uses [eslint-plugin-react's
setup](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/index.js)
as a reference, where the presets are assigned to `configs.flat` (not
documented by eslint).
2025-10-07 16:23:56 -04:00
lauren
5c15c1cd34 [ci] Dry run with debug mode (#34767)
Adds `--debug` to our dry run command so we can see the npm dry run
output
2025-10-07 15:16:18 -04:00
lauren
69b4cb8df4 [ci] Allow dry run (#34765)
Allow running the compiler release script as dry run.
2025-10-07 14:44:46 -04:00
lauren
a664f5f2ee [compiler] Fix incorrect version name (#34764)
Script was using the wrong version name.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34764).
* #34765
* __->__ #34764
2025-10-07 14:32:51 -04:00
lauren
1384ea8230 [compiler] Update release script for latest tag (#34763)
Updates our release script so we can publish to the `latest` tag.
2025-10-07 14:14:50 -04:00
Sebastian Markbåge
3025aa3964 [Flight] Don't serialize toJSON in Debug path and omit wide arrays (#34759)
There's a couple of issues with serializing Buffer in the debug renders.

For one, the Node.js Buffer has a `toJSON` on it which turns the binary
data into a JSON array which is very inefficient to serialize compared
to the real buffer. For debug info we never really want to resolve these
and unlike the regular render we can't error. So this uses the trick
where we read the original value. It's still unfortunate that this
intermediate gets created at all but at least now we're not serializing
it.

Second, we have a limit on depth of objects but we didn't have a limit
on width like large arrays or typed arrays. This omits large arrays from
the payload when possible and make them deferred when there's a debug
channel.
2025-10-07 06:59:34 -07:00
Sebastian "Sebbie" Silbermann
a4eb2dfa6f Release Fragment refs to Canary (#34720)
## Overview

This PR adds the `ref` prop to `<Fragment>` in `react@canary`.

This means this API is ready for final feedback and prepared for a
semver stable release.

## What this means

Shipping Fragment refs to canary means they have gone through extensive
testing in production, we are confident in the stability of the APIs,
and we are preparing to release it in a future semver stable version.

Libraries and frameworks following the [Canary
Workflow](https://react.dev/blog/2023/05/03/react-canaries) should begin
implementing and testing these features.

## Why we follow the Canary Workflow

To prepare for semver stable, libraries should test canary features like
Fragment refs with `react@canary` to confirm compatibility and prepare
for the next semver release in a myriad of environments and
configurations used throughout the React ecosystem. This provides
libraries with ample time to catch any issues we missed before slamming
them with problems in the wider semver release.

Since these features have already gone through extensive production
testing, and we are confident they are stable, frameworks following the
[Canary Workflow](https://react.dev/blog/2023/05/03/react-canaries) can
also begin adopting canary features like Fragment refs.

This adoption is similar to how different Browsers implement new
proposed browser features before they are added to the standard. If a
frameworks adopts a canary feature, they are committing to stability for
their users by ensuring any API changes before a semver stable release
are opaque and non-breaking to their users.

Apps not using a framework are also free to adopt canary features like
Fragment refs as long as they follow the [Canary
Workflow](https://react.dev/blog/2023/05/03/react-canaries), but we
generally recommend waiting for a semver stable release unless you have
the capacity to commit to following along with the canary changes and
debugging library compatibility issues.

Waiting for semver stable means you're able to benefit from libraries
testing and confirming support, and use semver as signal for which
version of a library you can use with support of the feature.

## Docs 

Check out the ["React Labs: View Transitions, Activity, and
more"](https://react.dev/blog/2025/04/23/react-labs-view-transitions-activity-and-more#fragment-refs)
blog post, and [the new docs for Fragment
refs`](https://react.dev/reference/react/Fragment#fragmentinstance) for
more info.
2025-10-06 21:24:24 -07:00
Sebastian "Sebbie" Silbermann
6a8c7fb6f1 Release <ViewTransition /> to Canary (#34712)
## Overview

This PR ships the View Transition APIs to `react@canary`:
- [`<ViewTransition
/>`](https://react.dev/reference/react/ViewTransition)
-
[`addTransitionType`](https://react.dev/reference/react/addTransitionType)

This means these APIs are ready for final feedback and prepare for
semver stable release.

## What this means

Shipping `<ViewTransition />` and `addTransitionType` to canary means
they have gone through extensive testing in production, we are confident
in the stability of the APIs, and we are preparing to release it in a
future semver stable version.

Libraries and frameworks following the [Canary
Workflow](https://react.dev/blog/2023/05/03/react-canaries) should begin
implementing and testing these features.

## Why we follow the Canary Workflow

To prepare for semver stable, libraries should test canary features like
`<ViewTransition />` with `react@canary` to confirm compatibility and
prepare for the next semver release in a myriad of environments and
configurations used throughout the React ecosystem. This provides
libraries with ample time to catch any issues we missed before slamming
them with problems in the wider semver release.

Since these features have already gone through extensive production
testing, and we are confident they are stable, frameworks following the
[Canary Workflow](https://react.dev/blog/2023/05/03/react-canaries) can
also begin adopting canary features like `<ViewTransition />`.

This adoption is similar to how different Browsers implement new
proposed browser features before they are added to the standard. If a
frameworks adopts a canary feature, they are committing to stability for
their users by ensuring any API changes before a semver stable release
are opaque and non-breaking to their users.

Apps not using a framework are also free to adopt canary features like
`<ViewTransition>` as long as they follow the [Canary
Workflow](https://react.dev/blog/2023/05/03/react-canaries), but we
generally recommend waiting for a semver stable release unless you have
the capacity to commit to following along with the canary changes and
debugging library compatibility issues.

Waiting for semver stable means you're able to benefit from libraries
testing and confirming support, and use semver as signal for which
version of a library you can use with support of the feature.

## Docs 

Check out the ["React Labs: View Transitions, Activity, and
more"](https://react.dev/blog/2025/04/23/react-labs-view-transitions-activity-and-more#view-transitions)
blog post, and [the new docs for `<ViewTransition
/>`](https://react.dev/reference/react/ViewTransition) and
[`addTransitionType`](https://react.dev/reference/react/addTransitionType)
for more info.
2025-10-06 21:23:34 -07:00
lauren
b65e6fc58b Revert [eprh] Remove hermes-parser (#34747)
Adds back HermesParser to eslint-plugin-react-hooks. There are still
[external users of
Flow](https://github.com/facebook/react/pull/34719#issuecomment-3368137743)
using the plugin, so we shouldn't break the plugin for them. However, we
still have the problem of double parsing: once from eslint (which we
discard) and then another via babel/hermes parser.

In the long run we should investigate a translation layer from estree to
babel (or alternatively, update the compiler to take estree as input).
But for now, I am reverting the PR.

This does mean that [Sandpack in
react.dev](11cb6b5915/src/components/MDX/Sandpack/runESLint.tsx (L31))
cannot update to the latest eprh as HermesParser does not appear to be
able to be run in a browser. I discovered this while trying to update
eprh on react.dev last week, but didn't investigate deeply. I'll need to
double check that again to find out more.
2025-10-06 12:43:39 -04:00
lauren
c786258422 [eprh] Fix config type not being exported correctly (#34746)
Another attempt to fix #34745. I updated our fixture for eslint-v9 to
include running tsc. I believe there were 2 issues:

1. `export * from './cjs/eslint-plugin-react-hooks'` in npm/index.d.ts
was no longer correct as we updated index.ts to export default instead
of named exports
2. After fixing ^ there was a typescript error which I fixed by making
some small tweaks
2025-10-06 00:53:21 -04:00
Sebastian "Sebbie" Silbermann
1be3ce9996 [Fiber] Bail out of diffing wide objects and arrays (#34742) 2025-10-06 01:13:22 +02:00
Ruslan Lesiutin
3b2a398106 [DevTools] Bump version of react-devtools-core for react-devtools (#34740)
This one was overlooked and yarn.lock was not synced.

Related:
- https://github.com/facebook/react/pull/34692
- https://github.com/facebook/react/pull/34723
2025-10-05 13:45:41 +01:00
Ruslan Lesiutin
62ff1e61fc Revert "[DevTools] Always include the root in the timeline and select it by default" (#34739)
Reverts facebook/react#34654
2025-10-05 13:35:07 +01:00
Sebastian Markbåge
0e79784702 [DevTools] Use documentElement to override cmd+F (#34734)
We override Cmd+F to jump to our search input instead of searching
through the HTML. This is ofc critical since our view virtualized.

However, Chrome DevTools installs its own listener on the document as
well (in the bubble phase) so if we prevent it at the document level
it's too late and it ends up stealing the focus instead. If we instead
listen at the documentElement it works as intended.
2025-10-05 13:13:22 +01:00
lauren
a2329c10ff [eprh] 6.1.1 changelog (#34726)
Update changelog for 6.1.1
2025-10-03 17:58:06 -04:00
Ruslan Lesiutin
d3f84a433a [DevTools] Bump version for extensions (#34723)
`./scripts/devtools/prepare-release.js` actually does automate the
version bump, but only path / minor ones.
2025-10-03 22:03:48 +01:00
lauren
bc2356176b [ci] Fix incorrect filtering logic for prereleases (#34725)
The workflow was correctly publishing the package(s) specified in
`only`, but due to incorrect logic it would also run the 'Publish all
packages' step.
2025-10-03 16:37:55 -04:00
lauren
4fdf7cf249 [ci] Fix runtime_prereleases (#34722)
When using the "only" or "skip" option in the workflow, we were adding
the `--skipTests` param, but that isn't an actual option:
1de32a5e75/scripts/release/publish-commands/parse-params.js
2025-10-03 14:41:34 -04:00
Sebastian "Sebbie" Silbermann
614a945d9d React DevTools 7.0.0 (#34692)
[Preview](https://github.com/eps1lon/react/blob/sebbie/09-28-react_devtools_7.0.0/packages/react-devtools/CHANGELOG.md#700)

Suspense tab is omitted since that's gated on Canary or 19.3.

Will draft a separate blog post for suspended by and open-in-editor
instructions while the extension is in review.
2025-10-03 18:48:28 +01:00
Joseph Savona
d6eb735938 [compiler] Update for Zod v3/v4 compatibility (#34717)
Partial redo of #34710. The changes there tried to use `z.function(args,
return)` to be compatible across Zod v3 and v4, but Zod 4's function API
has completely changed. Instead, I've updated to just use `z.any()`
where we expect a function, and manually validate that it's a function
before we call the value. We already have validation of the return type
(also using Zod).

Co-authored-by: kolvian <eliot@pontarelli.com>
2025-10-03 10:08:20 -07:00
lauren
71753ac90a [eprh] Remove hermes-parser (#34719)
We will be focusing eslint-plugin-react-hooks as the primary OSS-only
package for our lint plugin. eslint-plugin-react-compiler will remain as
a Meta only package as some limitations of our internal infra require us
to use packages that aren't widely adopted by the rest of the industry.

This PR removes `hermes-parser`, which was meant to support parsing Flow
syntax.
2025-10-03 12:58:00 -04:00
Joseph Savona
f24d3bbc70 Update readme for eprh (#34714) 2025-10-03 09:47:34 -07:00
Joseph Savona
85c427d822 [compiler] Remove @babel/plugin-proposal-private-methods (#34715)
redo of #34458 but fixing up prettier

Co-authored-by: Arnaud Barré <arnaud.barre@carbometrix.com>
2025-10-03 09:13:55 -07:00
Sebastian Markbåge
02bd4458f7 [DevTools] Double clicking the root should jump to the beginning of the timeline (#34704)
Unlike the rects, this never toggles. It just jumps.
2025-10-03 11:52:44 -04:00
Eugene Choi
0eebd37041 [playground] Config panel quality fixes (#34611)
Fixed two small issues with the config panel in the compiler playground:
1. Object descriptions were being confined in the config box and most of
it would not be visible upon hover
2. Changed it so that "Applied Configs" would only display a valid set
of configs, rather than switching between "Invalid Configs" and the set
of options. This would be less visually jarring for users as the Output
panel already displays errors. Additionally, if users want to see the
list of config options but have a currently broken config, they would
previously not know how to fix it.

Object hover before: 
<img width="702" height="481" alt="Screenshot 2025-09-26 at 10 41 03 AM"
src="https://github.com/user-attachments/assets/b2ddec2f-16ba-41a1-be1f-96211f46764c"
/>
Hover after:
<img width="702" height="481" alt="Screenshot 2025-09-26 at 10 40 37 AM"
src="https://github.com/user-attachments/assets/dc713a22-4710-46a8-a5d7-485060cc9074"
/>

Applied Configs always displays the last valid set of configs:


https://github.com/user-attachments/assets/2fb9232f-7388-4488-9b7a-bb48bf09e4ca
2025-10-03 10:52:36 -04:00
Jack Pope
74dee8ef64 Add getClientRects to fabric fragment instance (#34545)
Stacked on #34544 

We only have getBoundingClientRect available from RN currently. This
should work as a substitute for this case because the equivalent of
multi-rect elements in RN is a nested Text component. We only include
the rects of top-level host components here so we can assume that
calling getBoundingClientRect on each child is the same result.

Tested in react-native with Fantom.
2025-10-03 09:54:33 -04:00
Jack Pope
e866b1d1e9 Add getRootNode to fabric fragment instance (#34544)
Stacked on #34533 for root fragment handling

This is the same approach as DOM, where we call getRootNode on the
parent.
    
Tests are in react-native using Fantom.
2025-10-03 09:48:37 -04:00
lauren
19f65ff179 [eprh] Remove NoUnusedOptOutDirectives (#34703)
This rule was a leftover from a while ago and doesn't actually lint
anything useful. Specifically, you get a lint error if you try to opt
out a component that isn't already bailing out. If there's a bailout the
compiler already safely skips over it, so adding `'use no memo'` there
is unnecessary.

Fixes #31407

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34703).
* __->__ #34703
* #34700
2025-10-02 19:19:01 -04:00
lauren
26b177bc5e [eprh] Fix recommended config for flat config compatibility (#34700)
Previously, the `recommended` config used the legacy ESLint format
(plugins as an array of strings). This causes errors when used with
ESLint v9's `defineConfig()` helper. This was following [eslint's own
docs](https://eslint.org/docs/latest/extend/plugins#backwards-compatibility-for-legacy-configs):

> With this approach, both configuration systems recognize
"recommended". The old config system uses the recommended key while the
current config system uses the flat/recommended key. The defineConfig()
helper first looks at the recommended key, and if that is not in the
correct format, it looks for the flat/recommended key. This allows you
an upgrade path if you’d later like to rename flat/recommended to
recommended when you no longer need to support the old config system.

However,
[`isLegacyConfig()`](https://github.com/eslint/rewrite/blob/main/packages/config-helpers/src/define-config.js#L73-L81)
(also see
[`eslintrcKeys`](https://github.com/eslint/rewrite/blob/main/packages/config-helpers/src/define-config.js#L24-L35))
function doesn't check for the `plugins` key, so our config was
incorrectly treated as flat config despite being in legacy format.

This PR fixes the issue, along with a few other fixes combined:

1. Convert `recommended` to flat config format
2. Separate basic rules (exhaustive-deps, rules-of-hooks) from compiler
rules
3. Add `recommended-latest-legacy` config for non-flat config users who
want all recommended rules (including compiler rules)
4. Adding more types for the exported config

Our shipped presets in 6.x.x will essentially be:
- `recommended-legacy`: legacy (non-flat), with basic rules only
- `recommended-latest-legacy`: legacy (non-flat), all rules (basic +
compiler)
- `flat/recommended`: flat, basic rules only (now the same as
recommended, but to avoid making a breaking change we'll just keep it
around in 6.x.x)
- `recommended-latest`: flat, all rules (basic + compiler)
- `recommended`: flat, basic rules only

In the next breaking release 7.x.x, we will collapse down the presets
into three:

- `recommended-legacy`: all recommended rules
- `recommended`: all recommended rules
- `recommended-experimental`: all recommended rules + new bleeding edge
experimental rules

Closes #34679

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34700).
* #34703
* __->__ #34700
2025-10-02 18:52:52 -04:00
lauren
056a586928 [fixtures] Update eslint fixture lockfiles (#34699)
Updates the eslint fixture lockfiles.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34699).
* #34703
* #34700
* __->__ #34699
* #34675
2025-10-02 18:42:43 -04:00
lauren
5cc3d49f72 [eprh] Add compiler rules to recommended preset (#34675)
Adds back the compiler rules to the recommended preset, intended for the
next release.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34675).
* #34703
* #34700
* #34699
* __->__ #34675
2025-10-02 18:42:30 -04:00
Eugene Choi
289f070d64 [playground] Improve DiffEditor scrollbar + view (#34691)
The previous DiffEditor view of the playground looked broken and not
cohesive. There would be parts of the scrollbar appearing on the left
side for some reason, along with two scrollbars on the right side. This
PR makes the DiffEditor look more cohesive.

Previous:


https://github.com/user-attachments/assets/1aa1c775-5940-43b2-a75a-9b46452fb78b

After:


https://github.com/user-attachments/assets/b5c04998-6a6c-4b52-b3c5-b2fef21729e0
2025-10-02 17:41:29 -04:00
Sebastian "Sebbie" Silbermann
6a8a8ef326 [Flight] Add <Activity> (#34697) 2025-10-02 23:14:52 +02:00
Sebastian Markbåge
f89ed71ddf [DevTools] Track whether to auto select when new timeline entries come on (#34698)
This auto updates to select the last entry in the timeline until we make
the first selection. That way when new content loads in, we show the
last timeline of what is visible.
2025-10-02 17:06:52 -04:00
Josh Story
7d9f876cbc [Fizz] Detatch boundary after flushing segment with boundary (#34694)
When we flush a Suspense boundary we might not flush the fallback
segment, it might only flush a placeholder instead. In this case the
segment can flush again but we do not want to flush the boundary itself
a second time. We now detach the boundary after flushing it.

better solution to: https://github.com/facebook/react/pull/34668
2025-10-02 13:21:57 -07:00
Sebastian "Sebbie" Silbermann
df3562dc7f Fix DevTools regression tests (#34696) 2025-10-02 21:52:50 +02:00
Sebastian Markbåge
b56907db51 [DevTools] Show Props as Read-only for Suspense/Activity but below (#34695)
Somehow my last commit didn't make it in #34630.
2025-10-02 15:29:38 -04:00
Sebastian Markbåge
c825f03067 [DevTools] Hide State and Props in the Sidebar for Suspense (#34630)
We're showing too much noise in the side-panel when selecting a Suspense
boundary. The interesting thing to see directly is the "Suspended by".

The "props" are mostly useless because the `"name"` prop is already in
the tree. I'm now also showing it in the title bar of the selected
element panel. The "children" and "fallback" props are just the thing
that you can see in the tree view anyway.

The "state" is this weird section with just one field in it, which we
already have duplicated in the top toolbar as well. We can just delete
this. I make sure to show the icon and a "suspended..." section while
the boundary is still loading but now yet resuspended by force
suspending.

While still loading:

<img width="600" height="193" alt="Screenshot 2025-09-27 at 11 54 37 PM"
src="https://github.com/user-attachments/assets/1c3f3a96-46e0-4b11-806f-032569c7d5b5"
/>

After loading:

<img width="602" height="266" alt="Screenshot 2025-09-27 at 11 54 53 PM"
src="https://github.com/user-attachments/assets/c43cc4cb-036f-4ced-9b0d-226c6320cd76"
/>

Resuspended after loading:

<img width="602" height="300" alt="Screenshot 2025-09-27 at 11 55 07 PM"
src="https://github.com/user-attachments/assets/0be01735-48a7-47dc-b5cf-e72ec71e0148"
/>
2025-10-02 15:18:41 -04:00
Sebastian Markbåge
2e68dc76a4 [DevTools] Give a distinct color to the root (#34690)
Stacked on #34654.

The root is special since it represents "Initial Paint" (or a
"Transition" when an Activity is selected). This gives it a different
color in the timeline as well as gives it an outline that's clickable.
Hovering the timeline now shows "Initial Paint" or "Suspense".

Also made the cursor a pointer to invite you to try to click things and
some rounded corners.

<img width="1219" height="420" alt="Screenshot 2025-10-02 at 1 26 38 PM"
src="https://github.com/user-attachments/assets/12451f93-8917-4f3b-8f01-930129e5fc13"
/>

<img width="1217" height="419" alt="Screenshot 2025-10-02 at 1 26 54 PM"
src="https://github.com/user-attachments/assets/02b5e94c-3fbe-488d-b0f2-225b73578608"
/>

<img width="1215" height="419" alt="Screenshot 2025-10-02 at 1 27 24 PM"
src="https://github.com/user-attachments/assets/c24e8861-e74a-4ccc-8643-ee9d04bef43c"
/>

<img width="1216" height="419" alt="Screenshot 2025-10-02 at 1 27 10 PM"
src="https://github.com/user-attachments/assets/d5cc2b62-fa64-41bf-b485-116b1cd67467"
/>
2025-10-02 14:37:03 -04:00
Sebastian Markbåge
ced705d756 [DevTools] Always include the root in the timeline and select it by default (#34654)
Rebased on #34454.

Always include the root in the timeline even if it has no unique
suspenders, since even if it won't suspend, we have to be able to see
that and step to one step before the next boundary to see the first
boundary that does suspend in its fallback state.

Also, if there's no current selection on initial mount, select the last
entry in the timeline. We usually do this with `selectedSuspenseID` but
that doesn't happen on initial load. So this does it on initial load if
nothing else is selected by then. That way when you reload you get the
initial root selected.

There's a problem here because we should really use one source of truth
and `selectedSuspenseID` doesn't really do anything now. Either it
should be its separate source of truth and you can't show components in
the side-panel or it should be derived from the other state.

If it's derived, once there's a selection, e.g. in the root, then even
if new timelines load it will never change but that's probably a good
thing.
2025-10-02 14:20:02 -04:00
Joseph Savona
70b52beca6 [compiler] @enablePreserveExistingMemoizationGuarantees on by default (#34689)
This enables `@enablePreserveExistingMemoizationGuarantees` by default.
As of the previous PR (#34503), this mode now enables the following
behaviors:

- Treating variables referenced within a `useMemo()` or `useCallback()`
as "frozen" (immutable) as of the start of the call. Ie, the compiler
will assume that the values you reference are not mutated by the body of
the useMemo, not are they mutated later. Directly modifying them (eg
`var.property = true`) will be an error.
- Similarly, the results of the useMemo/useCallback are treated as
frozen (immutable) after the call.

These two rules match the behavior for other hooks: this means that
developers will see similar behavior to swapping out `useMemo()` for a
custom `useMyMemo()` wrapper/alias.

Additionally, as of #34503 the compiler uses information from the manual
dependencies to know which variables are non-nullable. Even if a useMemo
block conditionally accesses a nested property — `if (cond) { log(x.y.z)
}` — where the compiler would not usually know that `x` is non-nullable,
if the user specifies `x.y.z` as a manual dependency then the compiler
knows that `x` and `x.y` are non-nullable and can infer a more precise
dependency.

Finally, this mode also ensures that we always memoize function calls
that return primitives. See #34343 for more details.

For now, I've explicitly opted out of this feature in all test fixtures
where the behavior changed.
2025-10-02 10:25:00 -07:00
Sebastian "Sebbie" Silbermann
4a28227960 [DevTools] Inspect the Initial Paint when inspecting a Root (#34454) 2025-10-02 19:18:15 +02:00
Sebastian "Sebbie" Silbermann
e4a27db283 [DevTools] Defer Suspense tab to 19.3.0-canary (#34688) 2025-10-02 19:13:52 +02:00
Joseph Savona
57d5a59748 [compiler] enablePreserveMemo treats manual deps as non-nullable (#34503)
The `@enablePreserveExistingMemoizationGuarantees` mode can still fail
to preserve manual memoization due to mismtached dependencies.
Specifically, where the user's dependencies are more precise than the
compiler infers bc the compiler is being conservative about what might
be nullable. In this mode though we're intentionally using information
from the manual memoization and can also rely on the deps as a signal
for what's non-nullable.

The idea of the PR is that we treat manual memo deps just like other
inferred-as-non-nullable objects during PropagateScopeDeps. We're
careful to not treat the full path as non-nullable, only up to the last
property index. So `x.y.z` as a manual dep treats `x` and `x.y` as
non-nullable, allowing us to preserve a conditional dependency on
`x.y.z`.

Optionals within manual dependencies are a bit trickier and aren't
handled yet, but hopefully that's less common and something we can
improve in a follow-up. Not handling them just means that developers may
hit false positives on validating existing memoization if they use
optional chains in manual dependencies.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34503).
* #34689
* __->__ #34503
2025-10-02 09:48:52 -07:00
Sebastian "Sebbie" Silbermann
bc828bf6e3 [DevTools] Recommend React Performance tracks if supported when Timeline profiler is not supported (#34684) 2025-10-02 18:33:50 +02:00
Sebastian "Sebbie" Silbermann
a757cb7667 Fix DevTools fixture crash due to usage of renamed APIs (#34682) 2025-10-02 14:43:02 +02:00
Sebastian Markbåge
d74f061b69 [Fiber] Clean up ViewTransition when it fails to start (#34676)
The View Transition docs were unclear about this but apparently the
`finished` promise never settles if the animation never started. So if
there's an error that rejects the `ready` promise, we'll never run the
clean up which can cause it to stall.

Fixes #34662.

However, ultimately that is caused by Chrome stalling our default
`onDefaultTransitionIndicator` but it should be unblocked after 10
seconds, not a minute.
2025-10-01 21:58:13 -04:00
Eugene Choi
f7254efc5c [playground] Persist open tabs on compiler error (#34673)
This change allows it so that tabs that were open before a compiler
error are automatically opened again when the error is resolved. Quality
of life change for those especially working with the advanced view of
the playground.


https://github.com/user-attachments/assets/cd2dc117-e6fc-4f57-a08f-259757c4f5e8
2025-10-01 21:26:16 -04:00
Sebastian "Sebbie" Silbermann
79ca5ae855 Bump next prerelease version numbers (#34674) 2025-10-02 00:31:55 +02:00
lauren
ae74234eae [eprh] Allow compiler rules to be opted-in but not in the preset (#34672)
Follow up to #34649. This adds the compiler rules back so they can be
opted-in 6.1.0, but aren't included in the presets as that would be a
breaking change.
2025-10-01 17:05:42 -04:00
Sebastian "Sebbie" Silbermann
861811347b Bump scheduler version (#34671)
The canaries have been published depending on 0.27-canary. Bumping
scheduler just in case to be sure.
2025-10-01 22:45:31 +02:00
Ricky
7f9d99749c Land enableHiddenSubtreeInsertionEffectCleanup (#34372)
Fixes a bug where insertion effects were not cleaned up if a hidden
Activity is unmounted.
2025-10-01 16:31:30 -04:00
Sebastian "Sebbie" Silbermann
aef8b1b562 19.2 changelog (#34655)
Co-authored-by: Jack Pope <jackpope1@gmail.com>
Co-authored-by: Rick Hanlon <rickhanlonii@meta.com>
2025-10-01 22:11:02 +02:00
Jack Pope
67e24bc527 Improve lint error messages for useEffectEvent (#34669)
Called Before:

> `logEvent` is a function created with React Hook "useEffectEvent", and
can only be called from the same component.

Called After:

> `logEvent` is a function created with React Hook "useEffectEvent", and
can only be called from Effects and Effect Events in the same component.

Referenced Before:

> `logEvent` is a function created with React Hook "useEffectEvent", and
can only be called from the same component. They cannot be assigned to
variables or passed down.

Referenced After:

> `logEvent` is a function created with React Hook "useEffectEvent", and
can only be called from Effects and Effect Events in the same component.
It cannot be assigned to a variable or passed down.
2025-10-01 15:17:08 -04:00
Sebastian Markbåge
bbc2d596fa Traverse down an updated tree even if it has no passive effects in profiling mode (#34667)
We need this to be able to log the renders that happened inside.

This is the same thing we do here but for the offscreen special cases:


https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberCommitWork.js#L3452-L3457
2025-10-01 13:45:37 -04:00
Sebastian "Sebbie" Silbermann
1bd1f01f2a Ship partial-prerendering APIs to Canary (#34633) 2025-10-01 18:22:30 +02:00
Sebastian "Sebbie" Silbermann
548235db10 Enable React performance tracks in Canary (#34665)
Co-authored-by: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com>
2025-10-01 18:13:15 +02:00
Sebastian "Sebbie" Silbermann
1f460f31ee [DevTools] Fix host instance highlighting (#34661) 2025-10-01 17:32:34 +02:00
Sebastian "Sebbie" Silbermann
2f0649a0b2 [Fizz] Remove nonce option from resume-and-prerender APIs (#34664) 2025-10-01 17:32:26 +02:00
Sebastian Markbåge
7bccdbd765 Fix "Consecutive" Event Logs in Performance Track (#34659)
Reset EventTime when clearing timers. We need to track repeat updates
separately.

Typically we always reset all timers when we've logged an update. The
same update shouldn't be logged again.

I was trying to be clever and not reset the XEventTime because we also
need the timestamp to know if it's a repeat event. However, because of
this it looked like we had an event schedule an update even after we had
reset them.

This always resets the XEventTime to -1.1 and then stashes the old time
on EventRepeatTime which is our indication whether the next update was a
repeat of the old event.

---------

Co-authored-by: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com>
Co-authored-by: Ricky <rickhanlonii@gmail.com>
2025-10-01 10:53:08 -04:00
Sebastian "Sebbie" Silbermann
5667a41fe4 Bump next prerelease version numbers (#34639) 2025-10-01 15:15:24 +02:00
lauren
cf884083e0 [eprh] Temporarily disable compiler rules (#34649)
Temporarily disables the compiler rules in eslint-plugin-react-hooks.
Will revert this later.
2025-09-30 18:45:33 -04:00
Jack Pope
57b16e3788 [lint] Remove experimental gating useEffectEvent rules (#34660)
Stacked on https://github.com/facebook/react/pull/34637

`useEffectEvent` is now in canary so we need to remove this
`__EXPERIMENTAL__` gating on the rules and tests
2025-09-30 16:55:56 -04:00
Jordan Brown
2a04bae651 [lint] Use settings for additional hooks in exhaustive deps (#34637)
Like in the diff below, we can read from the shared configuration to
check exhaustive deps.

I allow the classic additionalHooks configuration to override it so that
this change
is backwards compatible.


--

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34637).
* __->__ #34637
* #34497
2025-09-30 16:44:43 -04:00
Jordan Brown
92cfdc3a4e [lint] Enable custom hooks configuration for useEffectEvent calling rules (#34497)
We need to be able to specify additional effect hooks for the
RulesOfHooks lint rule
in order to allow useEffectEvent to be called by custom effects.
ExhaustiveDeps
does this with a regex suppplied to the rule, but that regex is not
accessible from
other rules.

This diff introduces a `react-hooks` entry you can put in the eslint
settings that
allows you to specify custom effect hooks and share them across all
rules.

This works like:
```
{
  settings: {
    'react-hooks': {
      additionalEffectHooks: string,
    },
  },
}
```

The next diff allows useEffect to read from the same configuration.


----

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34497).
* #34637
* __->__ #34497
2025-09-30 16:44:22 -04:00
Eugene Choi
a55e98f738 [playground] ViewTransition on internals toggle & tab expansion (#34597)
<!--
  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?
-->

Added `<ViewTransition>` for when the "Show Internals" button is toggled
for a basic fade transition. Additionally added a transition for when
tabs are expanded in the advanced view of the Compiler Playground to
display a smoother show/hide animation.

## 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.
-->


https://github.com/user-attachments/assets/c706b337-289e-488d-8cd7-45ff1d27788d
2025-09-30 15:25:10 -04:00
Ruslan Lesiutin
063394cf82 [Perf Tracks]: Always log effect that spawned blocking update (#34648)
We've observed some scenarios, where cascading update happens in an
effect that was shorter than 0.05ms. In this case, this effect won't be
displayed on a timeline, because of the threshold that we are using, but
it would be shown in entry properties or in a stack trace.

To avoid confusion, we should always log such effects.

Validated via manually changing the threshold to 100ms+ and observing
that only effects that triggered an update are visible on a timeline.
2025-09-30 20:05:44 +01:00
Sebastian Markbåge
d8a15c49a4 [Fiber] Reset remaining child lanes after propagating context inside Offscreen (#34658)
Otherwise, when a context is propagated into an Activity (or Suspense)
this will leave work behind on the Offscreen component itself. Which
will cause an extra unnecessary render and commit pass just to figure
out that we're still defering it to idle.

This is because lazy context propagation, when calling to schedule some
work walks back up the tree all the way to the root. This is usually
fine for other nodes since they'll recompute their remaining child lanes
on the way up. However, for the Offscreen component we'll have already
computed it. We need to set it after propagation to ensure it gets
reset.
2025-09-30 14:51:48 -04:00
Sebastian Markbåge
0d8ff4d8c7 [DevTools] Show "Initial Paint" in the breadcrumbs when root is selected (#34652)
We selected the root. This means that we're currently viewing the
Transition that rendered the whole screen. In laymans terms this is
really "Initial Paint". Once we add subtree selection, then the
equivalent should be called "Transition" since in that case it's really
about a Transition within the page. So if you've selected an Activity
tree this should be called "Transition".

Once we add the environment support to the timeline. The first entry on
the timeline should also be called "Initial Paint" when you haven't
selected an Activity and "Transition" when you have.

Technically they're both meant to be "Transition" but nobody thinks of
initial load as a "Transition" from the previous MPA page.

<img width="1214" height="419" alt="Screenshot 2025-09-29 at 5 18 58 PM"
src="https://github.com/user-attachments/assets/cae263e3-133c-4fa9-9587-a7b2344199f4"
/>
2025-09-30 14:40:33 -04:00
Sebastian Markbåge
554a373d7e [DevTools] Use the scrollWidth/Height for the root when the root is the documentElement (#34651)
If I can scroll the document due to it overflowing, I should be able to
scroll the suspense tab as much. The real rect for the root when it's
the document is really the full scroll height.

This doesn't fully eliminate the need to do recursive bounding boxes for
the root since it's still possible to have the rects overflow. E.g. if
they're currently resuspended or inside nested scrolls.

~However, maybe we should have the actual paintable root rect just be
this rectangle instead of including the recursive ones.~ Actually never
mind. The root really represents the Transition so it doesn't make sense
to give it any specific rectangle. It's rather the whole background.
2025-09-30 14:37:40 -04:00
Sebastian Markbåge
5dd163b49e [DevTools] Auto-scroll when stepping through the timeline (#34653)
This brings the Suspense boundary that's switching into view so that
when you play the loading sequence you can see how it plays out.
Otherwise it's really hard to find where things are changing.

This assumes we'll also scroll synchronize the suspense tab which will
bring it into view there too.
2025-09-30 14:37:14 -04:00
Pieter De Baets
ef8894452b Rollout enablePersistedModeClonedFlag (#34520)
## Summary

Experimentation has completed for this at Meta and we've observed
positive impact on key React Native surfaces.

## How did you test this change?

yarn flow fabric
2025-09-30 12:34:13 +01:00
Sebastian "Sebbie" Silbermann
e6f2a8a376 Allow running yarn lint on subset of paths (#34646) 2025-09-30 12:30:40 +02:00
Jack Pope
ba2214e571 Apply build script changes for RN to main (#34640)
This was merged into the 19.1.1 patch release branch in
https://github.com/facebook/react/pull/33972 but we never upstreamed it
to main. This should merge to main to make it easier to sync versions to
RN after future releases.

---------

Co-authored-by: Riccardo Cipolleschi <cipolleschi@meta.com>
2025-09-29 20:40:24 -04:00
Sebastian "Sebbie" Silbermann
ecb2ce6c5f [Flight] Compute better I/O description for exotic types (#34650) 2025-09-29 21:03:07 +02:00
Eugene Choi
3580584ba2 [playground] ViewTransition on tab switch (#34596)
<!--
  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?
-->

Utilized `<ViewTransition>` to introduce a sliding animation upon
switching between the Output and SourceMap tabs in the default
playground view.

## 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.
-->


https://github.com/user-attachments/assets/1ac93482-8104-4f9a-887e-6adca3537dca
2025-09-29 14:40:33 -04:00
Eugene Choi
319a7867d0 [playground] ViewTransition on config expand (#34595)
<!--
  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?
-->

Introduced `<ViewTransition>` to the React Compiler Playground. Added an
initial animation on the config panel opening/closing to allow for a
smoother visual experience. Previously, the panel would flash in and out
of the screen upon open/close.

## 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.
-->




https://github.com/user-attachments/assets/9dc77a6b-d4a5-4a7a-9d81-007ebb55e8d2
2025-09-29 14:09:37 -04:00
Sebastian Markbåge
d15d7fd79e [DevTools] Double click a Suspense Rect to jump to its position in the timeline (#34642)
When you double click it will hide or show by jumping to the selected
index or one step before the selected.

Let's you go from a suspense boundary into the timeline to find its
position. I also highlight the step in the timeline when you hover the
rect.

This only works if it's in the selected root but all of those should be
merged into one single timeline.

One thing that's weird about the SuspenseNodes now is that they
sometimes gets deleted but not always when they're resupended. Nested
ones maybe? This means that if you double click to hide it, you can't
double click again to show it. This seems like an unrelated bug that we
should fix.

We could potentially repurpose the existing "Suspend" button in the
toolbar to do this too, or maybe add another icon there.
2025-09-29 10:43:01 -04:00
Sebastian "Sebbie" Silbermann
8674c3ba28 [DevTools] Enable Suspense tab for Canary releases (#34591) 2025-09-29 16:23:30 +02:00
Sebastian "Sebbie" Silbermann
24e260d35b Enable rules-of-hooks for DevTools (#34645) 2025-09-29 15:31:06 +02:00
Sebastian "Sebbie" Silbermann
2bbb7be0e1 [DevTools] Don't call Hooks conditionally (#34644) 2025-09-29 15:15:09 +02:00
Sebastian Markbåge
dce1f6cd5d [DevTools] Custom Scrubber Design (#34627)
Stacked on #34620.

This will let us use different color for different segments of the
timeline.

Since we're modeling discrete steps (sometimes just a couple), a
scrubber with a handle that you have to move is quite annoying and
misleading. Doesn't show you how many steps there are. Therefore I went
with a design that highlights each segment as its own step and you can
click to jump to a step.

This is still backed by an input range for accessibility and keyboard
controls.

<img width="1213" height="434" alt="Screenshot 2025-09-27 at 4 50 21 PM"
src="https://github.com/user-attachments/assets/2c81753d-1b66-4434-8b1d-0a163fa22ab3"
/>
<img width="1213" height="430" alt="Screenshot 2025-09-27 at 4 50 45 PM"
src="https://github.com/user-attachments/assets/07983978-a8f6-46ed-8c51-6ec96487af66"
/>


https://github.com/user-attachments/assets/bc725f01-f0b5-40a8-bbb5-24cc4e84e86d
2025-09-28 20:00:09 -04:00
Sebastian Markbåge
7c0fff6f2b [DevTools] Add Play/Pause and Skip Controls to the Timeline (#34620)
Stacked on #34625.

This is a nice way to step through the timeline and simulate the visuals
on screen as you do it. It's also convenient to step through one at a
time, especially with the forwards button.

However, the secondary purpose of this is that it helps anchor the UI
visually as something like a timeline like in a video so that the
timeline itself becomes more identifiable.


https://github.com/user-attachments/assets/cb367c8e-9efb-4a00-a58e-4579be20beb8
2025-09-28 19:14:28 -04:00
Sebastian Markbåge
e2d19bf6a9 [DevTools] Use pretty icon with icon for unique suspenders toggle (#34625)
Stacked on #34624.

<img width="638" height="170" alt="Screenshot 2025-09-27 at 12 57 10 PM"
src="https://github.com/user-attachments/assets/f67023b1-e7be-4252-93ab-6302bc63ac26"
/>
<img width="641" height="250" alt="Screenshot 2025-09-27 at 12 57 21 PM"
src="https://github.com/user-attachments/assets/f96a9b48-c6f4-406f-a0ea-b3da288411b5"
/>
2025-09-28 19:13:15 -04:00
Sebastian Markbåge
a7d8dddaf3 [DevTools] Add Settings button on Suspense Tab (#34624)
The settings dialog appears on all tabs and should be reachable from
Suspense tab too. It's a bit weird because it's not contextual to the
tab and it shows you whatever your last settings tab was opened. Maybe
it should default to opening to the current tab's settings?

There aren't any Suspense specific settings yet but there definitely
will be. We could move the "Show all" into settings but it might be
frequently that you want to check why something isn't suspending a
Suspense boundary or test SSR streaming.

However, the general settings still apply to the Suspense tab. E.g.
switching dark/light mode.

<img width="857" height="233" alt="Screenshot 2025-09-27 at 12 35 05 PM"
src="https://github.com/user-attachments/assets/4a38e94f-2074-4dce-906b-9a1c40bccb9b"
/>
2025-09-28 19:09:52 -04:00
Sebastian Markbåge
8309724cb4 [Fiber][DevTools] Add scheduleRetry to DevTools Hook (#34635)
When forcing suspense/error we're doing that by scheduling a sync update
on the fiber. Resuspending a Suspense boundary can only happen sync
update so that makes sense. Erroring also forces a sync commit. This
means that no View Transitions fire.

However, unsuspending (and dismissing an error dialog) can be async so
the reveal should be able to be async.

This adds another hook for scheduling using the Retry lane. That way
when you play through a reveal sequence of Suspense boundaries (like
playing through the timeline), it'll run the animations that would've
ran during a loading sequence.
2025-09-28 13:51:35 -04:00
Sebastian Markbåge
09d3cd8fb5 [DevTools] Larger panel buttons and center (#34619)
The panel icons are quite small. Especially compared to the equivalent
buttons elsewhere in Chrome DevTools that otherwise use the same icons.
This makes them a little bigger to make them similar size to our other
button icons.

They were also a bit off center. This centers them as well.

Before:

<img width="409" height="426" alt="Screenshot 2025-09-26 at 4 23 15 PM"
src="https://github.com/user-attachments/assets/4a5de032-e316-44ed-9424-8bccce00f0cd"
/>

After:

<img width="519" height="388" alt="Screenshot 2025-09-26 at 4 22 57 PM"
src="https://github.com/user-attachments/assets/1763e522-5683-4fac-a913-27910a30a039"
/>
2025-09-28 12:09:08 -04:00
Sebastian Markbåge
f78b2343cc [DevTools] Recursively compute the bounding rect of the roots (#34629)
It's possible for the children to overflow the bounding rect of the root
in general when they overflow in the DOM. However even when it doesn't
overflow in the DOM, the bounding rect of the root can shrink while the
content is suspended. In fact, it's very likely.

Originally I thought we didn't need to consider this recursively because
document scrolling takes absolute positioned content into account but
because we're using nested overflow scrolling, we have to manually
compute this.
2025-09-28 10:15:31 -04:00
Sebastian "Sebbie" Silbermann
e08f53b182 Match react-dom/static test entrypoints and published entrypoints (#34599) 2025-09-28 13:26:31 +02:00
Sebastian Markbåge
2622487a74 [DevTools] Move Timeline to footer instead of header (#34617)
One thing that always bothered me is that the collapse buttons on either
side of the toolbar looks like left/right buttons which would conflict
with some steps buttons I plan to add. Another issue is that we'll need
to add more tool buttons to the top and probably eventually a Search
field. Ideally this whole section should line up vertically with the
height of the title row.

I also realized that all UIs that have some kind of timeline control
(and play/pause/skip) do that in the bottom below the content. E.g.
music players and video players all do that. We're better off playing
into that structure since that's the UI analogy we're going for here.
Makes it clearer what the weird timeline is for.

By moving it to the bottom it also frees up the top for the collapse
buttons and more controls.

__Horizontal__

<img width="794" height="809" alt="Screenshot 2025-09-26 at 3 40 35 PM"
src="https://github.com/user-attachments/assets/dacad9c4-d52f-4b66-9585-5cc74f230e6f"
/>

__Vertical__

<img width="570" height="812" alt="Screenshot 2025-09-26 at 3 40 53 PM"
src="https://github.com/user-attachments/assets/db225413-849e-46f1-b764-8fbd08b395c4"
/>
2025-09-26 16:27:49 -04:00
Sebastian "Sebbie" Silbermann
8a24ef3e75 [DevTools] Show Transition indicator when "suspended by" rows are expanded (#34565) 2025-09-26 22:27:22 +02:00
Ruslan Lesiutin
c552618a82 flags: make enableAsyncDebugInfo dynamic for www (#34430)
As titled. This adds dev-only debugging information to Fizz / Flight
that could be used for tracking Promise's stack traces in "suspended by"
section of DevTools.
2025-09-26 11:43:03 -07:00
Sebastian "Sebbie" Silbermann
df38ac9a3b Ensure useEffectEvent implementation is available in Canary (#34614) 2025-09-26 18:53:12 +02:00
Jack Pope
8bb7241f4c Bump useEffectEvent to Canary (#34610)
Bumps `useEffectEvent` from `@experimental` to `@canary`. Removes the
`experimental_` prefix from the export.

## TODO
- [ ] Update useEffectEvent reference page and Canary badging in docs:
https://github.com/reactjs/react.dev/pull/8025
2025-09-26 11:51:30 -04:00
Sebastian "Sebbie" Silbermann
8d557a638e [DevTools] Only show Suspense rects matching "unique-suspenders-only" filter (#34607) 2025-09-26 17:29:15 +02:00
Sebastian Markbåge
6a51a9fea6 [DevTools] Track Server Environment Names of Each SuspenseNode (#34605)
Tracks the environment names of the I/O in each SuspenseNode and sent it
to the front end when the suspenders change.

In the front end, every child boundary should really be treated as it
has all environment names of the parents too since they're blocked by
the parent too. We could do this tracking on backend but if there's ever
one added on the root would need to be send for every child.

This lets us highlight which subtrees are blocked by content on the
server.

---------

Co-authored-by: Sebastian "Sebbie" Silbermann <silbermann.sebastian@gmail.com>
2025-09-26 09:43:38 -04:00
Sebastian Markbåge
1fd291d3c5 [DevTools] Disable the tree list for now (#34606)
When there are no named Activities we should hide the tree side panel
(and the button to show it). Since it's not implemented yet there are
never any ones so it's always hidden.
2025-09-26 09:43:00 -04:00
Sebastian Markbåge
047715c4ba [Flight] Preload <img> and <link> using hints before they're rendered (#34604)
In Fizz and Fiber we emit hints for suspensey images and CSS as soon as
we discover them during render. At the beginning of the stream. This
adds a similar capability when a Host Component is known to be a Host
Component during the Flight render.

The client doesn't know that these resources are in the payload until it
parses that particular component which is lazy. So they need to be
hoisted with hints. We detect when these are rendered during Flight and
add them as hints. That allows you to consume a Flight payload to
preload prefetched content without having to render it.

`<link rel="preload">` can be hoisted more or less as is.

`<link rel="stylesheet">` we preload but we don't actually insert them
anywhere until they're rendered. We do these even for non-suspensey
stylesheets since we know that when they're rendered they're going to
start loading even if they're not immediately used. They're never lazy.

`<img src>` we only preload if they follow the suspensey image pattern
since otherwise they may be more lazy e.g. by if they're in the
viewport. We also skip if they're known to be inside `<picture>`. Same
as Fizz. Ideally this would preload the other `<source>` but it's
tricky.

The downside of this is that you might conditionally render something in
only one branch given a client component. However, in that case you're
already eagerly fetching the server component's data in that branch so
it's not too much of a stretch that you want to eagerly fetch the
corresponding resources as well. If you wanted it to be lazy, you
should've done a lazy fetch of the RSC.

We don't collect hints when any of these are wrapped in a Client
Component. In those cases you might want to add your own preload to a
wrapper Shared Component.

Everything is skipped if it's known to be inside `<noscript>`.

Note that the format context is approximate (see #34601) so it's
possible for these hints to overfetch or underfetch if you try to trick
it. E.g. by rendering Server Components inside a Client Component that
renders `<noscript>`.

---------

Co-authored-by: Josh Story <josh.c.story@gmail.com>
2025-09-25 23:44:14 -04:00
Eugene Choi
250f1b20e0 [playground] Fix useEffect on tabify (#34594)
There was a bug in the Compiler Playground related to the "Show
Internals" toggle due to a useEffect that was causing the tab names to
flicker from a rerender. Rewritten instead with a `<Suspense>` boundary
+ `use`.
2025-09-25 14:56:41 -04:00
Sebastian Markbåge
b0c1dc01ec [Flight] Add approximate parent context for FormatContext (#34601)
Flight doesn't have any semantically sound notion of a parent context.
That's why we removed Server Context. Each root can really start
anywhere in the tree when you refetch subtrees. Additionally when you
dedupe elements they can end up in multiple different parent contexts.

However, we do have a DEV only version of this with debugTask being
tracked for the nearest parent element to track the context of
properties inside of it.

To apply certain DOM specific hints and optimizations when you render
host components we need some information of the context. This is usually
very local so doesn't suffer from the likelihood that you refetch in the
middle. We'll also only use this information for optimistic hints and
not hard semantics so getting it wrong isn't terrible.

```
<picture>
  <img />
</picture>
<noscript>
  <p>
    <img />
  </p>
</noscript>
```

For example, in these cases we should exclude preloading the image but
we have to know if that's the scope we're in.

We can easily get this wrong if they're split or even if they're wrapped
in client components that we don't know about like:

```
<NoScript>
  <p>
    <img />
  </p>
</NoScript>
```

However, getting it wrong in either direction is not the end of the
world. It's about covering the common cases well.
2025-09-25 12:05:47 -04:00
Sebastian Markbåge
6eb5d67e9c [Fizz] Outline a Suspense Boundary if it has Suspensey CSS or Images (#34552)
We should favor outlining a boundary if it contains Suspensey CSS or
Suspensey Images since then we can load that content separately and not
block the main content. This also allows us to animate the reveal.

For example this should be able to animate the reveal even though the
actual HTML content isn't large in this case it's worth outlining so
that the JS runtime can choose to animate this reveal.

```js
<ViewTransition>
  <Suspense>
    <img src="..." />
  </Suspense>
</ViewTransition>
```

For Suspensey Images, in Fizz, we currently only implement the suspensey
semantics when a View Transition is running. Therefore the outlining
only applies if it appears inside a Suspense boundary which might
animate. Otherwise there's no point in outlining. It is also only if the
Suspense boundary itself might animate its appear and not just any
ViewTransition. So the effect is very conservative.

For CSS it applies even without ViewTransition though, since it can help
unblock the main content faster.
2025-09-25 09:38:41 -04:00
Hendrik Liebau
ac2c1a5a58 [Flight] Ensure blocked debug info is handled properly (#34524)
This PR ensures that server components are reliably included in the
DevTools component tree, even if debug info is received delayed, e.g.
when using a debug channel. The fix consists of three parts:

- We must not unset the debug chunk before all debug info entries are
resolved.
- We must ensure that the "RSC Stream" IO debug info entry is pushed
last, after all other entries were resolved.
- We need to transfer the debug info from blocked element chunks onto
the lazy node and the element.

Ideally, we wouldn't even create a lazy node for blocked elements that
are at the root of the JSON payload, because that would basically wrap a
lazy in a lazy. This optimization that ensures that everything around
the blocked element can proceed is only needed for nested elements.
However, we also need it for resolving deduped references in blocked
root elements, unless we adapt that logic, which would be a bigger lift.

When reloading the Flight fixture, the component tree is now displayed
deterministically. Previously, it would sometimes omit synchronous
server components.

<img width="306" height="565" alt="complete"
src="https://github.com/user-attachments/assets/db61aa10-1816-43e6-9903-0e585190cdf1"
/>

---------

Co-authored-by: Sebastian Markbage <sebastian@calyptus.eu>
2025-09-25 15:13:15 +02:00
Sebastian "Sebbie" Silbermann
c44fbf43b1 [DevTools] Fix instrumentation error when reconciling promise-as-a-child (#34587) 2025-09-24 22:50:12 +02:00
Joseph Savona
8ad773b1f3 [compiler] Add support for commonjs (#34589)
We previously always generated import statements for any modules that
had to be required, notably the `import {c} from
'react/compiler-runtime'` for the memo cache function. However, this
obviously doesn't work when the source is using commonjs. Now we check
the sourceType of the module and generate require() statements if the
source type is 'script'.

I initially explored using
https://babeljs.io/docs/babel-helper-module-imports, but the API design
was unfortunately not flexible enough for our use-case. Specifically,
our pipeline is as follows:
* Compile individual functions. Generate candidate imports,
pre-allocating the local names for those imports.
* If the file is compiled successfully, actually add the imports to the
program.

Ie we need to pre-allocate identifier names for the imports before we
add them to the program — but that isn't supported by
babel-helper-module-imports. So instead we generate our own require()
calls if the sourceType is script.
2025-09-24 11:17:42 -07:00
Sebastian "Sebbie" Silbermann
58d17912e8 Fix failing React DevTools regression tests (#34585) 2025-09-24 19:08:13 +02:00
Joseph Savona
2c6d92fd80 [compiler] Name anonymous functions from inlined useCallbacks (#34586)
@eps1lon flagged this case. Inlined useCallback has an extra LoadLocal
indirection which caused us not to add a name. While I was there I added
some extra checks to make sure we don't generate names for a given node
twice (just in case).
2025-09-24 09:18:16 -07:00
Sebastian Markbåge
e233218359 Track "Animating" Entry for Gestures while the Gesture is Still On-going (#34548)
Stacked on #34546.

Same as #34538 but for gestures.

Includes various fixes.

This shows how it ends with a Transition when you release in the
committed state. Note how the Animation of the Gesture continues until
the Transition is done so that the handoff is seamless.

<img width="853" height="134" alt="Screenshot 2025-09-20 at 7 37 29 PM"
src="https://github.com/user-attachments/assets/6192a033-4bec-43b9-884b-77e3a6f00da6"
/>
2025-09-24 11:26:03 -04:00
Sebastian Markbåge
05b61f812a Add Gesture Track in Performance Tab (#34546) 2025-09-24 17:20:14 +02:00
Sebastian Markbåge
e0c421ab71 Include SyncLane in includesBlockingLane helper (#34543)
This helper weirdly doesn't include the sync lane.

Everywhere we use it we have to check the sync lane separately. We can
simplify things by simply including the sync lane.

This fixes a lack of optimization because we should not check the store
consistency for a `flushSync` render.


d91d28c8ba/packages/react-reconciler/src/ReactFiberHooks.js (L1691-L1693)
2025-09-24 09:34:35 -04:00
Ruslan Lesiutin
2ee6147510 [DevTools] Switch sourcemap-codec dependency (#34569)
[sourcemap-codec](https://www.npmjs.com/package/sourcemap-codec)
(deprecated) ->
[@jridgewell/sourcemap-codec](https://www.npmjs.com/package/@jridgewell/sourcemap-codec)

Validated that symbolication still works.
2025-09-24 06:11:53 -07:00
Jordan Brown
e02c173fa5 [lint] Allow useEffectEvent in useLayoutEffect and useInsertionEffect (#34492)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34492).
* #34497
* __->__ #34492
2025-09-23 16:56:09 -04:00
Ruslan Lesiutin
24a2ba03fb [DevTools] fix: dedupe file fetch requests and define a timeout (#34566)
If there is a large owner stack, we could potentially spam multiple
fetch requests for the same source map. This adds a simple deduplication
logic, based on URL.

Also, this adds a timeout of 60 seconds to all fetch requests initiated
by fileFetcher content script.
2025-09-23 11:38:07 -07:00
Sebastian "Sebbie" Silbermann
012b371cde [DevTools] Handle LegacyHidden Fibers like Offscreen Fibers. (#34564) 2025-09-23 20:14:53 +02:00
Jack Pope
83c88ad470 Handle fabric root level fragment with compareDocumentPosition (#34533)
The root instance doesn't have a canonical property so we were not
returning a public instance that we can call compareDocumentPosition on
when a Fragment had no other host parent in Fabric. In this case we need
to get the ReactNativeElement from the ReactNativeDocument.

I've also added test coverage for this case in DOM for consistency,
though it was already working there because we use DOM elements as root.
This same test will be copied to RN using Fantom.
2025-09-23 10:56:43 -04:00
Sebastian "Sebbie" Silbermann
cad813ac1e Fix CI from stale merge (#34555) 2025-09-23 08:49:16 +02:00
Sebastian "Sebbie" Silbermann
720bb13069 [compiler] Export PluginOptions as a type that can be used in input positions (#34550) 2025-09-22 18:28:19 +02:00
Eugene Choi
1eca9a2747 [playground] Add compiler playground tests (#34528)
<!--
  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?
-->

Added more tests for the compiler playground with the addition of the
new config editor and "Show Internals" button. Added testing to check
for incomplete store params in the URL, toggle functionality, and
correct errors showing for syntax/validation errors in the config
overrides.
2025-09-22 12:11:45 -04:00
Sebastian "Sebbie" Silbermann
cd85bb5616 Include Fizz runtime diff in CI (#34525) 2025-09-22 17:09:50 +02:00
Sebastian "Sebbie" Silbermann
07e4974bad [compiler] Don't leak global __DEV__ type (#34551) 2025-09-22 16:51:57 +02:00
Sebastian Markbåge
d91d28c8ba Use the JSX of the ViewTransition as the Stack Trace of "Animating" Traces (#34539)
Stacked on #34538.

Track the Task of the first ViewTransition that we detected as
animating. Use this as the Task as "Starting Animation", "Animating"
etc. That way you can see which ViewTransition spawned the Animation.
Although it's likely to be multiple.

<img width="757" height="393" alt="Screenshot 2025-09-19 at 10 19 18 PM"
src="https://github.com/user-attachments/assets/a6cdcb89-bd02-40ec-b3c3-11121c29e892"
/>
2025-09-20 11:11:27 -04:00
Sebastian Markbåge
b4fe1e6c7e Log the time until the Animation finishes as "Animating" (#34538)
Stacked on #34522.

<img width="1025" height="200" alt="Screenshot 2025-09-19 at 6 37 28 PM"
src="https://github.com/user-attachments/assets/f25900f6-6503-48b1-876d-bd6697a29c6f"
/>

We already cover the time between "Starting Animation" and "Remaining
Effects" as "Animating". However, if the effects are forced then we can
still be animating after that. This fills in that gap.

This also fills in the gap if another render starts before the animation
finishes on the same track. It'll mark the blank space between the
previous render finishing and the next render starting as "Animating".

This should correspond roughly to the native "Animations" track.
2025-09-20 11:10:42 -04:00
Sebastian Markbåge
b204edda3a Log Custom Reason for the Suspended Commit Track (#34522)
Stacked on #34511.

We currently log all Suspended Commit as "Suspended on Images or CSS"
but it can really be other reasons too now. Like waiting on the previous
View Transition. This allows the host config configure this reason.

Now when one animation starts before another one finishes we log that as
"Waiting for the previous Animation".

<img width="592" height="257" alt="Screenshot 2025-09-17 at 11 53 45 PM"
src="https://github.com/user-attachments/assets/817af8b5-37ae-46d8-bfd1-cd3fc637f3f3"
/>
2025-09-20 11:01:52 -04:00
Hendrik Liebau
115e3ec15f [ci] Document that full git shas are required for manual prereleases (#34537)
Triggering the "(Runtime) Publish Prereleases Manual" workflow with a
short git sha doesn't work. It needs the full sha. We might be able to
make it work with the short sha as well, but for now we can at least
document the restriction.
2025-09-20 08:09:44 +02:00
Sebastian Markbåge
565eb7888e Unwrap a reference to a Lazy value (#34535)
If we are referencing a lazy value that isn't explicitly lazy ($L...)
it's because we added it around an element that was blocked to be able
to defer things inside.

However, once that is unblocked we can start unwrap it and just use the
inner element instead for any future reference. The race condition is
still there since it's a race condition whether we added the wrapper in
the first place.

This just makes it consistent with unwrapping of the rest of the path.
2025-09-19 18:23:18 -04:00
Hendrik Liebau
d415fd3ed7 [Flight] Handle Lazy in renderDebugModel (#34536)
If we don't handle Lazy types specifically in `renderDebugModel`, all of
their properties will be emitted using `renderDebugModel` as well. This
also includes its `_debugInfo` property, if the Lazy comes from the
Flight client. That array might contain objects that are deduped, and
resolving those references in the client can cause runtime errors, e.g.:

```
TypeError: Cannot read properties of undefined (reading '$$typeof')
```

This happened specifically when an "RSC stream" debug info entry, coming
from the Flight client through IO tracking, was emitted and its
`debugTask` property was deduped, which couldn't be resolved in the
client.

To avoid actually initializing a lazy causing a side-effect, we make
some assumptions about the structure of its payload, and only emit
resolved or rejected values, otherwise we emit a halted chunk.
2025-09-19 23:38:11 +02:00
Jack Pope
5e3cd53f20 Update MAINTAINERS (#34534) 2025-09-19 15:49:08 -04:00
Janka Uryga
01cad9eaca [Flight] Support Async Modules in Turbopack Server References (#34531)
Seems like this was missed in
https://github.com/facebook/react/pull/31313
2025-09-19 12:12:37 -07:00
Eugene Choi
6eda534718 [playground] bug fixes & UX improvements (#34499)
<!--
  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

Made many small changes to the compiler playground to improve user
experience. Removed any "Loading" indicators that would flash in before
a component would finish loading in. Additionally, before users would
see the "Show Internals" button toggling from false to true if they had
set it at true previously. I was able to refactor the URL/local storage
loading so that the `Store` would be fully initialized before the
components would load in.

Attempted to integrate `<Activity>` into showing/hiding these different
editors, but the current state of [monaco
editors](https://github.com/suren-atoyan/monaco-react) does not allow
for this. I created an issue for them to address:
https://github.com/suren-atoyan/monaco-react/issues/753

Added a debounce to the config editor so every key type wouldn't cause
the output panel to respond instantly. Users can type for 500 ms before
an error is thrown at them.

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

## How did you test this change?

Here is what loading the page would look like before (not sure why its
so blurry):


https://github.com/user-attachments/assets/58f4281a-cc02-4141-b9b5-f70d6ace12a2


Here is how it looks now:


https://github.com/user-attachments/assets/40535165-fc7c-44fb-9282-9c7fa76e7d53

Here is the debouncing:


https://github.com/user-attachments/assets/e4ab29e4-1afd-4249-beca-671fb6542f5e



<!--
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.
-->
2025-09-18 15:44:25 -04:00
Hendrik Liebau
c03a51d836 Move getDebugInfo test util function to internal-test-utils (#34523)
In an upstack PR, I need `getDebugInfo` in another test file, so I'm
moving it to `internal-test-utils` so it can be shared.
2025-09-18 21:32:36 +02:00
Sebastian Markbåge
ad578aa01f Log Suspended startViewTransition Phase (#34511)
Stacked on #34510.

The "Commit" phase for a View Transition starts before the snapshot
phase (before mutation) and then stretches into the async gap of
`startViewTransition`, encompasses the mutation phase inside of its
update callback and finally the layout phase.

However, between the mutation phase and the layout phase we may suspend
the start of the view transition on fonts and/or images. In that case we
now split the Commit phase into first one before we suspend and then we
log "Waiting for Images and/or Fonts" and then another Commit phase
around the layout effects.

<img width="897" height="119" alt="Screenshot 2025-09-16 at 11 37 26 PM"
src="https://github.com/user-attachments/assets/0fe21388-bb48-4456-a594-62227d12d9b7"
/>
2025-09-18 15:25:41 -04:00
Sebastian "Sebbie" Silbermann
03a96c75db [DevTools] Record Suspense node for roots in legacy renderers (#34516) 2025-09-18 18:50:23 +02:00
Sebastian "Sebbie" Silbermann
755cebad6b [DevTools] Elevate Suspense rects to visualize hierarchy (#34455) 2025-09-18 18:37:00 +02:00
zeki
581321160f [Compiler Bug] Complier mark ts instantiation expression as reorderable in build hir (#34488)
<!--
  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?
--> The React Compiler rejected a default parameter that contains a
TSInstantiationExpression with the todo message that the expression
cannot be safely reordered. This change teaches the reorder check in
BuildHIR.ts to treat TSInstantiationExpression as reorderable. This is
safe because TypeScript instantiation only affects types and is erased
at runtime, so it has no side effects and does not change semantics.

## How did you test this change?

```
Set-Content testfilter.txt 'ts-instantiation-default-param'

yarn test --filter --update

yarn test --filter
```


<!--
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.
--> I added a fixture: 
>
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-instantiation-default-param.js
2025-09-18 09:34:47 -07:00
Joseph Savona
1bcdd224b1 [compiler] Don't show hint about ref-like naming if we infer another type (#34521)
Some components accept a union of a ref callback function or ref object.
In this case we may infer the type as a function due to the presence of
invoking the ref callback function. In that case, we currently report a
"Hint: name `fooRef` as "ref" or with a "-Ref" suffix..." even though
the variable is already named appropriately — the problem is that we
inferred a non-ref type. So here we check the type and don't report this
hint if we inferred another type.
2025-09-18 09:26:10 -07:00
Sebastian Markbåge
84af9085c1 Log Performance Track Entries for View Transitions (#34510)
Stacked on #34509.

View Transitions introduces a bunch of new types of gaps in the commit
phase which needs to be logged differently in the performance track.

One thing that can happen is that a `flushSync` update forces the View
Transition to abort before it has started if it happens in the gap
before the transition is ready. In that case we log "Interrupted View
Transition".

Otherwise, when we're done in `startViewTransition` there's some work to
finalize the animations before the `ready` calllback. This is logged as
"Starting Animation".

Then there's a gap before the passive effects fire which we log as
"Animating". This can be long unless they're forced to flush early e.g.
due to another lane updating.

The "Animating" track should then pick up which doesn't do yet. This one
is tricky because this is after the actual commit phase and needs to be
interrupted by new renders which themselves can be suspended on the
animation finshing.

This PR is just a subset of all the cases. Will need a lot more work.

<img width="679" height="161" alt="Screenshot 2025-09-16 at 10 19 06 PM"
src="https://github.com/user-attachments/assets/0407372d-aaed-41f5-a262-059b2686ae87"
/>
2025-09-17 13:06:30 -04:00
Sebastian "Sebbie" Silbermann
128abcfa01 [DevTools] Don't inline workers for extensions (#34508) 2025-09-17 17:59:55 +02:00
Sebastian Markbåge
e3c9656d20 Ensure Performance Track are Clamped and Don't overlap (#34509)
This simplifies the logic for clamping the start times of various
phases. Instead of checking in multiple places I ensure we compute a
value for each phase that is then clamped to the next phase so they
don't overlap. If they're zero they're not printed.

I also added a name for all the anonymous labels. Those are mainly
fillers for sync work that should be quick but it helps debugging if we
can name them.

Finally the real fix is to update the clamp time which previously could
lead to overlapping entries for consecutive updates when a previous
update never finalized before the next update.
2025-09-17 10:52:02 -04:00
Sebastian "Sebbie" Silbermann
27b4076ab0 [DevTools] Use a single Webpack config for the extensions (#34513) 2025-09-17 15:45:25 +02:00
Sebastian "Sebbie" Silbermann
81d66927af [DevTools] Stop polyfilling Buffer (#34512) 2025-09-17 15:36:21 +02:00
Sebastian "Sebbie" Silbermann
6a4c8f51fa [DevTools] Store Webpack stats when building extensions (#34514) 2025-09-17 15:03:12 +02:00
Sebastian "Sebbie" Silbermann
16df13b84c [DevTools] Minify backend (#34507) 2025-09-17 14:52:32 +02:00
Joseph Savona
7899729130 [compiler] Option to treat "set-" prefixed callees as setState functions (#34505)
Calling setState functions during render can lead to extraneous renders
or even infinite loops. We also have runtime detection for loops, but
static detection is obviously even better.

This PR adds an option to infer identifers as setState functions if both
the following conditions are met:
- The identifier is named starting with "set"
- The identifier is used as the callee of a call expression

By inferring values as SetState type, this allows our existing
ValidateNoSetStateInRender rule to flag calls during render, disallowing
examples like the following:

```js
function Component({setParentState}) {
  setParentState(...);
  ^^^^^^^^^^^^^^ Error: Cannot call setState in render
}
```
2025-09-16 15:48:27 -07:00
Sebastian "Sebbie" Silbermann
a51f925217 [DevTools] Only check if we previously removed IO if its removal failed (#34506) 2025-09-16 19:55:03 +02:00
Sebastian "Sebbie" Silbermann
941cd803a7 [DevTools] Don't keep stale root instances we never mounted around (#34504) 2025-09-16 19:17:28 +02:00
Sebastian "Sebbie" Silbermann
851bad0c88 [DevTools] Ignore repeated removals of the same IO (#34495) 2025-09-16 18:54:52 +02:00
Sebastian Markbåge
5e0c951b58 Add forwards fill mode to animations in view transition fixture (#34502)
It turns out that View Transitions can sometimes overshoot and then we
need to ensure it fills. It can otherwise sometimes flash in Chrome.

This is something users might hit as well.
2025-09-16 10:20:40 -04:00
Sebastian Markbåge
348a4e2d44 [Fiber] Wait for suspensey image in the viewport before starting an animation (#34500)
Stacked on #34486.

If we gave up on loading suspensey images for blocking the commit (e.g.
due to #34481), we can still block the view transition from committing
to allow an animation to include the image from the start.

At this point we have more information about the layout so we can
include only the images that are within viewport in the calculation
which may end up with a different answer.

This only applies when we attempt to run an animation (e.g. something
mutated inside a `<ViewTransition>` in a Transition). We could attempt a
`startViewTransition` if we gave up on the suspensey images just so that
we could block it even if no animation would be running.

However, this point the screen is frozen and you can no longer have sync
updates interrupt so ideally we would have already blocked the commit
from happening in the first place.

The reason to have two points where we block is that ideally we leave
the UI responsive while blocking, which blocking the commit does. In the
simple case of all images or a single image being within the viewport,
that's favorable. By combining the techniques we only end up freezing
the screen in the special case that we had a lot of images added outside
the viewport and started an animation with some image inside the
viewport (which presumably is about to finish anyway).
2025-09-15 18:11:04 -04:00
Sebastian Markbåge
5d49b2b7f4 [Fiber] Track SuspendedState on stack instead of global (#34486)
Stacked on #34481.

We currently track the suspended state temporarily with a global which
is safe as long as we always read it during a sync pass. However, we
sometimes read it in closures and then we have to be carefully to pass
the right one since it's possible another commit on a different root has
started at that point. This avoids this footgun.

Another reason to do this is that I want to read it in
`startViewTransition` which is in an async gap after which point it's no
longer safe. So I have to pass that through the `commitRoot` bound
function.
2025-09-15 16:10:47 -04:00
Sebastian Markbåge
ae22247dce [Fiber] Don't wait on Suspensey Images if we guess that we don't load them all in time anyway (#34481)
Stacked on #34478.

In general we don't like to deal with timeouts in suspense world. We've
had that in the past but in general it doesn't work well because if you
have a timeout and then give up you made everything wait longer for no
benefit at the end. That's why the recommendation is to remove a
Suspense boundary if you expect it to be fast and add one if you expect
it to be slow. You have to estimate as the developer.

Suspensey images suffer from this same problem. We want to apply
suspensey images to as much as possible so that it's the default to
avoid flashing because if just a few images flash it's still almost as
bad as all of them. However, we do know that it's also very common to
use images and on a slow connection or many images, it's not worth it so
we have the timeout to eventually give up.

However, this means that in cases that are always slow or connections
that are always slow, you're always punished for no reason.

Suspensey images is mainly a polish feature to make high end experiences
on high end connections better but we don't want to unnecessarily punish
all slow connections in the process or things like lots of images below
the viewport.

This PR adds an estimate for whether or not we'll likely be able to load
all the images within the timeout on a high end enough connection. If
not, we'll still do a short suspend (unless we've already exceeded the
wait time adjusted for #34478) to allow loading from cache if available.

This estimate is based on two heuristics:

1) We compute an estimated bandwidth available on the current device in
mbps. This is computed from performance entries that have loaded static
resources already on the site. E.g. this can be other images, css, or
scripts. We see how long they took. If we don't have any entries (or if
they're all cross-origin in Safari) we fallback to
`navigator.connection.downlink` in Chrome or a 5mbps default in
Firefox/Safari.
2) To estimate how many bytes we'll have to download we use the
width/height props of the img tag if available (or a 100 pixel default)
times the device pixel ratio. We assume that a good img implementation
downloads proper resolution image for the device and defines a
width/height up front to avoid layout trash. Then we estimate that it
takes about 0.25 bytes per pixel which is somewhat conservative
estimate.

This is somewhat conservative given that the image could've been
preloaded and be better compressed.

So it really only kicks in for high end connections that are known to
load fast.

In a follow up, we can add an additional wait for View Transitions that
does the same estimate but only for the images that turn out to be in
viewport.
2025-09-15 16:08:59 -04:00
Sebastian Markbåge
e3f191803c [Fiber] Adjust the suspensey image/css timeout based on already elapsed time (#34478)
Currently suspensey images doesn't account for how long we've already
been waiting. This means that you can for example wait for 300ms for the
throttle + 500ms for the images. If a Transition takes a while to
complete you can also wait that time + an additional 500ms for the
images.

This tracks the start time of a Transition so that we can count the
timeout starting from when the user interacted or when the last fallback
committed (which is where the 300ms throttle is computed from). Creating
a single timeline.

This also moves the timeout to a central place which I'll use in a
follow up.
2025-09-15 16:05:20 -04:00
Cody Olsen
e12b0bdc3b [compiler]: add @tanstack/react-virtual to known incompatible libraries (#34493)
Replaces #31820. #34027 added a check for `@tanstack/react-table`, but
not `@tanstack/react-virtual`.
In our testing `@tanstack/react-virtual`'s `useVirtualizer` returns
functions that cannot be memoized, [this is also documented in the
community](https://github.com/TanStack/virtual/issues/736#issuecomment-3065658277).
2025-09-15 11:53:45 -07:00
Ruslan Lesiutin
92d7ad5dd9 [DevTools] fix: validate url in file fetcher bridging calls (#34498)
This was prone to races and sometimes messed up symbolication when
multiple source maps were fetched simultaneously.
2025-09-15 18:14:09 +01:00
Eugene Choi
67a44bcd1b Playground applied configs (#34474)
<!--
  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
Added an "Applied Configs" section under the Config Overrides panel.
Users will now be able to see the full list of configs applied to the
compiler in the playground. Adds greater discoverability for config
options to override as well. Updated the default config as well to be a
commented config option, so users will start with empty overrides.

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

## How did you test this change?


https://github.com/user-attachments/assets/1a57b2d5-0405-4fc8-9990-1747c30181c0


<!--
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.
-->
2025-09-15 12:13:28 -04:00
Sebastian "Sebbie" Silbermann
3fa927b674 Fix some DevTools regression test actions and assertions (#34459) 2025-09-15 15:31:58 +02:00
Sebastian "Sebbie" Silbermann
47664deb8e Allow running download_devtools_regression_build.js on a clean repo (#34456) 2025-09-13 11:07:36 +02:00
Sebastian "Sebbie" Silbermann
5502d85cc7 [DevTools] Unmount fallbacks in the context of the parent Suspense (#34475)
Co-authored-by: Ruslan Lesiutin <hoxy@meta.com>
2025-09-13 11:03:32 +02:00
Ricky
8a8e9a7edf move devtools notify to different channel (#34476) 2025-09-12 14:14:25 -04:00
Ricky
68f00c901c Release Activity in Canary (#34374)
## Overview

This PR ships `<Activity />` to the `react@canary` release channel for
final feedback and prepare for semver stable release.

## What this means

Shipping `<Activity />` to canary means it has gone through extensive
testing in production, we are confident in the stability of the feature,
and we are preparing to release it in a future semver stable version.

Libraries and frameworks following the [Canary
Workflow](https://react.dev/blog/2023/05/03/react-canaries) should begin
implementing and testing the feature.

## Why we follow the Canary Workflow

To prepare for semver stable, libraries should test canary features like
`<Activity>` with `react@canary` to confirm compatibility and prepare
for the next semver release in a myriad of environments and
configurations used throughout the React ecosystem. This provides
libraries with ample time to catch any issues we missed before slamming
them with problems in the wider semver release.

Since these features have already gone through extensive production
testing, and we are confident they are stable, frameworks following the
[Canary Workflow](https://react.dev/blog/2023/05/03/react-canaries) can
also begin adopting canary features like `<Activity />`.

This adoption is similar to how different Browsers implement new
proposed browser features before they are added to the standard. If a
frameworks adopts a canary feature, they are committing to stability for
their users by ensuring any API changes before a semver stable release
are opaque and non-breaking to their users.

Apps not using a framework are also free to adopt canary features like
Activity as long as they follow the [Canary
Workflow](https://react.dev/blog/2023/05/03/react-canaries), but we
generally recommend waiting for a semver stable release unless you have
the capacity to commit to following along with the canary changes and
debugging library compatibility issues.

Waiting for semver stable means you're able to benefit from libraries
testing and confirming support, and use semver as signal for which
version of a library you can use with support of the feature.

## Docs 

Check out the ["React Labs: View Transitions, Activity, and
more"](https://react.dev/blog/2025/04/23/react-labs-view-transitions-activity-and-more#activity)
blog post, and [the new docs for
`<Activity>`](https://react.dev/reference/react/Activity) for more info.

## TODO
- [x] Bump Activity docs to Canary
https://github.com/reactjs/react.dev/pull/7974

---------

Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
2025-09-12 12:47:40 -04:00
Sebastian Markbåge
93d7aa69b2 [Fiber] Add context for the display: inline warning (#34461)
This warning doesn't execute within any particular context so doesn't
have a stack.

Pick the fiber of the child if it exists, otherwise the parent.

<img width="846" height="316" alt="Screenshot 2025-09-10 at 12 38 28 PM"
src="https://github.com/user-attachments/assets/7ab283a9-6e11-428d-9def-38f80ca958ef"
/>
2025-09-12 11:55:25 -04:00
Sebastian Markbåge
20e5431747 [Flight][Fiber] Encode owner in the error payload in dev and use it as the Error's Task (#34460)
When we report an error we typically log the owner stack of the thing
that caught the error. Similarly we restore the `console.createTask`
scope of the catching component when we call `reportError` or
`console.error`.

We also have a special case if something throws during reconciliation
which uses the Server Component task as far as we got before we threw.


https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactChildFiber.js#L1952-L1960

Chrome has since fixed it (on our request) that the Error constructor
snapshots the Task at the time the constructor was created and logs that
in `reportError`. This is a good thing since it means we get a coherent
stack. Unfortunately, it means that the fake Errors that we create in
Flight Client gets a snapshot of the task where they were created so
when they're reported in the console they get the root Task instead of
the Task of the handler of the error.

Ideally we'd transfer the Task from the server and restore it. However,
since we don't instrument the Error object to snapshot the owner and we
can't read the native Task (if it's even enabled on the server) we don't
actually have a correct snapshot to transfer for a Server Component
Error. However, we can use the parent's task for where the error was
observed by Flight Server and then encode that as a pseudo owner of the
Error.

Then we use this owner as the Task which the Error is created within.
Now the client snapshots that Task which is reported by `reportError` so
now we have an async stack for Server Component errors again. (Note that
this owner may differ from the one observed by `captureOwnerStack` which
gets the nearest Server Component from where it was caught. We could
attach the owner to the Error object and use that owner when calling
`onCaughtError`/`onUncaughtError`).

Before:

<img width="911" height="57" alt="Screenshot 2025-09-10 at 10 57 54 AM"
src="https://github.com/user-attachments/assets/0446ef96-fad9-4e17-8a9a-d89c334233ec"
/>

After:

<img width="910" height="128" alt="Screenshot 2025-09-10 at 11 06 20 AM"
src="https://github.com/user-attachments/assets/b30e5892-cf40-4246-a588-0f309575439b"
/>

Similarly, there are Errors and warnings created by ChildFiber itself.
Those execute in the scope of the general render of the parent Fiber.
They used to get the scope of the nearest client component parent (e.g.
div in this case) but that's the parent of the Server Component. It
would be too expensive to run every level of reconciliation in its own
task optimistically, so this does it only when we know that we'll throw
or log an error that needs this context. Unfortunately this doesn't
cover user space errors (such as if an iterable errors).

Before:

<img width="903" height="298" alt="Screenshot 2025-09-10 at 11 31 55 AM"
src="https://github.com/user-attachments/assets/cffc94da-8c14-4d6e-9a5b-bf0833b8b762"
/>

After:

<img width="1216" height="252" alt="Screenshot 2025-09-10 at 11 50
54 AM"
src="https://github.com/user-attachments/assets/f85f93cf-ab73-4046-af3d-dd93b73b3552"
/>

<img width="412" height="115" alt="Screenshot 2025-09-10 at 11 52 46 AM"
src="https://github.com/user-attachments/assets/a76cef7b-b162-4ecf-9b0a-68bf34afc239"
/>
2025-09-12 11:55:07 -04:00
Eugene Choi
1a27af3607 [playground] Update the playground UI (#34468)
<!--
  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

Updated the UI of the React compiler playground. The config, Input, and
Output panels will now span the viewport width when "Show Internals" is
not toggled on. When "Show Internals" is toggled on, the old vertical
accordion tabs are still used. Going to add support for the "Applied
Configs" tabs underneath the "Config Overrides" tab next.

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

## How did you test this change?


https://github.com/user-attachments/assets/b8eab028-f58c-4cb9-a8b2-0f098f2cc262


<!--
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.
-->
2025-09-12 11:43:04 -04:00
Ruslan Lesiutin
0e10ee906e [Reconciler] Set ProfileMode for Host Root Fiber by default in dev (#34432)
Requiring DevTools to be present for dev builds seems like an overkill,
let's enable the instrumentation by default.

Nothing changes for profiling or production artifacts.
2025-09-12 12:20:39 +01:00
Ruslan Lesiutin
0c813c528d [Tracks]: display method name and component name for updates in DEV (#34463)
For every "Update" entry we are going to add properties that will be
displayed when the user clicks on that entry: name of the method that
caused this first update and name of the component where this update
happened.

We could use the name of the component as a deeplink to React DevTools
components panel in the future, once we support stable identificators on
Fibers.

<img width="1444" height="530" alt="Screenshot 2025-09-10 at 18 31 10"
src="https://github.com/user-attachments/assets/7f9af037-2e7f-4e7b-9b7e-bf9f7d5a6e72"
/>
<img width="2088" height="530" alt="Screenshot 2025-09-10 at 18 24 21"
src="https://github.com/user-attachments/assets/f557a173-bd9b-43f7-9333-74066f433ced"
/>
<img width="2088" height="530" alt="Screenshot 2025-09-10 at 18 26 04"
src="https://github.com/user-attachments/assets/ff37d13f-bbe3-4f85-800e-81aa3aed7833"
/>
2025-09-12 11:34:41 +01:00
Sebastian "Sebbie" Silbermann
a9ad64c852 [DevTools] Stop mounting empty roots (#34467) 2025-09-11 20:00:53 +02:00
Sebastian "Sebbie" Silbermann
7fc888dde2 [DevTools] Stop recording reorders in disconnected subtrees (#34464) 2025-09-11 19:13:14 +02:00
Sebastian "Sebbie" Silbermann
67415c8c4a [DevTools] Stop using native title for buttons/icons (#34379) 2025-09-11 18:49:35 +02:00
Hendrik Liebau
f3a803617e [Flight] Ensure async info owners are outlined properly (#34465)
When we emit objects of type `ReactAsyncInfo`, we need to make sure that
their owners are outlined, using `outlineComponentInfo`. Otherwise we
would end up accidentally emitting stashed fields that are not part of
the transport protocol, specifically `debugStack`, `debugTask`, and
`debugLocation`. This would lead to runtime errors in the client, when
for example, the stack for a `debugLocation` is processed in
`buildFakeCallStack`, but the stack was actually omitted from the RSC
payload, because for those fields we don't ensure that the object limit
is increased by the length of the stack, as we do when we're emitting
the `stack` of a `ReactComponentInfo` object in `outlineComponentInfo`.
2025-09-11 18:10:25 +02:00
Eugene Choi
fe84397e81 [compiler][playground] (4/N) Config override panel (#34436)
<!--
  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

Removed the old `OVERRIDE` pragma to make the source of truth for config
overrides in the left-hand pane. Now, it will automatically update the
output pane each time there is an edit to the config. The old pragma
format is still supported, but it will be overwritten by the config pane
if they are modifying the same flags. Removed the gating on the config
panel so now all users will automatically be able to view it, but it
will be initially collapsed.

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

## How did you test this change?


https://github.com/user-attachments/assets/9d4512b9-e203-4ce0-ae95-dd96ff03bbc1


<!--
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.
-->
2025-09-11 11:51:32 -04:00
Sebastian "Sebbie" Silbermann
b1c519f3d4 [DevTools] Only show boundaries with unique suspenders by default in the timeline (#34397) 2025-09-11 11:33:05 +02:00
Sebastian "Sebbie" Silbermann
8c1501452c [DevTools] Preserve Suspense lineage when clicking through breadcrumbs (#34422) 2025-09-11 10:54:25 +02:00
Joseph Savona
bd9e6e0bed [compiler] More flexible/helpful lazy ref initialization (#34449)
Two small QoL improvements inspired by feedback:
* `if (ref.current === undefined) { ref.current = ... }` is now allowed.
* `if (!ref.current) { ref.current = ... }` is still disallowed, but we
emit an extra hint suggesting the `if (!ref.current == null)` pattern.

I was on the fence about the latter. We got feedback asking to allow `if
(!ref.current)` but if your ref stores a boolean value then this would
allow reading the ref in render. The unary form is also less precise in
general due to sketchy truthiness conversions. I figured a hint is a
good compromise.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34449).
* __->__ #34449
* #34424
2025-09-10 13:42:01 -07:00
lauren
835b00908b [compiler] Allow setStates in use{Layout,Insertion}Effect where the set value is derived from a ref (#34462)
@stipsan found this issue where the compiler would bailout on the
`useLayoutEffect` examples in the React docs. While setState in an
effect is typically an anti-pattern due to the fact that it hurts
performance through cascading renders, the one scenario where it _is_
allowed is if the value being set flows from a ref.
2025-09-10 14:56:04 -04:00
Ruslan Lesiutin
e2ba45bb39 [DevTools] fix: keep search query in a local sync state (#34423)
When the search query changes, we kick off a transition that updates the
search query in a reducer for TreeContext. The search input is also
using this value for an `input` HTML element.

For a larger applications, sometimes there is a noticeable delay in
displaying the updated search query. This changes the approach to also
keep a local synchronous state that is being updated on a change
callback.
2025-09-10 18:38:47 +01:00
Sebastian Markbåge
886b3d36d7 [DevTools] Show suspended by subtree from Activity to next Suspense boundary (#34438)
Stacked on #34435.

This adds a method to get all suspended by filtered by a specific
Instance. The purpose of this is to power the feature when you filter by
Activity. This would show you the "root" within that Activity boundary.

This works by selecting the nearest Suspense boundary parent and then
filtering its data based on if all the instances for a given I/O info is
within the Activity instance. If something suspended within the Suspense
boundary but outside the Activity it's not included even if it's also
suspending inside the Activity since we assume it would've already been
loaded then.

Right now I wire this up to be a special case when you select an
Activity boundary same as when you select a Suspense boundary in the
Components tab but we could also only use this when you select the root
in the Suspense tab for example.
2025-09-10 09:44:51 -04:00
Sebastian Markbåge
288d428af1 [DevTools] Only show the highest end/byteSize I/O of RSC streams (#34435)
Stacked on #34425.

RSC stream info is split into one I/O entry per chunk. This means that
when a single instance or boundary depends on multiple chunks, it'll
show the same stream multiple times. This makes it so just the last one
is shown.

This is a special case for the name "RSC stream" but ideally we'd more
explicitly model the concept of awaiting only part of a stream.

<img width="667" height="427" alt="Screenshot 2025-09-09 at 2 09 43 PM"
src="https://github.com/user-attachments/assets/890f6f61-4657-4ca9-82fd-df55a696bacc"
/>

Another remaining issue is that it's possible for an intermediate chunk
to be depended on by just a child boundary. In that case that can be
considered a "unique suspender" even though the parent depends on a
later one. Ideally it would dedupe on everything below. Could also model
it as every Promise depends on its chunk and every previous chunk.
2025-09-10 09:08:36 -04:00
Sebastian Markbåge
a34c5dff15 Ignore generic InvalidStateError in View Transitions (#34450)
Fixes #34098.

There's an issue in Chrome where the `InvalidStateError` always has the
same error message. The spec doesn't specify the error message to use
but it's more useful to have a specific one for each case like Safari
does.

One reason it's better to have a specific error message is because the
browser console is not the main surface that people look for errors.
Chrome relies on a separate log also in the console. Frameworks has
built-in error dialogs that pop up first and that's where you see the
error and that dialog can't show something specific. Additionally, these
errors can't log something specific to servers in production logging. So
this is a bad strategy.

It's not good to have those error dialogs pop up for non-actionable
errors like when it doesn't start because the document was hidden. Since
we don't have more specific information we have no choice but to hide
all of them. This includes actionable things like duplicate names
(although we also have a React specific warning for that in the common
case).
2025-09-10 09:07:11 -04:00
Sebastian Markbåge
3bf8ab430e Add missing Activity export to development mode (#34439)
This is exported in the prod version of ReactServer experimental but not
the development version so we can't use it in fixtures from Server
Components.
2025-09-09 21:30:37 -04:00
Joseph Savona
acada3035f [compiler] Fix false positive hook return mutation error (#34424)
This was fun. We previously added the MaybeAlias effect in #33984 in
order to describe the semantic that an unknown function call _may_ alias
its return value in its result, but that we don't know this for sure. We
record mutations through MaybeAlias edges when walking backward in the
data flow graph, but downgrade them to conditional mutations. See the
original PR for full context.

That change was sufficient for the original case like

```js
const frozen = useContext();
useEffect(() => {
  frozen.method().property = true;
}, [...]);
```

But it wasn't sufficient for cases where the aliasing occured between
operands:

```js
const dispatch = useDispatch();
<div onClick={(e) => {
  dispatch(...e.target.value)
  e.target.value = ...;
}} />
```

Here we would record a `Capture dispatch <- e.target` effect. Then
during processing of the `event.target.value = ...` assignment we'd
eventually _forward_ from `event` to `dispatch` (along a MaybeAlias
edge). But in #33984 I missed that this forward walk also has to
downgrade to conditional.

In addition to that change, we also have to be a bit more precise about
which set of effects we create for alias/capture/maybe-alias. The new
logic is a bit clearer, I think:

* If the value is frozen, it's an ImmutableCapture edge
* If the values are mutable, it's a Capture
* If it's a context->context, context->mutable, or mutable->context,
count it as MaybeAlias.
2025-09-09 14:07:47 -07:00
Sebastian Markbåge
969a9790ad [Flight] Track I/O Entry for the RSC Stream itself (#34425)
One thing that can suspend is the downloading of the RSC stream itself.
This tracks an I/O entry for each Promise (`SomeChunk<T>`) that
represents the request to the RSC stream. As the value we use the
`Response` for `createFromFetch` (or the `ReadableStream` for
`createFromReadableStream`). The start time is when you called those.

Since we're not awaiting the whole stream, each I/O entry represents the
part of the stream up until it got unblocked. However, in a production
environment with TLS packets and buffering in practice the chunks
received by the client isn't exactly at the boundary of each row. It's a
bit longer into larger chunks. From testing, it seems like multiples of
16kb or 64kb uncompressed are common. To simulate a production
environment we group into roughly 64kb chunks if they happen in rapid
sequence. Note that this might be too small to give a good idea because
of the throttle many boundaries might be skipped anyway so this might
show too many.

The React DevTools will see each I/O entry as separate but dedupe if an
outer boundary already depends on the same chunk. This deduping makes it
so that small boundaries that are blocked on the same chunk, don't get
treated as having unique suspenders. If you have a boundary with large
content, then that content will likely be in a separate chunk which is
not in the parent and then it gets marked as.

This is all just an approximation. The goal of this is just to highlight
that very large boundaries will very likely suspend even if they don't
suspend on any I/O on the server. In practice, these boundaries can
float around a lot and it's really any Suspense boundary that might
suspend but some are more likely than others which this is meant to
highlight.

It also just lets you inspect how many bytes needs to be transferred
before you can show a particular part of the content, to give you an
idea that it's not just I/O on the server that might suspend.

If you don't use the debug channel it can be misleading since the data
in development mode stream will have a lot more data in it which leads
to more chunking.

Similarly to "client references" these I/O infos don't have an "env"
since it's the client that has the I/O and so those are excluded from
flushing in the Server performance tracks.

Note that currently the same Response can appear many times in the same
Instance of SuspenseNode in DevTools when there are multiple chunks. In
a follow up I'll show only the last one per Response at any given level.

Note that when a separate debugChannel is used it has its own I/O entry
that's on the `_debugInfo` for the debug chunks in that channel.
However, if everything works correctly these should never leak into the
DevTools UI since they should never be propagated from a debug chunk to
the values waited by the runtime. This is easy to break though.
2025-09-09 16:46:11 -04:00
Joseph Savona
665de2ed28 [compiler] Improve name hints for outlined functions (#34434)
The previous PR added name hints for anonymous functions, but didn't
handle the case of outlined functions. Here we do some cleanup around
function `id` and name hints:
* Make `HIRFunction.id` a ValidatedIdentifierName, which involved some
cleanup of the validation helpers
* Add `HIRFunction.nameHint: string` as a place to store the generated
name hints which are not valid identifiers
* Update Codegen to always use the `id` as the actual function name, and
only use nameHint as part of generating the object+property wrapper for
debug purposes.

This ensures we don't conflate synthesized hints with real function
names. Then, we also update OutlineFunctions to use the function name
_or_ the nameHint as the input to generating a unique identifier. This
isn't quite as nice as the object form since we lose our formatting, but
it's a simple step that gives more context to the developer than `_temp`
does.

Switching to output the object+property lookup form for outlined
functions is a bit more involved, let's do that in a follow-up.
2025-09-09 12:14:09 -07:00
mofeiZ
eda778b8ae [compiler] Fix false positive memo validation (alternative) (#34319)
Alternative to #34276

---
(Summary taken from @josephsavona 's #34276)
Partial fix for #34262. Consider this example:

```js
function useInputValue(input) {
  const object = React.useMemo(() => {
    const {value} = transform(input);
    return {value};
  }, [input]);
  return object;
}
```

React Compiler breaks this code into two reactive scopes:
* One for `transform(input)`
* One for `{value}`

When we run ValidatePreserveExistingMemo, we see that the scope for
`{value}` has the dependency `value`, whereas the original memoization
had the dependency `input`, and throw an error that the dependencies
didn't match.

In other words, we're flagging the fact that memoized _better than the
user_ as a problem. The more complete solution would be to validate that
there is a subgraph of reactive scopes with a single input and output
node, where the input node has the same dependencies as the original
useMemo, and the output has the same outputs. That is true in this case,
with the subgraph being the two consecutive scopes mentioned above.

But that's complicated. As a shortcut, this PR checks for any
dependencies that are defined after the start of the original useMemo.
If we find one, we know that it's a case where we were able to memoize
more precisely than the original, and we don't report an error on the
dependency. We still check that the original _output_ value is able to
be memoized, though. So if the scope of `object` were extended, eg with
a call to `mutate(object)`, then we'd still correctly report an error
that we couldn't preserve memoization.

Co-authored-by: Joe Savona <joesavona@fb.com>
2025-09-09 14:26:52 -04:00
Jorge Cabiedes
1836b46fff [compiler] Have react-compiler eslint plugin return a RuleModule (#34421)
Eslint is expecting a map of [string] => RuleModule. Before we were
passing {rule: RuleModule, severity: ErrorSeverity} which was breaking
legacy Eslint configurations
2025-09-09 11:18:37 -07:00
Sebastian "Sebbie" Silbermann
eec50b17b3 [Flight] Only use debug component info for parent stacks (#34431) 2025-09-09 19:58:02 +02:00
Joseph Savona
a9410fb487 [compiler] Option to infer names for anonymous functions (#34410)
Adds a `@enableNameAnonymousFunctions` feature to infer helpful names
for anonymous functions within components and hooks. The logic is
inspired by a custom Next.js transform, flagged to us by @eps1lon, that
does something similar. Implementing this transform within React
Compiler means that all React (Compiler) users can benefit from more
helpful names when debugging.

The idea builds on the fact that JS engines try to infer helpful names
for anonymous functions (in stack traces) when those functions are
accessed through an object property lookup:

```js
({'a[xyz]': () => {
  throw new Error('hello!')
} }['a[xyz]'])()

// Stack trace:
Uncaught Error: hello!
    at a[xyz] (<anonymous>:1:26) // <-- note the name here
    at <anonymous>:1:60
```

The new NameAnonymousFunctions transform is gated by the above flag,
which is off by default. It attemps to infer names for functions as
follows:

First, determine a "local" name:
* Assigning a function to a named variable uses the variable name.
`const f = () => {}` gets the name "f".
* Passing the function as an argument to a function gets the name of the
function, ie `foo(() => ...)` get the name "foo()", `foo.bar(() => ...)`
gets the name "foo.bar()". Note the parenthesis to help understand that
it was part of a call.
* Passing the function to a known hook uses the name of the hook,
`useEffect(() => ...)` uses "useEffect()".
* Passing the function as a JSX prop uses the element and attr name, eg
`<div onClick={() => ...}` uses "<div>.onClick".

Second, the local name is combined with the name of the outer
component/hook, so the final names will be strings like `Component[f]`
or `useMyHook[useEffect()]`.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34410).
* #34434
* __->__ #34410
2025-09-09 10:22:19 -07:00
Sebastian "Sebbie" Silbermann
6b70072c4f [DevTools] Finalize heuristic for naming unnamed <Suspense> (#34428) 2025-09-09 17:56:26 +02:00
Ruslan Lesiutin
b2cff47472 [DevTools] feat: propagate fetchFileWithCaching from initialization options for Fusebox (#34429)
Each integrator: browser extension, Chrome DevTools Frontend fork,
Electron shell must define and provide `fetchFileWithCaching` in order
for DevTools to be able to fetch application resources, such as scripts
or source maps.

More specifically, if this is available, React DevTools will be able to
symbolicate source locations for component frames, owner stacks,
"suspended by" Promises call frames.

This will be available with the next release of React DevTools.
2025-09-09 13:00:53 +01:00
Sebastian "Sebbie" Silbermann
8943025358 [DevTools] Fix handling of host roots on mount (#34400) 2025-09-08 22:53:02 +02:00
Eugene Choi
3d9d22cbdb [playground] Fix CompilerError mismatch (#34420)
The compiler playground was crashing at any small syntax errors in the
`Input` panel due to updating the `CompilerErrorDetailOptions` type in
#34401. Updated the option to take in a `ErrorCategory` instead.

---------

Co-authored-by: lauren <poteto@users.noreply.github.com>
2025-09-08 15:06:54 -04:00
Eugene Choi
d4374b3ae3 [compiler] [playground] Show internals toggle (#34399)
<!--
  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

Added a "Show Internals" toggle switch to either show only the Config,
Input, Output, and Source Map tabs, or these tabs + all the additional
compiler options. The open/close state of these tabs will be preserved
(unless on page refresh, which is the same as the currently
functionality).


<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

## 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.
-->



https://github.com/user-attachments/assets/8eb0f69e-360c-4e9b-9155-7aa185a0c018
2025-09-08 14:21:03 -04:00
Joseph Savona
3f2a42a5de [compiler] Handle empty list of eslint suppression rules (#34323)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34323).
* #34276
* __->__ #34323
2025-09-08 10:33:10 -07:00
Sebastian Markbåge
294c33f34d [Flight] Always initialize a debug info array for each Chunk (#34419)
I'm about to add info for pretty much all of these anyway since they all
depend on the data stream itself.
2025-09-08 12:28:14 -04:00
Sebastian "Sebbie" Silbermann
3fb190f729 [DevTools] Avoid renders of stale Suspense store (#34396) 2025-09-08 11:42:03 +02:00
Joseph Savona
f5e96b9740 [compiler] Add missing source locations to statements, expressions (#34406)
Adds missing locations to all the statement kinds that we produce in
codegenInstruction(), and adds generic handling of source locations for
the nodes produced by codegenInstructionValue(). There are definitely
some places where we are still missing a location, but this should
address some of the known issues we've seen such as missing location on
`throw`.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34406).
* #34394
* __->__ #34406
* #34346
2025-09-06 11:14:30 -07:00
lauren
78992521a8 [compiler] Filter out disabled errors from being reported (#34409)
This PR stops error details of severity `ErrorSeverity.Off` from being
reported.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34409).
* __->__ #34409
* #34404
2025-09-06 13:07:23 -04:00
lauren
80d7aa17ad [compiler] Fix error description inconsistency (#34404)
Small fix to make all descriptions consistently printed with a single
period at the end.

Ran `grep -rn "description:" packages/babel-plugin-react-compiler/src
--include="*.ts" --exclude-dir="__tests__" | grep '\.\s*["\`]'` to find
all descriptions ending in a period and manually fixed them.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34404).
* #34409
* __->__ #34404
2025-09-06 13:07:02 -04:00
lauren
474f25842a [compiler] Migrate CompilerError.invariant to new CompilerDiagnostic infra (#34403)
Mechanical PR to migrate existing invariants to use the new
CompilerDiagnostic infra @josephsavona added. Will tackle the others at
a later time.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34403).
* #34409
* #34404
* __->__ #34403
2025-09-06 12:58:08 -04:00
lauren
1fef581e1a [compiler] Deprecate CompilerErrorDetail (#34402)
Now that we have a new CompilerDiagnostic type (which the CompilerError
aggregate can hold), the old CompilerErrorDetail type can be marked as
deprecated. Eventually we should migrate everything to the new
CompilerDiagnostic type.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34402).
* #34409
* #34404
* #34403
* __->__ #34402
* #34401
2025-09-06 12:41:54 -04:00
lauren
60d9b9740d [compiler] Derive ErrorSeverity from ErrorCategory (#34401)
With #34176 we now have granular lint rules created for each compiler
ErrorCategory. However, we had remnants of our old error severities
still in use which makes reporting errors quite clunky. Previously you
would need to specify both a category and severity which often ended up
being the same.

This PR moves severity definition into our rules which are generated
from our categories. For now I decided to defer "upgrading" categories
from a simple string to a sum type since we are only using severities to
map errors to eslint severity.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34401).
* #34409
* #34404
* #34403
* #34402
* __->__ #34401
2025-09-06 12:41:29 -04:00
KimCookieYa
c4e2508dad [react-devtools-shared] Fix URL construction when base URL is invalid (#34407)
### Problem
- Users encounter “Failed to construct 'URL': Invalid base URL” when
clicking the “View source” action in DevTools if the underlying base URL
is invalid.
- This exception originates from `new URL(relative, base)` and bubbles
up, interrupting the DevTools UI.
- Fixes GitHub issue
[#34317](https://github.com/facebook/react/issues/34317)

### Solution
- Wrap URL construction to:
  - First try `new URL(sourceMapAt, sourceURL)`.
  - If that fails, try `new URL(sourceMapAt)` as an absolute URL.
  - If both fail, return `null` (no symbolication) rather than throwing.
- This preserves normal behavior for valid bases and absolute URLs,
while avoiding crashes for invalid bases.

### Implementation details
- Updated `symbolicateSource` in
`packages/react-devtools-shared/src/symbolicateSource.js` to handle
invalid base URL scenarios without throwing.
- Added/verified tests in
`packages/react-devtools-shared/src/__tests__/utils-test.js`:
- “should not throw for invalid base URL with relative source map” →
resolves to `null`.
- “should resolve absolute source map even if base URL is invalid” →
still resolves correctly.

### Test plan
- Lint/format:
  - `yarn prettier-check`
  - `yarn linc`
- Type checking:
  - `yarn flow dom-node`
- Unit tests:
  - `yarn test --watchAll=false utils-test`
  - Optionally: `yarn test --watchAll=false utils-test inspectedElement`
- All of the above pass locally for experimental channel.

### Risks and rollout
- Risk: Low. Only affects cases where the base URL is invalid.
- Normal cases (valid base or absolute `sourceMappingURL`) are
unchanged.
- No user-facing API changes; DevTools UX becomes more resilient.

### Affected packages
- `react-devtools-shared`

### Related
- Fixes GitHub issue
[#34317](https://github.com/facebook/react/issues/34317)

### Checklist
- [x] Ran `yarn prettier-check`
- [x] Ran `yarn linc`
- [x] Ran `yarn flow dom-node`
- [x] Relevant unit tests passing
- [x] Linked issue and added a concise summary


<!--
  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?
-->

## 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.
-->
2025-09-06 14:00:45 +01:00
Eugene Choi
de5a1b203e [compiler][playground] (3/N) Config override panel (#34371)
<!--
  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

Part 3 of adding a "Config Override" panel to the React compiler
playground. Added a button to apply config changes to the Input panel,
as well as making the tab collapsible. Added validation for the the
PluginOptions type (although comes with a bit more boilerplate) to make
it very obvious what the possible config errors could be. Added some
toasts for trying to apply broken configs.

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

## How did you test this change?


https://github.com/user-attachments/assets/63ab8636-396f-45ba-aaa5-4136e62ccccc


<!--
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.
-->
2025-09-05 10:12:01 -04:00
Sebastian "Sebbie" Silbermann
b9a045368b [DevTools] Allow inspecting root when navigating Suspense timeline (#34380) 2025-09-04 16:42:25 +02:00
Sebastian "Sebbie" Silbermann
e2cc315a1b [DevTools] Don't suspend shell while retrieving original source for "open-in-editor" (#34381) 2025-09-04 16:39:07 +02:00
Sebastian "Sebbie" Silbermann
5a31758ed6 [DevTools] Allow inspection before streaming has finished in Chrome (#34360) 2025-09-04 12:21:06 +02:00
Sebastian "Sebbie" Silbermann
ba6590dd7c [DevTools] Rerender boundaries when they unsuspend when advancing the timeline (#34359) 2025-09-04 10:49:16 +02:00
Joseph Savona
2710795a1e [compiler] Cleanup for @enablePreserveExistingMemoizationGuarantees (#34346)
I tried turning on `@enablePreserveExistingMemoizationGuarantees` by
default and cleaned up a couple small things:

* We emit freeze calls for StartMemoize deps but these had
ValueReason.Other so the message wasn't great. We now treat these like
other hook arguments.
* PruneNonEscapingScopes was being too aggressive in this mode and
memoizing even loads of globals. Switching to
MemoizationLevel.Conditional ensures we build a graph that connects
through to primitive-returning function calls, but doesn't unnecessarily
force memoization otherwise.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34346).
* #34347
* __->__ #34346
2025-09-03 21:30:52 -07:00
Joseph Savona
735e9ac54e [compiler] enablePreserveExistingMemo memoizes primitive-returning functions (#34343)
`@enablePreserveExistingMemoizationGuarantees` mode currently does not
guarantee memoization of primitive-returning functions. We're often able
to infer that a function returns a primitive based on how its result is
used, for example `foo() + 1` or `object[getIndex()]`, and by default we
do not currently memoize computation that produces a primitive. The
reasoning behind this is that the compiler is primarily focused on
stopping cascading updates — it's fine to recompute a primitive since we
can cheaply compare that primitive and avoid unnecessary downstream
recomputation. But we've gotten a lot of feedback that people find this
surprising, and that sometimes the computation can be expensive enough
that it should be memoized.

This PR changes `@enablePreserveExistingMemoizationGuarantees` mode to
ensure that primitive-returning functions get memoized. Other modes will
not memoize these functions. Separately from this we are considering
enabling this mode by default.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34343).
* #34347
* #34346
* __->__ #34343
* #34335
2025-09-03 17:45:17 -07:00
Joseph Savona
5d64f74211 [compiler] Fix for scopes with unreachable fallthroughs (#34335)
Fixes #34108. If a scope ends with with a conditional where some/all
branches exit via labeled break, we currently compile in a way that
works but bypasses memoization. We end up with a shape like


```js
let t0;
label: {
 if (changed) {
   ...
   if (cond) {
     t0 = ...;
     break label;
   }
   // we don't save the output if the break happens!
   t0 = ...;
   $[0] = t0;
 } else {
   t0 = $[0];
}
```

The fix here is to update AlignReactiveScopesToBlockScopes to take
account of breaks that don't go to the natural fallthrough. In this
case, we take any active scopes and extend them to start at least as
early as the label, and extend at least to the label fallthrough. Thus
we produce the correct:

```js
let t0;
if (changed) {
  label: {
    ...
    if (cond) {
      t0 = ...;
      break label;
    }
    t0 = ...;
  }
  // now the break jumps here, and we cache the value
  $[0] = t0;
} else {
  t0 = $[0];
}
```

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34335).
* #34347
* #34346
* #34343
* __->__ #34335
2025-09-03 17:44:42 -07:00
Andrew Clark
3302d1f791 Fix: uDV skipped initial value if earlier transition suspended (#34376)
Fixes a bug in useDeferredValue's optional `initialValue` argument. In
the regression case, if a new useDeferredValue hook is mounted while an
earlier transition is suspended, the `initialValue` argument of the new
hook was ignored. After the fix, the `initialValue` argument is
correctly rendered during the initial mount, regardless of whether other
transitions were suspended.

The culprit was related to the mechanism we use to track whether a
render is the result of a `useDeferredValue` hook: we assign the
deferred lane a TransitionLane, then entangle that lane with the
DeferredLane bit. During the subsequent render, we check for the
presence of the DeferredLane bit to determine whether to switch to the
final, canonical value.

But because transition lanes can themselves become entangled with other
transitions, the effect is that every entangled transition was being
treated as if it were the result of a `useDeferredValue` hook, causing
us to skip the initial value and go straight to the final one.

The fix I've chosen is to reserve some subset of TransitionLanes to be
used only for deferred work, instead of using entanglement. This is
similar to how retries are already implemented. Originally I tried not
to implement it this way because it means there are now slightly fewer
lanes allocated for regular transitions, but I underestimated how
similar deferred work is to retries; they end up having a lot of the
same requirements. Eventually it may be possible to merge the two
concepts.
2025-09-03 19:24:38 -04:00
lauren
7697a9f62e [playground] Upgrade to latest next (#34375)
We were still on a canary version of next in the playground, so let's
update to the latest version.
2025-09-03 13:47:37 -04:00
Ricky
3168e08f83 [flags] enable opt-in for enableDefaultTransitionIndicator (#34373)
So we can test the feature.
2025-09-03 12:33:55 -04:00
Ruslan Lesiutin
2805f0ed9e Performance Tracks: log properties diff for renders in DEV if no console task available (#34370)
React Native doesn't support `console.createTask` yet, but it does
support `performance.measure` and extensibility APIs for Performance
panel, including `detail.devtools` field.

Previously, this logic was gated with `if (__DEV__ && debugTask)`, now
`debugTask` is no longer required to log render. If there is no console
task, we will just call `performance.measure(...)`. The same pattern is
used in other reporters.
2025-09-03 17:08:05 +01:00
Eugene Choi
ac3e705a18 [compiler][playground] (2/N) Config override panel (#34344)
<!--
  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

Part 2 of adding a "Config Override" panel to the React compiler
playground. Added sync from the config editor (still only accessible
with the "showConfig" param) to the main source code editor. Adding a
valid config to the editor will add/replace the `@OVERRIDE` pragma above
the source code. Additionally refactored the old implementation to
remove `useEffect`s and unnecessary renders.

Realized upon testing that the user experience is quite jarring,
planning to add a `sync` button in the next PR to fix this.

## 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.
-->



https://github.com/user-attachments/assets/a71b1b5f-0539-4c00-8d5c-22426f0280f9
2025-09-02 17:38:57 -04:00
Sebastian "Sebbie" Silbermann
8e60cb7ed5 [DevTools] Remove markers from Suspense timeline (#34357) 2025-09-02 14:59:15 +02:00
Sebastian "Sebbie" Silbermann
6a58b80020 [DevTools] Only inspect elements on left mouseclick (#34361) 2025-09-02 12:40:54 +02:00
Sebastian "Sebbie" Silbermann
b1b0955f2b [DevTools] Fix inspected element scroll in Suspense tab (#34355) 2025-09-01 16:40:30 +02:00
Hendrik Liebau
1549bda33f [Flight] Only assign _store in dev mode when creating lazy types (#34354)
Small follow-up to #34350. The `_store` property is now only assigned in
development mode when creating lazy types. It also uses the `validated`
value that was passed to `createElement`, if applicable.
2025-09-01 12:13:05 +02:00
Hendrik Liebau
bb6f0c8d2f [Flight] Fix wrong missing key warning when static child is blocked (#34350) 2025-09-01 11:03:57 +02:00
Hendrik Liebau
aad7c664ff [Flight] Don't try to close debug channel twice (#34340)
When the debug channel was already closed, we must not try to close it
again when the Response gets garbage collected.

**Test plan:**

1. reduce the Flight fixture `App` component to a minimum [^1]
    - remove everything from `<body>`
    - delete the `console.log` statement
2. open the app in Firefox (seems to have a more aggressive GC strategy)
3. wait a few seconds

On `main`, you will see the following error in the browser console:

```
TypeError: Can not close stream after closing or error
```

With this change, the error is gone.

[^1]: It's a bit concerning that step 1 is needed to reproduce the
issue. Either GC is behaving differently with the unmodified App, or we
may hold on to the Response under certain conditions, potentially
creating a memory leak. This needs further investigation.
2025-08-29 17:22:39 +02:00
Hendrik Liebau
3fe51c9e14 [Flight] Use more robust web socket implementation in fixture (#34338)
The `WebSocketStream` implementation seems to be a bit unreliable. We've
seen `Cannot close a ERRORED writable stream` errors when expanding the
logged deep object, for example. And when reducing the fixture to a
minimal app, we even get `Connection closed` errors, because the web
socket connection is closed before all debug chunks are sent.

We can improve the reliability of the web socket connection by using a
normal `WebSocket` instance on the client, along with manually creating
a `WritableStream` and a `ReadableStream` for processing the messages.

As an additional benefit, the debug channel now also works in Firefox
and Safari.

On the server, we're simplifying the integration with the Express server
a bit by utilizing the `server` property for `WebSocket.Server`, instead
of the `noServer` property with the manual upgrade handling.
2025-08-29 12:04:27 +02:00
Joseph Savona
4082b0e7d3 [compiler] Detect known incompatible libraries (#34027)
A few libraries are known to be incompatible with memoization, whether
manually via `useMemo()` or via React Compiler. This puts us in a tricky
situation. On the one hand, we understand that these libraries were
developed prior to our documenting the [Rules of
React](https://react.dev/reference/rules), and their designs were the
result of trying to deliver a great experience for their users and
balance multiple priorities around DX, performance, etc. At the same
time, using these libraries with memoization — and in particular with
automatic memoization via React Compiler — can break apps by causing the
components using these APIs not to update. Concretely, the APIs have in
common that they return a function which returns different values over
time, but where the function itself does not change. Memoizing the
result on the identity of the function will mean that the value never
changes. Developers reasonable interpret this as "React Compiler broke
my code".

Of course, the best solution is to work with developers of these
libraries to address the root cause, and we're doing that. We've
previously discussed this situation with both of the respective
libraries:
* React Hook Form:
https://github.com/react-hook-form/react-hook-form/issues/11910#issuecomment-2135608761
* TanStack Table:
https://github.com/facebook/react/issues/33057#issuecomment-2840600158
and https://github.com/TanStack/table/issues/5567

In the meantime we need to make sure that React Compiler can work out of
the box as much as possible. This means teaching it about popular
libraries that cannot be memoized. We also can't silently skip
compilation, as this confuses users, so we need these error messages to
be visible to users. To that end, this PR adds:

* A flag to mark functions/hooks as incompatible
* Validation against use of such functions
* A default type provider to provide declarations for two
known-incompatible libraries

Note that Mobx is also incompatible, but the `observable()` function is
called outside of the component itself, so the compiler cannot currently
detect it. We may add validation for such APIs in the future.

Again, we really empathize with the developers of these libraries. We've
tried to word the error message non-judgementally, because we get that
it's hard! We're open to feedback about the error message, please let us
know.
2025-08-28 16:21:15 -07:00
Smruti Ranjan Badatya
6b49c449b6 Update Code Sandbox CI to Node 20 to Match .nvmrc (#34329)
## Summary
Update the CodeSandbox CI configuration to use Node 20 instead of Node
18, so that it matches the Node version specified in .nvmrc. This
ensures consistency between local development environments and CI
builds, reducing the risk of version-related build issues.

Closes #34328

## How did you test this change?
- Verified that .nvmrc specifies Node 20 and .codesandbox/ci.json is
updated accordingly.
- Locally switched to Node 20 using nvm use 20 and successfully ran
build scripts for all packages: `react`, `react-dom`,
`react-server-dom-webpack`, and `scheduler`.
- Confirmed there are no Node 20–specific build errors or warnings
locally.
- CI on the feature branch will now run with Node 20, and all builds are
expected to succeed.
2025-08-28 18:33:12 -04:00
lauren
872b4fef6d [eprh] Update installation instructions in readme (#34331)
Small PR to update our readme for eslint-plugin-react-hooks, to better
describe what a minimal but complete eslint config would look like.
2025-08-28 18:27:49 -04:00
Eugene Choi
c5362a380f [compiler][playground] (1/N) Config override panel (#34303)
## Summary
Part 1 of adding a "Config Override" panel to the React compiler
playground. The panel is placed to the left of the current input
section, and supports converting the comment pragmas in the input
section to a JavaScript-based config. Backwards sync has not been
implemented yet.

NOTE: I have added support for a new `OVERRIDE` type pragma to add
support for Map and Function types. (For now, the old pragma format is
still intact)

## Testing
Example of the config overrides synced to the source code:
<img width="1542" height="527" alt="Screenshot 2025-08-28 at 3 38 13 PM"
src="https://github.com/user-attachments/assets/d46e7660-61b9-4145-93b5-a4005d30064a"
/>
2025-08-28 16:26:15 -04:00
Sebastian "Sebbie" Silbermann
89a803fcec [DevTools] Add breadcrumbs to Suspense tab (#34312) 2025-08-28 16:03:54 +02:00
Joseph Savona
8d7b5e4903 [compiler] Show a ref name hint when assigning to non-ref in a callback (#34298)
In #34125 I added a hint where if you assign to the .current property of
a frozen object, we suggest naming the variable as `ref` or `-Ref`.
However, the tracking for mutations that assign to .current specifically
wasn't propagated past function expression boundaries, which meant that
the hint only showed up if you mutated the ref in the main body of the
component/hook. That's less likely to happen since most folks know not
to access refs in render. What's more likely is that you'll (correctly)
assign a ref in an effect or callback, but the compiler will throw an
error. By showing a hint in this case we can help people understand the
naming pattern.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34298).
* #34276
* __->__ #34298
2025-08-27 17:05:44 -07:00
Jack Pope
3434ff4f4b Add scrollIntoView to fragment instances (#32814)
This adds `experimental_scrollIntoView(alignToTop)`. It doesn't yet
support `scrollIntoView(options)`.

Cases:
- No host children: Without host children, we represent the virtual
space of the Fragment by attempting to scroll to the nearest edge by
using its siblings. If the preferred sibling is not found, we'll try the
other side, and then the parent.
- 1 or more host children: In order to handle the case of children
spread between multiple scroll containers, we scroll to each child in
reverse order based on the `alignToTop` flag.

Due to the complexity of multiple scroll containers and dealing with
portals, I've added this under a separate feature flag with an
experimental prefix. We may stabilize it along with the other APIs, but
this allows us to not block the whole feature on it.

This PR was previously implementing a much more complex approach to
handling multiple scroll containers and portals. We're going to start
with the simple loop and see if we can find any concrete use cases where
that doesn't suffice. 01f31d43013ba7f6f54fd8a36990bbafc3c3cc68 is the
diff between approaches here.
2025-08-27 18:05:57 -04:00
lauren
bd5b1b7639 [compiler] Emit better error for unsupported syntax this (#34322) 2025-08-27 17:58:44 -04:00
lauren
0a1f1fcd50 [ci] Cache playwright in run_devtools_e2e_tests (#34320)
I happened to notice that I forgot to cache playwright in
run_devtools_e2e_tests, so it would try to install it every time which
can randomly take a while to complete (I'm not sure why it's not
deterministic, but the dependencies appear to be installed
inconsistently across multiple workflows).

This PR adds the same cache we use for other steps that use playwright,
which should shave off some time from this workflow when the cache is
warm.

Additionally I omitted the standalone install-deps command as it appears
to be redundant and adds a lot of extra time to CI, due to the fact that
it installs many unrelated dependencies.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34320).
* #34321
* __->__ #34320
2025-08-27 14:37:18 -04:00
lauren
b870042915 [compiler] Validate against component/hook factories (#34305)
Previously, the compiler would incorrectly attempt to compile nested
components/hooks defined inside non-React functions. This would lead to
scope reference errors at runtime because the compiler would optimize
the nested React function without understanding its closure over the
parent function's variables.

This PR adds detection when non-React functions declare components or
hooks, and reports a clear error before compilation. I put this under a
new compiler flag defaulting to false. I'll run a test on this
internally first, but I expect we should be able to just turn it on in
both compiler (so we stop miscompiling) and linter.

Closes #33978

Playground example:
https://react-compiler-playground-git-pr34305-fbopensource.vercel.app/#N4Igzg9grgTgxgUxALhAejQAgAIDcCGANgJYAm+ALggHIQAiAngHb4C2xcRhDAwjApQSkeEVgAcITBEwpgA8jAASECAGswAHSkAPCTAqYAZlCZwKxSZgDmCCgEkmYqBQAU+AJSZgWzJjiSwAwB1GHwxMQQYTABeTBdPaIA+Lx9fPwCDAAt8JlJCBB5sphsYuITk7yY0tPwAOklCnJt4gG5U3wBfNqZ2zH4KWCqAHmJHZ0wGopto4CK8gqmEDsw0RO7O7tT+wcwQsIiYbo6QDqA
2025-08-27 13:59:26 -04:00
Joseph Savona
33a1095d72 [compiler] Infer render helpers for additional validation (#33647)
We currently assume that any functions passes as props may be event
handlers or effect functions, and thus don't check for side effects such
as mutating globals. However, if a prop is a function that returns JSX
that is a sure sign that it's actually a render helper and not an event
handler or effect function. So we now emit a `Render` effect for any
prop that is a JSX-returning function, triggering all of our render
validation.

This required a small fix to InferTypes: we weren't correctly populating
the `return` type of function types during unification. I also improved
the printing of types so we can see the inferred return types.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33647).
* #33643
* #33650
* #33642
* __->__ #33647
2025-08-27 08:44:09 -07:00
Sebastian "Sebbie" Silbermann
213594860f [DevTools] Better scrolling in Suspense tab (#34299) 2025-08-27 16:00:06 +02:00
Hendrik Liebau
9c2e2b8475 [Flight] Don't drop debug info if there's only a readable debug channel (#34304)
When the Flight Client is waiting for pending debug chunks, it drops the
debug info if there is no writable side of the debug channel defined.
However, it should instead check if there's no readable side defined.

Fixing this is not only important for browser clients that don't want or
need a return channel, but it's also crucial for server-side rendering,
because the Node and Edge clients only accept a readable side of the
debug channel. So they can't even define a noop writable side as a
workaround.
2025-08-27 13:50:19 +02:00
Sebastian "Sebbie" Silbermann
4123f6b771 [Fizz] Skip past hidden inputs when attempting to hydrate hydration boundaries (#34302) 2025-08-26 17:28:36 +02:00
Sebastian "Sebbie" Silbermann
cb1e73be04 [DevTools] Batch Suspense toggles when advancing the Suspense timeline (#34251) 2025-08-26 17:22:30 +02:00
Hendrik Liebau
cacc20e37c [Flight] Wait for both streams to end before closing the response (#34301)
When a debug channel is defined, we must ensure that we don't close the
Flight Client's response when the debug channel's readable is done, but
the RSC stream is still flowing. Now, we wait for both streams to end
before closing the response.
2025-08-26 17:15:25 +02:00
Sebastian "Sebbie" Silbermann
bb7c9c1b8a Create more realistic containers in DevTools fixture (#34296) 2025-08-26 17:13:37 +02:00
Sebastian "Sebbie" Silbermann
44f8451ede [DevTools] Avoid tearing Suspense store (#34294) 2025-08-26 17:09:55 +02:00
Sebastian "Sebbie" Silbermann
ad4ecb6e6e [DevTools] Fix symbolication with Index Source Maps (#34300) 2025-08-26 15:18:20 +02:00
Jan Kassens
26e87b5f15 Fix Flow issue from land race (#34293)
A Flow upgrade removed the bundled library definitinos for
SynthaticEvent and we probably want to use our internal definitions.
Those are not properly typed at this point yet, but we can look into
that as a followup.
2025-08-25 12:58:12 -04:00
Sebastian "Sebbie" Silbermann
75dc0026d6 [DevTools] Initial version of Suspense timeline (#34233) 2025-08-25 17:47:29 +02:00
Jan Kassens
df10309e2b Update Flow to 0.279 (#34277)
Multiple of these version upgrades required minor additional
annotations.
2025-08-25 11:02:56 -04:00
Sebastian "Sebbie" Silbermann
e42f3d30ca [DevTools] Include name prop when highlighting host instances (#34258) 2025-08-25 16:40:56 +02:00
Sebastian "Sebbie" Silbermann
67e743fba5 [compiler] Fix missing dependency in eslint-plugin-react-hooks (#34287) 2025-08-25 16:39:23 +02:00
Sebastian "Sebbie" Silbermann
9eede45646 Stop treating all Node.js builtins implicitly as externals (#34249) 2025-08-25 09:39:56 +02:00
Jan Kassens
090777d78a Update Flow to 0.274 (#34275)
An exported needed explicit typing as it was inferred incorrectly.
2025-08-22 17:46:37 -04:00
Jan Kassens
4049cfeeab Update Flow to 0.273 (#34274)
This version introduces "Natural Inference" which requires a couple more
type annotations to make Flow pass.
2025-08-22 16:58:01 -04:00
Jan Kassens
e67e3bed92 Update Flow to 0.272 (#34273)
This is the last version before "Natural Inference" change to Flow that
will require more changes, so doing a quick fast-forward PR here.

- Disabled a new Flow lint against unsafe `Object.assign`.
2025-08-22 16:25:49 -04:00
Jan Kassens
06cfa99f37 Update Flow to 0.267 (#34272)
Changes to type inference require some more annotations.
2025-08-22 15:53:07 -04:00
Jan Kassens
05addfc663 Update Flow to 0.266 (#34271)
- replace `$ElementType` and `$PropertyType` with `T[K]` accesses.
- Use component types
2025-08-22 15:46:41 -04:00
Jan Kassens
d260b0d8b8 Update Flow to 0.265 (#34270)
Looks like this version removed `Object.prototype` although I didn't see
that in the changelog. This is fine for this code here.
2025-08-22 15:22:22 -04:00
Joseph Savona
425ba0ad6d [compiler] Script to produce markdown of lint rule docs (#34260)
The docs site is in a separate repo, but this gives us a semi-automated
way to update the docs about our lint rules. The script generates
markdown files from the rule definitions which we can then manually
copy/paste into the docs site somewhere. In the future we can automate
this fully.
2025-08-22 09:59:28 -07:00
Jan Kassens
6de32a5a07 Update Flow to 0.263 (#34269)
This update was a bit more involved.

- `React$Component` was removed, I replaced it with Flow component
types.
- Flow removed shipping the standard library. This adds the environment
libraries back from `flow-typed` which seemed to have changed slightly
(probably got more precise and less `any`s). Suppresses some new type
errors.
2025-08-22 12:10:13 -04:00
Abdulwahab Omira
698bb4deb7 Add support for ARIA 1.3 attributes (#34264)
Co-authored-by: Abdulwahab Omira <abdulwahabomira@gmail.com>
Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
2025-08-22 16:22:18 +02:00
Sebastian Markbåge
11d7bcf88c [DevTools] Use source maps to infer name asynchronously (#34212) 2025-08-22 00:38:09 +02:00
Sebastian Markbåge
a85ec041d6 [DevTools] Ignore List Stack Traces (#34210)
Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
2025-08-22 00:03:05 +02:00
Joseph Savona
7d29ecbeb2 [compiler] Aggregate error reporting, separate eslint rules (#34176)
NOTE: this is a merged version of @mofeiZ's original PR along with my
edits per offline discussion. The description is updated to reflect the
latest approach.

The key problem we're trying to solve with this PR is to allow
developers more control over the compiler's various validations. The
idea is to have a number of rules targeting a specific category of
issues, such as enforcing immutability of props/state/etc or disallowing
access to refs during render. We don't want to have to run the compiler
again for every single rule, though, so @mofeiZ added an LRU cache that
caches the full compilation output of N most recent files. The first
rule to run on a given file will cause it to get cached, and then
subsequent rules can pull from the cache, with each rule filtering down
to its specific category of errors.

For the categories, I went through and assigned a category roughly 1:1
to existing validations, and then used my judgement on some places that
felt distinct enough to warrant a separate error. Every error in the
compiler now has to supply both a severity (for legacy reasons) and a
category (for ESLint). Each category corresponds 1:1 to a ESLint rule
definition, so that the set of rules is automatically populated based on
the defined categories.

Categories include a flag for whether they should be in the recommended
set or not.

Note that as with the original version of this PR, only
eslint-plugin-react-compiler is changed. We still have to update the
main lint rule.

## Test Plan

* Created a sample project using ESLint v9 and verified that the plugin
can be configured correctly and detects errors
* Edited `fixtures/eslint-v9` and introduced errors, verified that the w
latest config changes in that fixture it correctly detects the errors
* In the sample project, confirmed that the LRU caching is correctly
caching compiler output, ie compiling files just once.

Co-authored-by: Mofei Zhang <feifei0@meta.com>
2025-08-21 14:53:34 -07:00
Sebastian Markbåge
253abc78a1 [Flight] Transfer Debug Info from a synchronous Reference to another Chunk (#34229) 2025-08-21 23:50:20 +02:00
Jan Kassens
d73b6f1110 Update Flow to 0.261 (#34255)
- 0.261 required to pull out a constant to preserve refinement
- 0.259 needed some updated suppressions for hacky stuff
2025-08-21 15:02:49 -04:00
Jan Kassens
d5586e2059 Update Flow to 0.258 (#34254)
Minor new suppressions only.
2025-08-21 14:17:13 -04:00
Jan Kassens
ec5dd0ab3a Update Flow to 0.257 (#34253)
After an easy couple version with #34252, this version is less flexible
(and safer) on inferring exported types mainly.

We require to annotate some exported types to differentiate between
`boolean` and literal `true` types, etc.
2025-08-21 13:30:01 -04:00
Ruslan Lesiutin
8120753665 [DevTools] fix: always send a response to fetch-file request in the extension (#34235)
This fixes the displaying of "rendered by" section if owner stacks
contained any native frames. This regressed after
https://github.com/facebook/react/pull/34185, where we added the
Suspense boundary for the StackTraceView.

This fails because the Promise that is responsible for symbolication of
the source is never getting resolved or rejected.
Previously, we would just throw an Error without sending a corresponding
message to the `main` script, and it would just cache a Promise that is
never resolved, hence the Suspense boundary for "rendered by" section is
never resolved.

In a separate change, I think we need to update StackTraceView component
to display `native` as location, instead of `:0`:
<img width="712" height="118" alt="Screenshot 2025-08-20 at 00 20 42"
src="https://github.com/user-attachments/assets/c79735c9-fdd2-467c-96cd-2bc29d38c4e0"
/>
2025-08-21 18:28:33 +01:00
Jan Kassens
3770ff3853 Update Flow to 0.256 (#34252)
Looks like these versions didn't require changes, so easy fast forward.
2025-08-21 12:33:56 -04:00
Jan Kassens
873f711299 Update Flow to 0.248 (#34248)
This update remove support for `%checks`.

Thanks @SamChou19815 for finding a close replacement that works.
2025-08-21 11:15:34 -04:00
Jan Kassens
5f06c3d22a Update Flow to 0.247 (#34245)
`$Call` was removed.
2025-08-20 22:19:57 -04:00
Jan Kassens
243a56b9a2 Update Flow to 0.246 (#34244)
Catching up Flow versions. Since there's plenty new errors, I'm taking
each version with breaking changes as a new PR.
2025-08-20 21:46:55 -04:00
Jan Kassens
83c7379b96 Add flow suppression for Constant Condition rollout (#34243) 2025-08-20 18:24:01 -04:00
lauren
c2ac8b4f0e [ci] Fix permissions for direct sync branch PRs workflow (#34241)
Because we sync built artifacts into Meta, we can't support edits from
inside www/fbsource to be synced back into OSS as it would cause merge
conflicts for future OSS PRs.

We have a workflow that should automatically catch and close these PRs,
but it looks like this one was missing one permission.
2025-08-20 17:09:38 -04:00
Sebastian "Sebbie" Silbermann
03fda05d2c [DevTools] Fix display of stack frames with anonymous sources (#34237) 2025-08-20 17:31:42 +02:00
Hendrik Liebau
0bc71e67ab [Flight] Add debugChannel option to Edge and Node clients (#34236)
When a debug channel is used between the Flight server and a browser
Flight client, we want to allow the same RSC stream to be used for
server-side rendering. To support this, the Edge and Node Flight clients
also need to accept a `debugChannel` option. Without it, debug
information would be missing (e.g. for SSR error stacks), and in some
cases this could result in `Connection closed` errors.

This PR adds support for the `debugChannel` option in the Edge and Node
clients for ESM, Parcel, Turbopack, and Webpack. Unlike the browser
clients, these clients only support a one-way channel, since the Flight
server’s return protocol is not designed for multiple clients.

The implementation follows the approach used in the browser clients, but
excludes the writable parts.
2025-08-20 16:46:34 +02:00
Sebastian "Sebbie" Silbermann
3e20dc8b9c [DevTools] Fix crash when inspecting Components suspended on data awaited in anonymous functions (#34234) 2025-08-20 09:34:06 +02:00
Sebastian "Sebbie" Silbermann
ae5c2f82b3 [DevTools] Handle reorders when resuspending while fallback contains Suspense (#34225) 2025-08-19 20:22:54 +02:00
Sebastian Markbåge
0bdb9206b7 [Fizz] If we haven't painted yet, wait to reveal everything until next paint (#34230)
Before the first rAF, we don't know if there has been other paints
before this and if so when. (We could get from performance observer.) We
can assume that it's not earlier than 0 so we used delay up until the
throttle time starting from zero but if the first paint is about to
happen that can be very soon after.

Instead, this reveals it during the next paint which should let us be
able to get into the first paint. If we can trust `rel="expect"` to have
done its thing we should schedule our raf before first paint but ofc
browsers can cheat and paint earlier if they want to.

If we're wrong, this is at least more batched than doing it
synchronously. However it will mean that things might get more flashy
than it should be if it would've been throttled. An alternative would be
to always throttle first reveal.
2025-08-18 20:22:40 -04:00
lauren
f508edc83f [compiler] Stop publishing eslint-plugin-react-compiler to npm (#34228)
While we still use this package internally, we now ask users to install
eslint-plugin-react-hooks instead, so this package can now be deprecated
on npm.
2025-08-18 11:34:55 -04:00
Sebastian Markbåge
0c89b160f6 [Flight] Add DebugInfo for Bundler Chunks (#34226)
This adds a "suspended by" row for each chunk that is referenced from a
client reference. So when you select a client component, you can see
what bundles will block that client component when loading on the
client.

This is only done in the browser build since if we added it on the
server, it would show up as a blocking resource and while it's possible
we expect that a typical server request won't block on loading JS.

<img width="664" height="486" alt="Screenshot 2025-08-17 at 3 45 14 PM"
src="https://github.com/user-attachments/assets/b1f83445-2a4e-4470-9a20-7cd215ab0482"
/>

<img width="745" height="678" alt="Screenshot 2025-08-17 at 3 46 58 PM"
src="https://github.com/user-attachments/assets/3558eae1-cf34-4e11-9d0e-02ec076356a4"
/>

Currently this is only included if it ends up wrapped in a lazy like in
the typical type position of a Client Component, but there's a general
issue that maybe hard references need to transfer their debug info to
the parent which can transfer it to the Fiber.
2025-08-18 11:34:00 -04:00
Benjamin
87a45ae37f [eslint-plugin-react-hooks][RulesOfHooks] handle React.useEffect in addition to useEffect (#34076)
## Summary

This is a fix for https://github.com/facebook/react/issues/34074

## How did you test this change?

I added tests in the eslint package, and ran `yarn jest`. After adding
the new tests, I have this:

On main | On this branch
-|-
<img width="356" height="88" alt="image"
src="https://github.com/user-attachments/assets/4ae099a1-0156-4032-b2ca-635ebadcaa3f"
/> | <img width="435" height="120" alt="image"
src="https://github.com/user-attachments/assets/b06c04b8-6cec-43de-befa-a8b4dd20500e"
/>

## Changes

- Add tests to check that we are checking both `CallExpression`
(`useEffect(`), and `MemberExpression` (`React.useEffect(`). To do that,
I copied the `getNodeWithoutReactNamespace(` fn from `ExhaustiveDeps.ts`
to `RulesOfHooks.ts`
2025-08-18 09:12:49 -04:00
Sebastian "Sebbie" Silbermann
01ed0e9642 [DevTools] Avoid uncached Promise when symbolicating sources in environments without file fetching (#34224) 2025-08-18 12:46:19 +02:00
Sebastian "Sebbie" Silbermann
b58a8e3c40 [DevTools] Handle mount of disconnected Suspense boundaries (#34208) 2025-08-18 10:15:56 +02:00
Sebastian Markbåge
42b1b33a24 [DevTools] Add byteSize field to ReactIOInfo and show this in the tooltip (#34221)
This is intended to be used by various client side resources where the
transfer size is interesting to know how it'll perform in various
network conditions. Not intended to be added by the server.

For now it's only added internally by DevTools itself on img/css but
I'll add it from Flight Client too in a follow up.

This now shows this as the "transfer size" which is the encoded body
size + headers/overhead. Where as the "fileSize" that I add to images is
the decoded body size, like what you'd see on disk. This is what Chrome
shows so it's less confusing if you compare Network tab and this view.
2025-08-17 16:17:11 -04:00
Sebastian Markbåge
7a36dfedc7 [Fizz] Delay retrying hydration until after an animation frame (#34220)
The theory here is that when we reveal a boundary coming from the server
we want to paint that before hydrating it. Hydration gets scheduled in a
macrotask with the scheduler but it's in theory possible that it runs
before the paint. If that's the case, then the JS that runs before
yielding during hydration might slightly delay the paint and we might
miss a window to skip the previous paint.
2025-08-16 12:16:58 -04:00
Sebastian "Sebbie" Silbermann
546bac7281 [DevTools] Always attempt to mount dehydrated roots (#34209) 2025-08-16 10:45:39 +02:00
Sebastian "Sebbie" Silbermann
2cb8edbb05 [DevTools] Handle dehydrated Suspense boundaries (#34196) 2025-08-16 10:34:19 +02:00
Sebastian Markbåge
431bb0bddb [DevTools] Mark Unknown Reasons for Suspending with a Note (#34200)
We currently only track the reason something might suspend in
development mode through debug info but this excludes some cases. As a
result we can end up with boundary that suspends but has no cause. This
tries to detect that and show a notice for why that might be. I'm also
trying to make it work with old React versions to cover everything.

In production we don't track any of this meta data like `_debugInfo`,
`_debugThenable` etc. so after resolution there's no information to take
from. Except suspensey images / css which we can track in prod too. We
could track lazy component types already. We'd have to add something
that tracks after the fact if something used a lazy child, child as a
promise, hooks, etc. which doesn't exist today. So that's not backwards
compatible and might add some perf/memory cost. However, another
strategy is also to try to replay the components after the fact which
could be backwards compatible. That's tricky for child position since
there's so many rules for how to do that which would have to be
replicated.

If you're in development you get a different error. Given that we've
added instrumentation very recently. If you're on an older development
version of React, then you get a different error. Unfortunately I think
my feature test is not quite perfect because it's tricky to test for the
instrumentation I just added.
https://github.com/facebook/react/pull/34146 So I think for some
prereleases that has `_debugOwner` but doesn't have that you'll get a
misleading error.

Finally, if you're in a modern development environment, the only reason
we should have any gaps is because of throw-a-Promise. This will
highlight it as missing. We can detect that something threw if a
Suspense boundary commits with a RetryCache but since it's a WeakSet we
can't look into it to see anything about what it might have been. I
don't plan on doing anything to improve this since it would only apply
to new versions of React anyway and it's just inherently flawed. So just
deprecate it #34032.

Note that nothing in here can detect that we suspended Transition. So
throwing at the root or in an update won't show that anywhere.
2025-08-15 18:32:27 -04:00
Joseph Savona
5063b3283f [compiler] Remove now-unused FunctionEffect type (#34029)
The new mutation/aliasing model significantly expands on the idea of
FunctionEffect. The type (and its usage in HIRFunction.effects) was only
necessary for the now-deleted old inference model so we can clean up
this code now.
2025-08-15 15:27:30 -07:00
Joseph Savona
eaf6adb127 [compiler][wip] Remove old mutation/aliasing implementation (#34028)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34028).
* #34029
* __->__ #34028
2025-08-15 15:21:28 -07:00
Joseph Savona
6ffcac8558 [compiler] Add support for diagnostic hints (#34126)
Hints are meant as additional information to present to the developer
about an error. The first use-case here is for the suggestion to name
refs with "-Ref" if we encounter a mutation that looks like it might be
a ref. The original error printing used a second error detail which
printed the source code twice, a hint with just extra text is less
noisy.
2025-08-15 15:09:27 -07:00
Joseph Savona
724b324b96 [compiler] Add hint to name variables with "Ref" suffix (#34125)
If you have a ref that the compiler doesn't know is a ref (say, a value
returned from a custom hook) and try to assign its `.current = ...`, we
currently fail with a generic error that hook return values are not
mutable. However, an assignment to `.current` specifically is a very
strong hint that the value is likely to be a ref. So in this PR, we
track the reason for the mutation and if it ends up being an error, we
use it to show an additional hint to the user. See the fixture for an
example of the message.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34125).
* #34126
* __->__ #34125
* #34124
2025-08-15 15:05:29 -07:00
Jack Pope
45a6532a08 Add compareDocumentPosition to Fabric FragmentInstance (#34103)
Stacked on https://github.com/facebook/react/pull/34069

Same basic semantics as the react-dom for determining document position
of a Fragment compared to a given node. It's simpler here because we
don't have to deal with inserted nodes or portals. So we can skip a
bunch of the validation logic.

The logic for handling empty fragments is the same so I've split out
`compareDocumentPositionForEmptyFragment` into a shared module. There
doesn't seem to be a great place to put shared DOM logic between Fabric
and DOM configs at the moment. There may be more of this coming as we
add more and more DOM APIs to RN.

For testing I've written Fantom tests internally which pass the basic
cases on this build. The renderer we have configured for Fabric tests in
the repo doesn't support the Element APIs we need like
`compareDocumentPosition`.
2025-08-15 15:07:42 -04:00
Sebastian "Sebbie" Silbermann
8dba9311e5 [DevTools] Handle fallback unmount in Suspense update path (#34199) 2025-08-15 19:40:35 +02:00
Sebastian "Sebbie" Silbermann
2d98b45d92 [DevTools] Fix Suspense boundaries always being marked as not suspended (#34206) 2025-08-15 19:39:59 +02:00
Sebastian Markbåge
2ba7b07ce1 [DevTools] Compute a min and max range for the currently selected suspense boundary (#34201)
This computes a min and max range for the whole suspense boundary even
when selecting a single component so that each component in a boundary
has a consistent range.

The start of this range is the earliest start of I/O in that boundary or
the end of the previous suspense boundary, whatever is earlier. If the
end of the previous boundary would make the range large, then we cap it
since it's likely that the other boundary was just an independent
render.

The end of the range is the latest end of I/O in that boundary. If this
is smaller than the end of the previous boundary plus the 300ms
throttle, then we extend the end. This visualizes what throttling could
potentially do if the previous boundary committed right at its end. Ofc,
it might not have committed exactly at that time in this render. So this
is just showing a potential throttle that could happen. To see actual
throttle, you look in the Performance Track.

<img width="661" height="353" alt="Screenshot 2025-08-14 at 12 41 43 AM"
src="https://github.com/user-attachments/assets/b0155e5e-a83f-400c-a6b9-5c38a9d8a34f"
/>

We could come up with some annotation to highlight that this is eligible
to be throttled in this case. If the lines don't extend to the edge,
then it's likely it was throttled.
2025-08-15 13:34:07 -04:00
Jack Pope
a96a0f3903 Fix fragmentInstance#compareDocumentPosition nesting and portal cases (#34069)
Found a couple of issues while integrating
FragmentInstance#compareDocumentPosition into Fabric.

1. Basic checks of nested host instances were inaccurate. For example,
checking the first child of the first child of the Fragment would not
return CONTAINED_BY.
2. Then fixing that logic exposed issues with Portals. The DOM
positioning relied on the assumption that the first and last top-level
children were in the same order as the Fiber tree. I added additional
checks against the parent's position in the DOM, and special cased a
portaled Fragment by getting its DOM parent from the child instance,
rather than taking the instance from the Fiber return. This should be
accurate in more cases. Though its still a guess and I'm not sure yet
I've covered every variation of this. Portals are hard to deal with and
we may end up having to push more results towards
IMPLEMENTATION_SPECIFIC if accuracy is an issue.
2025-08-15 12:14:23 -04:00
Sebastian "Sebbie" Silbermann
02a8811864 [SuspenseTab] Scuffed version of Suspense rects (#34188) 2025-08-14 18:24:41 +02:00
Sebastian "Sebbie" Silbermann
379a083b9a Include stack of cause in React instrumentation errors (#34198) 2025-08-13 19:18:02 +02:00
Sebastian "Sebbie" Silbermann
534bed5fa7 Use yarn run in Flight fixture (#34197) 2025-08-13 15:49:44 +02:00
Sebastian Markbåge
db06f6b751 [DevTools] Track virtual debug info from suspensey images (#34181)
Same as #34166 but for Suspensey images.

The trick here is to check the `SuspenseyImagesMode` since not all
versions of React and not all subtrees will have Suspensey images
enabled yet.

The other trick is to read back from `currentSrc` to get the image url
we actually resolved to in this case. Similar to how for Suspensey CSS
we check if the media query would've matched.

<img width="591" height="205" alt="Screenshot 2025-08-11 at 9 32 56 PM"
src="https://github.com/user-attachments/assets/ac98785c-d3e0-407c-84e0-c27f86c0ecac"
/>
2025-08-13 09:26:21 -04:00
Sebastian "Sebbie" Silbermann
9433fe357a Fail tests if unasserted console calls contain undefined (#34191) 2025-08-13 08:48:04 +02:00
Sebastian "Sebbie" Silbermann
0032b2a3ee [Flight] Log error if prod elements are rendered (#34189) 2025-08-13 08:47:09 +02:00
Sebastian Markbåge
14c50e344c [DevTools] Use Visually Lighter Skeletons (#34185)
The skeletons right now are too jarring because they're visually heavier
than the content that comes in later. This makes them draw attention to
themselves as flashing things.

A good skeleton and loading indicator should ideally start as invisible
as possible and then gradually become more visible the longer time
passes so that if it loads quickly then it was never much visible at
all.

Even at its max it should never be heavier weight than the final content
so that it visually reverts into lesser. Another rule of thumb is that
it should be as close as possible to the final content in size but if
it's unknown it should always be smaller than the final content so that
the content grows into its slot rather than the slot contracting.

This makes the skeleton fade from invisible into the dimmest color just
as a subtle hint that something is still loading.

I also added a missing skeleton since the stack traces in rendered by
can now suspend while source mapping.

The other tweak I did is use disabled buttons in all the cases where we
load the ability to enable a button. This is more subtle and if you
hover over you can see why it's still disabled. Rather than flashing the
button each time you change element.
2025-08-12 23:10:31 -04:00
Sebastian Markbåge
f1222f7652 [Fiber] Don't bind retry listener if it's in the cache (#34183)
This did an unnecessary bind allocation even if there's cache hit.
2025-08-12 21:42:24 -04:00
Josh Story
9baecbf02b [Fizz] Avoid hanging when suspending after aborting while rendering (#34192)
This fixes an edge case where you abort the render while rendering a
component that ends up Suspending. It technically only applied if you
were deep enough to be inside `renderNode` and was not susceptible to
hanging if the abort + suspending component was being tried inside
retryRenderTask/retryReplaytask.

The fix is to preempt the thenable checks in renderNode and check if the
request is aborting and if so just bubble up to the task handler.

The reason this hung before is a new task would get scheduled after we
had aborted every other task (minus the currently rendering one). This
led to a situation where the task count would not hit zero.
2025-08-12 16:46:56 -07:00
Sebastian "Sebbie" Silbermann
0422a00e3e [DevTools] Fix missing key warning (#34186) 2025-08-12 19:58:19 +02:00
Sebastian Markbåge
47fd2f5e14 [DevTools] Fix index (#34187)
I used the wrong indexer and tested with one entry.
2025-08-12 13:57:35 -04:00
Jan Kassens
1dc3bdead1 Remove unused arguments from ReactElement (#34174)
After various feature flag removals recently, these arguments became
unused and can be deleted.
2025-08-12 11:09:35 -04:00
Sebastian "Sebbie" Silbermann
de06211dbe [DevTools] Send Suspense rects to frontend (#34170) 2025-08-12 16:48:35 +02:00
Sebastian "Sebbie" Silbermann
ac7820a99e Create fresh Offscreen instance when replaying (#34127) 2025-08-11 20:55:48 +02:00
Sebastian Markbåge
3c67bbe5f9 [DevTools] Track suspensey CSS on "suspended by" (#34166)
We need to track that Suspensey CSS (Host Resources) can contribute to
the loading state. We can pick up the start/end time from the
Performance Observer API since we know which resource was loaded.

If DOM nodes are not filtered there's a link to the `<link>` instance.
The `"awaited by"` stack is the callsite of the JSX creating the
`<link>`.

<img width="591" height="447" alt="Screenshot 2025-08-11 at 1 35 21 AM"
src="https://github.com/user-attachments/assets/63af0ca9-de8d-4c74-a797-af0a009b5d73"
/>

Inspecting the link itself:

<img width="592" height="344" alt="Screenshot 2025-08-11 at 1 31 43 AM"
src="https://github.com/user-attachments/assets/89603dbc-6721-4bbf-8b58-6010719b29e3"
/>

In this approach I only include it if the page currently matches the
media query. It might contribute in some other scenario but we're not
showing every possible state but every possible scenario that might
suspend if timing changes in the current state.
2025-08-11 12:28:32 -04:00
Sebastian Markbåge
2c9a42dfd7 [DevTools] If the await doesn't have a stack use the stack from use() if any (#34162)
Stacked on #34148.

This picks up the stack for the await from the `use()` Hook if one was
used to get this async info.

When you select a component that used hooks, we already collect this
information.

If you select a Suspense boundary, this lazily invokes the first
component that awaited this data to inspects its hooks and produce a
stack trace for the use().

When all we have for the name is "Promise" I also use the name of the
first callsite in the stack trace if there's more than one. Which in
practice will be the name of the custom Hook that called it. Ideally
we'd use source mapping and ignore listing for this but that would
require suspending the display. We could maybe make the SuspendedByRow
wrapped in a Suspense boundary for this case.

<img width="438" height="401" alt="Screenshot 2025-08-10 at 10 07 55 PM"
src="https://github.com/user-attachments/assets/2a68917d-c27b-4c00-84aa-0ceb51c4e541"
/>
2025-08-11 12:28:10 -04:00
Jan Kassens
f1e70b5e0a [easy] remove leftover reference to disableDefaultPropsExceptForClasses (#34169)
Noticed that I missed this in some earlier cleanup diff.

Test Plan:
grep for disableDefaultPropsExceptForClasses
2025-08-11 12:13:33 -04:00
Sebastian Markbåge
d587434c35 [DevTools] Pick up suspended by info from use() (#34148)
Similar to #34144 but for `use()`.

`use()` dependencies don't get added to the `fiber._debugInfo` set
because that just models the things blocking the children, and not the
Fiber component itself. This picks up any debug info from the thenable
state that we stashed onto `_debugThenableState` so that we know it used
`use()`.

<img width="593" height="425" alt="Screenshot 2025-08-09 at 4 03 40 PM"
src="https://github.com/user-attachments/assets/c7e06884-4efd-47fa-a76b-132935db6ddc"
/>

Without #34146 this doesn't pick up uninstrumented promises but after
it, it'll pick those up as well. An instrumented promise that doesn't
have anything in its debug info is not picked up. For example, if it
didn't depend on any I/O on the server.

This doesn't yet pick up the stack trace of the `use()` call. That
information is in the Hooks information but needs a follow up to extract
it.
2025-08-11 12:10:05 -04:00
Sebastian Markbåge
ca292f7a57 [DevTools] Don't show "awaited by" if there's nothing to show (#34163)
E.g. if the owner is null or the same as current component and no stack.
This happens for example when you return a plain Promise in the child
position and inspect the component it was returned in since there's no
hook stack and the owner is the same as the instance itself so there's
nothing new to link to.

Before:

<img width="267" height="99" alt="Screenshot 2025-08-10 at 10 28 32 PM"
src="https://github.com/user-attachments/assets/23341ab2-2888-457d-a1d1-128f3e0bd5ec"
/>

After:

<img width="253" height="91" alt="Screenshot 2025-08-10 at 10 29 04 PM"
src="https://github.com/user-attachments/assets/b33bb38b-891a-4f46-bc16-15604b033cdb"
/>
2025-08-11 11:48:09 -04:00
Sebastian Markbåge
62a634b972 [DebugTools] Use thenables from the _debugThenableState if available (#34161)
In the case where a Promise is not cached, then the thenable state might
contain an older version. This version is the one that was actually
observed by the committed render, so that's the version we'll want to
inspect.

We used to not store the thenable state but now we have it on
`_debugThenableState` in DEV.

<img width="593" height="359" alt="Screenshot 2025-08-10 at 8 26 04 PM"
src="https://github.com/user-attachments/assets/51ee53f3-a31a-4e3f-a4cf-bb20b6efe0cb"
/>
2025-08-11 11:46:27 -04:00
Sebastian Markbåge
53d07944df [Fiber] Assign implicit debug info to used thenables (#34146)
Similar to #34137 but for Promises.

This lets us pick up the debug info from a raw Promise as a child which
is not covered by `_debugThenables`. Currently ChildFiber doesn't stash
its thenables so we can't pick them up from devtools after the fact
without some debug info added to the parent.

It also lets us track some approximate start/end time of use():ed
promises based on the first time we saw this particular Promise.
2025-08-11 11:44:05 -04:00
Sebastian Markbåge
34ce3acafd [DevTools] Pick up suspended by info from React.lazy in type position (#34144)
Normally, we pick up debug info from instrumented Promise or React.Lazy
while we're reconciling in ReactChildFiber when they appear in the child
position. We add those to the `_debugInfo` of the Fiber.

However, we don't do that for for Lazy in the Component type position.
Instead, we have to pick up the debug info from it explicitly in
DevTools. Likely this is the info added by #34137. Older versions
wouldn't be covered by this particular mechanism but more generally from
throwing a Promise.


<img width="592" height="449" alt="Screenshot 2025-08-08 at 11 32 33 PM"
src="https://github.com/user-attachments/assets/87211c64-a7df-47b7-a784-5cdc7c5fae16"
/>
2025-08-11 11:42:59 -04:00
Sebastian Markbåge
6445b3154e [Fiber] Add additional debugInfo to React.lazy constructors in DEV (#34137)
This creates a debug info object for the React.lazy call when it's
called on the client. We have some additional information we can track
for these since they're created by React earlier.

We can track the stack trace where `React.lazy` was called to associate
it back to something useful. We can track the start time when we
initialized it for the first time and the end time when it resolves. The
name from the promise if available.

This data is currently only picked up in child position and not
component position. The component position is in a follow up.

<img width="592" height="451" alt="Screenshot 2025-08-08 at 2 49 33 PM"
src="https://github.com/user-attachments/assets/913d2629-6df5-40f6-b036-ae13631379b9"
/>

This begs for ignore listing in the front end since these stacks aren't
filtered on the server.
2025-08-11 11:42:23 -04:00
Sebastian Markbåge
ab5238d5a4 [DevTools] Show name prop of Suspense / Activity in the Components Tree view (#34135)
The name prop will be used in the Suspense tab to help identity a
boundary. Activity will also allow names. A custom component can be
identified by the name of the component but built-ins doesn't have that.

This PR adds it to the Components Tree View as well since otherwise you
only have the key to go on. Normally we don't add all the props to avoid
making this view too noisy but this is an exception along with key to
help identify a boundary quickly in the tree.

Unlike the SuspenseNode store, this wouldn't ever have a name inferred
by owner since that kind of context already exists in this view.

<img width="600" height="161" alt="Screenshot 2025-08-08 at 1 20 36 PM"
src="https://github.com/user-attachments/assets/fe50d624-887a-4b9d-9186-75f131f83195"
/>

I also made both the key and name prop searchable.

<img width="608" height="206" alt="Screenshot 2025-08-08 at 1 32 27 PM"
src="https://github.com/user-attachments/assets/d3502d9c-7614-45fc-b973-57f06dd9cddc"
/>
2025-08-11 11:41:46 -04:00
Sebastian Markbåge
7a934a16b8 [DevTools] Show Owner Stacks in "rendered by" View (#34130)
This shows the stack trace of the JSX at each level so now you can also
jump to the code location for the JSX callsite. The visual is similar to
the owner stacks with `createTask` except when you click the `<...>` you
jump to the Instance in the Components panel.

<img width="593" height="450" alt="Screenshot 2025-08-08 at 12 19 21 AM"
src="https://github.com/user-attachments/assets/dac35faf-9d99-46ce-8b41-7c6fe24625d2"
/>

I'm not sure it's really necessary to have all the JSX stacks of every
owner. We could just have it for the current component and then the rest
of the owners you could get to if you just click that owner instance.

As a bonus, I also use the JSX callsite as the fallback for the "View
Source" button. This is primarily useful for built-ins like `<div>` and
`<Suspense>` that don't have any implementation to jump to anyway. It's
useful to be able to jump to where a boundary was defined.
2025-08-11 11:41:30 -04:00
Sebastian Markbåge
59ef3c4baf [DevTools] Allow Introspection of React Elements and React.lazy (#34129)
With RSC it's common to get React.lazy objects in the children position.
This first formats them nicely.

Then it adds introspection support for both lazy and elements.

Unfortunately because of quirks with the hydration mechanism we have to
expose it under the name `_payload` instead of something direct. Also
because the name "type" is taken we can't expose the type field on an
element neither. That whole algorithm could use a rewrite.

<img width="422" height="137" alt="Screenshot 2025-08-07 at 11 37 03 PM"
src="https://github.com/user-attachments/assets/a6f65f58-dbc4-4b8f-928b-d7f629fc51b2"
/>

<img width="516" height="275" alt="Screenshot 2025-08-07 at 11 36 36 PM"
src="https://github.com/user-attachments/assets/650bafdb-a633-4d78-9487-a750a18074ce"
/>

For JSX an alternative or additional feature might be instead to jump to
the first Instance that was rendered using that JSX. We know that based
on the equality of the memoizedProps on the Fiber. It's just a matter of
whether we do that eagerly or more lazily when you click but you may not
have a match so would be nice to indicate that before you click.
2025-08-11 11:41:14 -04:00
Sebastian "Sebbie" Silbermann
72965f3615 [DevTools] Restore reconciling Suspense stack after fallback was reconciled (#34168) 2025-08-11 17:12:39 +02:00
Sebastian Markbåge
594fb5e9ab [DevTools] Always skip 1 frame (#34167)
Follow up to #34093.

There's an issue where the skipFrames argument isn't part of the cache
key so the other parsers that expect skipping one frame might skip zero
and show the internal `fakeJSXDEV` callsite. Ideally we should include
the skipFrames as part of the cache key but we can also always just skip
one.
2025-08-11 01:50:26 -04:00
Sebastian "Sebbie" Silbermann
98286cf8e3 [DevTools] Send suspense nodes to frontend store (#34070) 2025-08-10 10:12:20 +02:00
Sophie Alpert
cf6e502ed2 Hot reloading: Avoid stack overflow on wide trees (#34145)
Every sibling added to the stack here. Not sure this needs to be
recursive at all but certainly for siblings this can just be a loop.
2025-08-09 08:02:22 -07:00
Sebastian Markbåge
3958d5d84b [Flight] Copy the name field of a serialized function debug value (#34085)
This ensures that if the name is set manually after the declaration,
then we get that name when we log the value. For example Node.js
`Response` is declared as `_Response` and then later assigned a new
name.

We should probably really serialize all static enumerable properties but
"name" is non-enumerable so it's still a special case.
2025-08-07 10:55:01 -04:00
Sebastian Markbåge
738aebdbac [DevTools] Add Badge to Owners and sometimes stack traces (#34106)
Stacked on #34101.

This adds a badge to owners if they are different from the currently
selected component's environment.

<img width="590" height="566" alt="Screenshot 2025-08-04 at 5 15 02 PM"
src="https://github.com/user-attachments/assets/e898254f-1b4c-498e-8713-978d90545340"
/>

We also add one to the end of stack traces if the stack trace has a
different environment than the owner which can happen when you call a
function (without rendering a component) into a third party environment
but the owner component was in the first party.

One awkward thing is that Suspense boundaries are always in the client
environment so their Server Components are always badged.
2025-08-07 10:39:08 -04:00
Sebastian Markbåge
4c9c109cea [Fiber] Try to give a stack trace to every entry in the Scheduler Performance Track (#34123)
For "render" and "commit" phases we don't give any specific stack atm.
This tries to always provide something useful to say the cause of the
render.

For normal renders this will now show the same thing as the "Event" and
"Update" entries already showed. We stash the task that was used for
those and use them throughout the render and commit phases.

For Suspense (Retry lane) and Idle (Offscreen lane), we don't have any
updates. Instead for those there's a component that left work behind in
previous passes. For those I use the debugTask of the `<Suspense>` or
`<Activity>` boundary to indicate that this was the root of the render.

Similarly when an Action is invoked on a `<form action={...}>` component
using the built-in submit handler, there's no actionable stack in user
space that called it. So we use the stack of the JSX for the form
instead.
2025-08-07 10:26:30 -04:00
Ruslan Lesiutin
552a5dadcf [DevTools] fix: handle store mutations synchronously in TreeContext (#34119)
If there is a commit that removes the currently inspected (selected)
elements in the Components tree, we are going to kick off the transition
to re-render the Tree. The elements will be re-rendered with the
previous inspectedElementID, which was just removed and all consecutive
calls to store object with this id would produce errors, since this
element was just removed.

We should handle store mutations synchronously. Doesn't make sense to
start a transition in this case, because Elements depend on the
TreeState and could make calls to store in render function.

Before:
<img width="2286" height="1734" alt="Screenshot 2025-08-06 at 17 41 14"
src="https://github.com/user-attachments/assets/97d92220-3488-47b2-aa6b-70fa39345f6b"
/>


After:


https://github.com/user-attachments/assets/3da36aff-6987-4b76-b741-ca59f829f8e6
2025-08-07 14:05:56 +01:00
Joseph Savona
f468d37739 [compiler] remove use of inspect module (#34124) 2025-08-06 23:59:55 -07:00
Joseph Savona
c403a7c548 [compiler] Upstream experimental flow integration (#34121)
all credit on the Flood/ code goes to @mvitousek and @jbrown215, i'm
just the one upstreaming it
2025-08-06 15:58:07 -07:00
Sebastian Markbåge
fa212fc2b1 [DevTools] Measure the Rectangle of Suspense boundaries as we reconcile (#34090)
Stacked on #34089.

This measures the client rects of the direct children of Suspense
boundaries as we reconcile. This will be used by the Suspense tab to
visualize the boundaries given their outlines.

We could ask for this more lazily just in case we're currently looking
at the Suspense tab. We could also do something like monitor the sizes
using a ResizeObserver to cover when they change.

However, it should be pretty cheap to this in the reconciliation phase
since we're already mostly visiting these nodes on the way down. We have
also already done all the layouts at this point since it was part of the
commit phase and paint already. So we're just reading cached values in
this phase. We can also infer that things are expected to change when
parents or sibling changes. Similar technique as ViewTransitions.
2025-08-06 14:56:52 -04:00
Sebastian Markbåge
b080063331 [DevTools] Source Map Stack Traces such in await locations (#34094)
Stacked on #34093.

Instead of using the original `ReactStackTrace` that has the call sites
on the server, this parses the `Error` object which has the virtual call
sites on the client. We'll need this technique for things stack traces
suspending on the client anyway like `use()`.

We can then use these callsites to source map in the front end.

We currently don't source map function names but might be useful for
this use case as well as getting original component names from prod.

One thing this doesn't do yet is that it doesn't ignore list the stack
traces on the client using the source map's ignore list setting. It's
not super important since we expect to have already ignore listed on the
server but this will become important for client stack traces like
`use()`.
2025-08-06 13:45:06 -04:00
Sebastian Markbåge
66f09bd054 [DevTools] Sort "Suspended By" view by the start time (#34105)
or end time if they have the same start time.

<img width="517" height="411" alt="Screenshot 2025-08-04 at 4 00 23 PM"
src="https://github.com/user-attachments/assets/b99be67b-5727-4e24-98c0-ee064fb21e2f"
/>

They would typically appear in this order naturally but not always.
Especially in Suspense boundaries where the order can also be depended
on when the components are discovered.
2025-08-06 11:23:00 -04:00
Sebastian Markbåge
0825d019be [DevTools] Prefer I/O stack and show await stack after only if it's a different owner (#34101)
Stacked on #34094.

This shows the I/O stack if available. If it's not available or if it
has a different owner (like if it was passed in) then we show the
`"awaited at:"` stack below it so you can see where it started and where
it was awaited. If it's the same owner this tends to be unnecessary
noise. We could maybe be smarter if the stacks are very different then
you might want to show both even with the same owner.

<img width="517" height="478" alt="Screenshot 2025-08-04 at 11 57 28 AM"
src="https://github.com/user-attachments/assets/2dbfbed4-4671-4a5f-8e6e-ebec6fe8a1b7"
/>

Additionally, this adds an inferred await if there's no owner and no
stack for the await. The inferred await of a function/class component is
just the owner. No stack. Because the stack trace would be the return
value. This will also be the case if you use throw-a-Promise. The
inferred await in the child position of a built-in is the JSX location
of that await like if you pass a promise to a child. This inference
already happens when you pass a Promise from RSC so in this case it
already has an await - so this is mainly for client promises.
2025-08-06 11:21:01 -04:00
Sebastian Markbåge
c97ec75324 [DevTools] Disconnect and Reconnect children of Suspense boundaries instead of Unmounting and Mounting (#34089)
Stacked on #34082.

This keeps the DevToolsInstance children alive inside Offscreen trees
while they're hidden. However, they're sent as unmounted to the front
end store.

This allows DevTools state to be preserved between these two states.

Such as it keeps the "suspended by" set on the SuspenseNode alive since
the children are still mounted. So now you when you resuspend, you can
see what in the children was suspended. This is useful when you're
simulating a suspense but can also be a bit misleading when something
suspended for real since it'll only show the previous suspended set and
not what is currently suspending it since that hasn't committed yet.

SuspenseNodes inside resuspended trees are now kept alive too. That way
they can contribute to the timeline even when resuspended. We can choose
whether to keep them visible in the rects while hidden or not.

In the future we'll also need to add more special cases around Activity.
Because right now if SuspenseNodes are kept alive in the Suspense tab UI
while hidden, then they're also alive inside Activity that are hidden
which maybe we don't want. Maybe simplest would be that they both
disappear from the Suspense tab UI but can be considered for the
timeline.

Another case is that when Activity goes hidden, Fiber will no longer
cause its content to suspend the parent but that's not modeled here. So
hidden Activity will show up as "suspended by" in a parent Suspense.
When they disconnect, they should really be removed from the "suspended
by" set of the parent (and perhaps be shown only on the Activity
boundary itself).
2025-08-06 11:05:19 -04:00
Sebastian Markbåge
99fd4f2ac1 [DevTools] Reorder moved filtered Fibers with backing DevToolsInstance (#34104)
Instead, we just continue to collect the unfiltered children.

---------

Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
2025-08-05 12:39:45 -04:00
Joseph Savona
7deda941f7 [compiler] Delete PropagatePhiTypes (#34107)
We moved this logic into InferTypes a long time ago and the PRs to clean
it up keep getting lost in the shuffle.
2025-08-04 15:15:51 -07:00
Joseph Savona
d3b26b2953 [compiler] rebase #32285 (#34102)
Redo of #32285 which was created with ghstack and is tedious to rebase
with sapling.
2025-08-04 12:04:44 -07:00
lauren
b211d7023c [compiler] Add repros for various invariants (#34099)
We received some bug reports about invariants reported by the compiler
in their codebase. Adding them as repros.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34099).
* #34100
* __->__ #34099
2025-08-04 14:36:12 -04:00
Sebastian Markbåge
ba4bdb2ab5 [DevTools] Consume SuspenseNodes that were skipped when we're bailing out of a subtree (#34082)
This searches through the remaining children to see if any of them were
children of the bailed out FiberInstance and if so we should reuse them
in the new set. It's faster to do this than search through children of
the FiberInstance for Suspense boundaries.
2025-08-04 13:04:47 -04:00
Sebastian Markbåge
be11cb5c4b [DevTools] Tweak the presentation of the Promise value (#34097)
Show the value as "fulfilled: Type" or "rejected: Type" immediately
instead of having to expand it twice. We could show all the properties
of the object immediately like we do in the Performance Track but it's
not always particularly interesting data in the value that isn't already
in the header.

I also moved it to the end after the stack traces since I think the
stack is more interesting but I'm also visually trying to connect the
stack trace with the "name" since typically the "name" will come from
part of the stack trace.

Before:

<img width="517" height="433" alt="Screenshot 2025-08-03 at 11 39 49 PM"
src="https://github.com/user-attachments/assets/ad28d8a2-c149-4957-a393-20ff3932a819"
/>

After:

<img width="520" height="476" alt="Screenshot 2025-08-03 at 11 58 35 PM"
src="https://github.com/user-attachments/assets/53a755b0-bb68-4305-9d16-d6fac7ca4910"
/>
2025-08-04 09:42:48 -04:00
Sebastian Markbåge
557745eb0b [DevTools] Add structure full stack parsing to DevTools (#34093)
We'll need complete parsing of stack traces for both owner stacks and
async debug info so we need to expand the stack parsing capabilities a
bit. This refactors the source location extraction to use some helpers
we can use for other things too.

This is a fork of `ReactFlightStackConfigV8` which also supports
DevTools requirements like checking both `react_stack_bottom_frame` and
`react-stack-bottom-frame` as well as supporting Firefox stacks.

It also supports extracting the first frame of a component stack or the
last frame of an owner stack for the source location.
2025-08-04 09:37:46 -04:00
Sebastian Markbåge
d3f800d47a [DevTools] Style clickable Owner components with angle brackets and bold (#34096)
We have two type of links that appear next to each other now. One type
of link jumps to a Component instance in the DevTools. The other opens a
source location - e.g. in your editor.

This clarifies that something will jump to the Component instance by
marking it as bold and using angle brackets around the name.

This can be seen in the "rendered by" list of owner as well as in the
async stack traces when the stack was in a different owner than the one
currently selected.

<img width="516" height="387" alt="Screenshot 2025-08-03 at 11 27 38 PM"
src="https://github.com/user-attachments/assets/5da50262-1e74-4e46-a6f8-96b4c1e4db31"
/>

The idea is to connect this styling to the owner stacks using
`createTask` where this same pattern occurs (albeit the task name is not
clickable):

<img width="454" height="188" alt="Screenshot 2025-08-03 at 11 23 45 PM"
src="https://github.com/user-attachments/assets/81a55c8f-963a-4fda-846a-97f49ef0c469"
/>

In fact, I was going to add the stack traces to the "rendered by" list
to give the ability to jump to the JSX location in the owner stack so
that it becomes this same view.
2025-08-04 09:28:31 -04:00
Sebastian Markbåge
8e3db095aa [DevTools] Make a non-editable name of KeyValue clickable (#34095)
This has been bothering me. You can click the arrow and the value to
expand/collapse a KeyValue row but not the name.

When the name is not editable it should be clickable. Such as when
inspecting a Promise value.
2025-08-04 09:27:37 -04:00
Sebastian Markbåge
041754697c [DevTools] Only show state for ClassComponents (#34091)
The only thing that uses `memoizedState` as a public API is
ClassComponents. Everything else uses it as internals. We shouldn't ever
show those internals.

Before those internals showed up for example on a suspended Suspense
boundary:

<img width="436" height="370" alt="Screenshot 2025-08-03 at 8 13 37 PM"
src="https://github.com/user-attachments/assets/7fe275a7-d5da-421d-a000-523825916630"
/>
2025-08-04 09:26:12 -04:00
Ruslan Lesiutin
30fca45c1c fix: apply initial horizontal offset on tree mount (#34088)
When the element is pre-selected and the Tree component is mounted,
right now we are only applying initial vertical offset, but not the
horizontal one.

Because of this, if the DOM element was selected on Elements panel and
then user opens Components panel for the first time of the browser
DevTools session, depending on the element's depth, it could be hidden.

Similarly to vertical offset, apply horizontal one, but via ref setter.

### Before:

https://github.com/user-attachments/assets/0ab3cca9-93c1-4e9e-8d23-88330d438912

### After:

https://github.com/user-attachments/assets/10de153a-1e55-4cf7-b1ff-4cc7cb35ba10
2025-08-04 12:12:53 +01:00
Sebastian Markbåge
c499adf8c8 [Flight] Allow Temporary References to be awaited (#34084)
Fixes #33534.

`.then` method can be tested when you await a value that's not a
Promise. For regular Client References we have a way to mark those as
"async" and yield a reference to the unwrapped value in case it's a
Promise on the Client.

However, the realization is that we never serialize Promises as opaque
when passed from the client to the server. If a Promise is passed, then
it would've been deserialized as a Promise (while still registered as a
temporary reference) and not one of these Proxy objects.

Technically it could be a non-function value on the client which would
be wrong but you're not supposed to dot into it in the first place.

So we can just assume it's `undefined`.
2025-08-02 18:44:20 -04:00
Dennis Kats
1d163962b2 Allow returning a temporary reference inside an async function (#33761)
<!--
  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

Fixes `await`-ing and returning temporary references in `async`
functions. These two operations invoke `.then()` under the hood if it is
available, which currently results in an "Cannot access then on the
server. You cannot dot into a temporary client reference..." error. This
can easily be reproduced by returning a temporary reference from a
server function.

Fixes #33534 

## How did you test this change?
I added a test in a new test file. I wasn't sure where else to put it.
<img width="771" height="138" alt="image"
src="https://github.com/user-attachments/assets/09ffe6eb-271a-4842-a9fe-c68e17b3fb41"
/>


<!--
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.
-->
2025-08-02 18:11:54 -04:00
Joseph Savona
ddf8bc3fba [compiler] Improve merging of scopes that invalidate together (#34049)
We try to merge consecutive reactive scopes that will always invalidate
together, but there's one common case that isn't handled.

```js
const y = [[x]];
```

Here we'll create two consecutive scopes for the inner and outer array
expressions. Because the input to the second scope is a temporary,
they'll merge into one scope.

But if we name the inner array, the merging stops:

```js
const array = [x];
const y = [array];
```

This is because the merging logic checks if all the dependencies of the
second scope are outputs of the first scope, but doesn't account for
renaming due to LoadLocal/StoreLocal. The fix is to track these
temporaries.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34049).
* __->__ #34049
* #34047
* #34044
2025-08-01 13:00:01 -07:00
Joseph Savona
0860b9cc1f [compiler] Add definitions for Object entries/keys/values (#34047)
Fixes remaining issue in #32261, where passing a previously useMemo()-d
value to `Object.entries()` makes the compiler think the value is
mutated and fail validatePreserveExistingMemo. While I was there I added
Object.keys() and Object.values() too.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34047).
* #34049
* __->__ #34047
* #34044
2025-08-01 12:59:49 -07:00
Sebastian Markbåge
538ac7ae4b [Flight] Fix debug info leaking to outer handler (#34081)
The `waitForReference` call for debug info can trigger inside a
different object's initializingHandler. In that case, we can get
confused by which one is the root object.

We have this special case to detect if the initializing handler's object
is `null` and we have an empty string key, then we should replace the
root object's value with the resolved value.


52612a7cbd/packages/react-client/src/ReactFlightClient.js (L1374)

However, if the initializing handler actually should have the value
`null` then we might get confused by this and replace it with the
resolved value from a debug object. This fixes it by just using a
non-empty string as the key for the waitForReference on debug value
since we're not going to use it anyway.

It used to be impossible to get into this state since a `null` value at
the root couldn't have any reference inside itself but now the debug
info for a `null` value can have outstanding references.

However, a better fix might be using a placeholder marker object instead
of null or better yet ensuring that we know which root we're
initializing in the debug model.
2025-08-01 15:44:48 -04:00
lauren
52612a7cbd [compiler] Emit more specific error when making identifiers with reserved words (#34080)
This currently throws an invariant which may be misleading. I checked
the ecma262 spec and used the same list of reserved words in our check.
To err on the side of being conservative, we also error when strict mode
reserved words are used.
2025-08-01 15:10:34 -04:00
Sebastian "Sebbie" Silbermann
bdb4a96f62 [DevTools] Lazily compute initial Tree state (#34078) 2025-08-01 17:49:25 +02:00
Sebastian Markbåge
c260b38d0a [DevTools] Clean up Virtual Instances from id map (#34063)
This was a pretty glaring memory leak. 🙈

I forgot to clean up the VirtualInstances from the id map so the Server
Component instances always leaked in DEV.
2025-07-31 10:30:31 -04:00
Sebastian Markbåge
5bbf9be246 [DevTools] Model Hidden Offscreen Boundaries as Unmounts (#34062)
This is modeling Offscreen boundaries as the thing that unmounts a tree
in the frontend. This will let us model this as a "hide" that preserves
state instead in a follow up but not yet.

By doing it this way, we don't have to special case suspended Suspense
boundaries, at least not for the modern versions that use Offscreen as
the internal node. It's still special cased for the old React versions.
Instead, this is handled by the Offscreen fiber getting hidden.

By giving this fiber an FilteredFiberInstance, we also have somewhere to
store the children on (separately from the parent children set which can
include other siblings too like the loading state).

One consequence is that Activity boundary content now disappears when
they're hidden which is probably a good thing since otherwise it would
be confusing and noisy when it's used to render multiple pages at once.
2025-07-31 10:30:10 -04:00
Josh Story
8de7aed892 [Fizz] Count Boundary bytes that may contribute to the preamble in the request byteSize (#34059)
Stacked on #34058

When tracking how large the shell is we currently only track the bytes
of everything above Suspense boundaries. However since Boundaries that
contribute to the preamble will always be inlined when the shell flushes
they should also be considered as part of the request byteSize since
they always flush alongside the shell. This change adds this tracking
2025-07-30 18:18:57 -07:00
Josh Story
98773466ce [Fizz] Don't outline Boundaries that may contribute to the preamble (#34058)
Suspense boundaries that may have contributed to the preamble should not
be outlined due to size because these boundaries are only meant to be in
fallback state if the boundary actually errors. This change excludes any
boundary which has the potential to contribute to the preamble. We could
alternatively track which boundaries actually contributed to the
preamble but in practice there will be very few and I think this is
sufficient.

One problem with this approach is it makes Suspense above body opt out
of the mode where we omit rel="expect" for large shells. In essence
Suspense above body has the semantics of a Shell (it blocks flushing
until resolved) but it doesn't get tracked as request bytes and thus we
will not opt users into the skipped blocking shell for very large
boundaries.

This will be fixed in a followup
2025-07-30 18:06:47 -07:00
Sebastian Markbåge
9784cb379e [DevTools] No suspending above the root (#34055)
Follow up to #34050.

It's not actually possible to suspend *above* the root since even if you
suspend in the first child position, you're still suspending the
HostRoot which always has a corresponding FiberInstance and
SuspenseNode.
2025-07-30 11:31:27 -04:00
Sebastian Markbåge
dcf2a6f665 [DevTools] Keep a Suspense Tree Parellel to the Instance tree in the Backend (#34050)
This keeps a data structure of Suspense boundaries and the root which
can keep track which boundaries might participate in a loading sequence
and everything that suspends them. This will power the Suspense tab.

Now when you select a `<Suspense>` boundary the "suspended by" section
shows the whole boundary instead of just that component.

In the future, we'll likely need to add "Activity" boundaries to this
tree as well, so that we can track what suspended the root of an
Activity when filtering a subtree. Similar to how the root SuspenseNode
now tracks suspending at the root. Maybe it's ok to just traverse to
collect this information on-demand when you select one though since this
doesn't contribute to the deduping.

We'll also need to add implicit Suspense boundaries for the rows of a
SuspenseList with `tail=hidden/collapsed`.
2025-07-30 09:55:09 -04:00
Sebastian "Sebbie" Silbermann
36c63d4f9c [DevTools] Layout for Suspense tab (#34042) 2025-07-30 07:12:18 +02:00
Joseph Savona
88b40f6e41 Enable ref validation in linter (#34044)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34044).
* #34027
* __->__ #34044
2025-07-29 12:30:29 -07:00
Joseph Savona
04a7a61918 [compiler] Allow assigning ref-accessing functions to objects if not mutated (#34026)
Allows assigning a ref-accessing function to an object so long as that
object is not subsequently transitively mutated. We should likely
rewrite the ref validation to use the new mutation/aliasing effects,
which would provide a more consistent behavior across instruction types
and require fewer special cases like this.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34026).
* #34027
* __->__ #34026
2025-07-29 10:57:26 -07:00
Joseph Savona
c2326b1336 [compiler] disallow ref access in state initializer, reducer/initializer (#34025)
Per title, disallow ref access in `useState()` initializer function,
`useReducer()` reducer, and `useReducer()` init function.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34025).
* #34027
* #34026
* __->__ #34025
2025-07-29 10:56:04 -07:00
Joseph Savona
4395689980 [compiler] ref guards apply up to fallthrough of the test (#34024)
Fixes #30782

When developers do an `if (ref.current == null)` guard for lazy ref
initialization, the "safe" blocks should extend up to the if's
fallthrough. Previously we only allowed writing to the ref in the if
consequent, but this meant that you couldn't use a ternary, logical, etc
in the if body.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34024).
* #34027
* #34026
* #34025
* __->__ #34024
2025-07-29 10:53:13 -07:00
Joseph Savona
6891dcb87d [compiler] treat ref-like identifiers as refs by default (#34005)
`@enableTreatRefLikeIdentifiersAsRefs` is now on by default. I made one
small fix to the render helper logic as part of this, uncovered by
including more tests.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34005).
* #34027
* #34026
* #34025
* #34024
* __->__ #34005
2025-07-29 10:51:10 -07:00
Joseph Savona
3f40eb73a8 [compiler] Allow passing refs to render helpers (#34006)
We infer render helpers as functions whose result is immediately
interpolated into jsx. This is a very conservative approximation, to
help with common cases like `<Foo>{props.renderItem(ref)}</Foo>`. The
idea is similar to hooks that it's ultimately on the developer to catch
ref-in-render validations (and the runtime detects them too), so we can
be a bit more relaxed since there are valid reasons to use this pattern.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34006).
* #34027
* #34026
* #34025
* #34024
* #34005
* __->__ #34006
* #34004
2025-07-29 10:06:23 -07:00
Joseph Savona
1d7e942da7 [compiler] Allow mergeRefs pattern (and detect refs passed as ref prop) (#34004)
Two related changes:
* ValidateNoRefAccessInRender now allows the mergeRefs pattern, ie a
function that aggregates multiple refs into a new ref. This is the main
case where we have seen false positive no-ref-in-render errors.
* Behind `@enableTreatRefLikeIdentifiersAsRefs`, we infer values passed
as the `ref` prop to some JSX as refs.

The second change is potentially helpful for situations such as

```js
function Component({ref: parentRef}) {
  const childRef = useRef(null);
  const mergedRef = mergeRefs(parentRef, childRef);
  useEffect(() => {
    // generally accesses childRef, not mergedRef
  }, []);
  return <Foo ref={mergedRef} />;
}
```

Ie where you create a merged ref but don't access its `.current`
property. Without inferring `ref` props as refs, we'd fail to allow this
merge refs case.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34004).
* #34027
* #34026
* #34025
* #34024
* #34005
* #34006
* __->__ #34004
2025-07-29 10:06:11 -07:00
Joseph Savona
79dc706498 [compiler] Improve ref validation error message (#34003)
Improves the error message for ValidateNoRefAccessInRender, using the
new diagnostic type as well as providing a longer but succinct summary
of what refs are for and why they're unsafe to access in render.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34003).
* #34027
* #34026
* #34025
* #34024
* #34005
* #34006
* #34004
* __->__ #34003
2025-07-29 10:03:28 -07:00
Joseph Savona
85bbe39ef8 [compiler] Fixes to enableTreatRefLikeIdentifiersAsRefs (#34000)
We added the `@enableTreatRefLikeIdentifiersAsRefs` feature a while back
but never enabled it. Since then we've continued to see examples that
motivate this mode, so here we're fixing it up to prepare to enable by
default. It now works as follows:

* If we find a property load or property store where both a) the
object's name is ref-like (`ref` or `-Ref`) and b) the property is
`current`, we infer the object itself as a ref and the value of the
property as a ref value. Originally the feature only detected property
loads, not stores.
* Inferred refs are not considered stable (this is a change from the
original implementation). The only way to get a stable ref is by calling
`useRef()`. We've seen issues with assuming refs are stable.

With this change, cases like the following now correctly error:

```js
function Foo(props) {
  const fooRef = props.fooRef;
  fooRef.current = true;
  ^^^^^^^^^^^^^^ cannot modify ref in render
}
```

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34000).
* #34027
* #34026
* #34025
* #34024
* #34005
* #34006
* #34004
* #34003
* __->__ #34000
2025-07-29 09:57:48 -07:00
lauren
820af20971 [eslint] Disallow use within try/catch blocks (#34040)
Follow up to #34032. The linter now ensures that `use` cannot be used
within try/catch.
2025-07-29 12:33:42 -04:00
Sebastian Markbåge
9be531cd37 [Fiber] Treat unwrapping React.lazy more like a use() (#34031)
While we want to get rid of React.lazy's special wrapper type and just
use a Promise for the type, we still have the wrapper.

However, this is still conceptually the same as a Usable in that it
should be have the same if you `use(promise)` or render a Promise as a
child or type position.

This PR makes it behave like a `use()` when we unwrap them. We could
move to a model where it actually reaches the internal of the Lazy's
Promise when it unwraps but for now I leave the lazy API signature
intact by just catching the Promise and then "use()" that.

This lets us align on the semantics with `use()` such as the suspense
yield optimization. It also lets us warn or fork based on legacy
throw-a-Promise behavior where as `React.lazy` is not deprecated.
2025-07-29 11:50:12 -04:00
Sebastian "Sebbie" Silbermann
b1cbb482d5 [DevTools] More robust resize handling (#34036) 2025-07-29 17:45:00 +02:00
Sebastian "Sebbie" Silbermann
9c9136b441 [DevTools] Swap Components tab layout based on container size (#34035) 2025-07-29 17:23:35 +02:00
Sebastian "Sebbie" Silbermann
33a2bf78c4 [DevTools] Silence unactionable bundle warnings in shell (#34034) 2025-07-29 11:18:47 +02:00
Sebastian Markbåge
5d7e8b90e2 [DevTools] Use use() instead of throwing a Promise in Caches (#34033) 2025-07-29 03:45:56 -04:00
Sebastian Markbåge
71236c9409 [DevTools] Include the description derived from the promise (#34017)
Stacked on #34016.

This is using the same thing we already do for the performance track to
provide a description of the I/O based on the content of the resolved
Promise. E.g. a Response's URL.

<img width="375" height="388" alt="Screenshot 2025-07-28 at 1 09 49 AM"
src="https://github.com/user-attachments/assets/f3fdc40f-4e21-4e83-b49e-21c7ec975137"
/>
2025-07-28 15:11:04 -04:00
lauren
7ee7571212 [compiler] Enable validateNoVoidUseMemo in eslint & playground (#34022)
Enables `validateNoVoidUseMemo` by default only in eslint (it defaults
to false otherwise) as well as the playground.
2025-07-28 13:42:14 -04:00
lauren
6b22f31f1a [compiler] Aggregate all errors reported from DropManualMemoization (#34002)
Noticed this from my previous PR that this pass was throwing on the
first error. This PR is a small refactor to aggregate every violation
and report them all at once.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34002).
* #34022
* __->__ #34002
2025-07-28 13:25:25 -04:00
lauren
b5c1637109 [compiler] Reuse DropManualMemoization for ValidateNoVoidUseMemo (#34001)
Much of the logic in the new validation pass is already implemented in
DropManualMemoization, so let's combine them. I opted to keep the
environment flag so we can more precisely control the rollout.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34001).
* #34022
* #34002
* __->__ #34001
2025-07-28 12:54:43 -04:00
lauren
c60eebffea [compiler] Add new ValidateNoVoidUseMemo pass (#33990)
Adds a new validation pass to validate against `useMemo`s that don't
return anything. This usually indicates some kind of "useEffect"-like
code that has side effects that need to be memoized to prevent
overfiring, and is an anti-pattern.

A follow up validation could also look at the return value of `useMemo`s
to see if they are being used.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33990).
* #34022
* #34002
* #34001
* __->__ #33990
* #33989
2025-07-28 12:46:42 -04:00
lauren
5dd622eabe [compiler] Disambiguate between void, implicit, and explicit returns (#33989)
Adds a new property to ReturnTerminals to disambiguate whether it was
explicit, implicit (arrow function expressions), or void (where it was
omitted). I will use this property in the next PR adding a new
validation pass.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33989).
* #34022
* #34002
* #34001
* #33990
* __->__ #33989
2025-07-28 12:46:30 -04:00
dan
904989f044 Clean up 19.1.1 changelog (#34023)
See
https://github.com/facebook/react/pull/34021#issuecomment-3128006800.

The purpose of the changelog is to communicate to React users what
changed in the release.

Therefore, it is important that the changelog is written oriented
towards React end users. Historically this means that we omit
internal-only changes, i.e. changes that have no effect on the end user
behavior. If internal changes are mentioned in the changelog (e.g. if
they affect end user behavior), they should be phrased in a way that is
understandable to the end user — in particular, they should not refer to
internal API names or concepts.

We also try to group changes according to the publicly known packages.

In this PR:

- Make #33680 an actual link (otherwise it isn't linkified in
CHANGELOG.md on GitHub).
- Remove two changelog entries listed under "React" that don't affect
anyone who upgrades the "React" package, that are phrased using
terminology and internal function names unfamiliar to React users, and
that seem to be RN-specific changes (so should probably go into the RN
changelog that goes out with the next renderer sync that includes these
changes).
2025-07-28 17:32:23 +01:00
Sebastian "Sebbie" Silbermann
ab2681af03 [DevTools] Skeleton for Suspense tab (#34020) 2025-07-28 18:26:55 +02:00
Sebastian Markbåge
101b20b663 [DevTools] Add a little bar indicating time span of an async entry relative to others (#34016)
Stacked on #34012.

This shows a time track for when some I/O started and when it finished
relative to other I/O in the same component (or later in the same
suspense boundary).

This is not meant to be a precise visualization since the data might be
misleading if you're running this in dev which has other perf
characteristics anyway. It's just meant to be a general way to orient
yourself in the data.

We can also highlight rejected promises here.

The color scheme is the same as Chrome's current Performance Track
colors to add continuity but those could change.

<img width="478" height="480" alt="Screenshot 2025-07-27 at 11 48 03 PM"
src="https://github.com/user-attachments/assets/545dd591-a91f-4c47-be96-41d80f09a94a"
/>
2025-07-28 12:22:33 -04:00
Jack Pope
eaee5308cc Add changelog entry for 19.1.1 (#34021)
Add changelog details matching release notes:
https://github.com/facebook/react/releases/tag/v19.1.1
2025-07-28 12:09:56 -04:00
Sebastian Markbåge
4a58b63865 [DevTools] Add "suspended by" Section to Component Inspector Sidebar (#34012)
This collects the ReactAsyncInfo between instances. It associates it
with the parent. Typically this would be a Server Component's Promise
return value but it can also be Promises in a fragment. It can also be
associated with a client component when you pass a Promise into the
child position e.g. `<div>{promise}</div>` then it's associated with the
div. If an instance is filtered, then it gets associated with the parent
of that's unfiltered.

The stack trace currently isn't source mapped. I'll do that in a follow
up.

We also need to add a "short name" from the Promise for the description
(e.g. url). I'll also add a little marker showing the relative time span
of each entry.

<img width="447" height="591" alt="Screenshot 2025-07-26 at 7 56 00 PM"
src="https://github.com/user-attachments/assets/7c966540-7b1b-4568-8cb9-f25cefd5a918"
/>
<img width="446" height="570" alt="Screenshot 2025-07-26 at 7 55 23 PM"
src="https://github.com/user-attachments/assets/4eac235b-e735-41e8-9c6e-a7633af64e4b"
/>
2025-07-28 12:05:56 -04:00
Hiroshi Ogawa
cc015840ef fix: React.use inside React.lazy-ed component on SSR (#33941) 2025-07-28 10:36:08 +02:00
Sebastian "Sebbie" Silbermann
19baee813c [Runtime] Fix CI (#33999) 2025-07-25 21:04:35 +02:00
Joseph Savona
2aa5f9d4e3 [compiler] fix false positive "mutate frozen" validation with refs (#33993)
The test case here previously reported a "Cannot modify local variables
after render completes" error (from
ValidateNoFreezingKnownMutableFunctions). This happens because one of
the functions passed to a hook clearly mutates a ref — except that we
try to ignore mutations of refs! The problem in this case is that the
`const ref = ...` was getting converted to a context variable since the
ref is accessed in a function before its declaration. We don't infer
types for context variables at all, and our ref handling is based on
types, so we failed to ignore this ref mutation.

The fix is to recognize that `StoreLocal const ...` is a special case:
the variable may be referenced in code before the declaration, but at
runtime it's either a TDZ error or the variable will have the type from
the declaration. So we can safely infer a type.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33993).
* __->__ #33993
* #33991
* #33984
2025-07-25 10:08:09 -07:00
Joseph Savona
8c587a2a41 [compiler] clarify text for setState-in-effect error (#33991)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33991).
* #33993
* __->__ #33991
* #33984
2025-07-25 10:07:55 -07:00
Joseph Savona
12483a119b [compiler] Fix for edge cases of mutation of potentially frozen values (#33984)
Fixes two related cases of mutation of potentially frozen values.

The first is method calls on frozen values. Previously, we modeled
unknown function calls as potentially aliasing their receiver+args into
the return value. If the receiver or argument were known to be frozen,
then we would downgrade the `Alias` effect into an `ImmutableCapture`.
However, within a function expression it's possible to call a function
using a frozen value as an argument (that gets `Alias`-ed into the
return) but where we don't have the context locally to know that the
value is frozen.

This results in cases like this:

```js
const frozen = useContext(...);
useEffect(() => {
  frozen.method().property = true;
  ^^^^^^^^^^^^^^^^^^^^^^^^ cannot mutate frozen value
}, [...]);
```

Within the function we would infer:

```
t0 = MethodCall ...
  Create t0 = mutable
  Alias t0 <- frozen
t1 = PropertyStore ...
  Mutate t0
```

And then transitively infer the function expression as having a `Mutate
'frozen'` effect, which when evaluated against the outer context
(`frozen` is frozen) is an error.

The fix is to model unknown function calls as _maybe_ aliasing their
receiver/args in the return, and then considering mutations of a
maybe-aliased value to only be a conditional mutation of the source:


```
t0 = MethodCall ...
  Create t0 = mutable
  MaybeAlias t0 <- frozen // maybe alias now
t1 = PropertyStore ...
  Mutate t0
```

Then, the `Mutate t0` turns into a `MutateConditional 'frozen'`, which
just gets ignored when we process the outer context.

The second, related fix is for known mutation of phis that may be a
frozen value. The previous inference model correctly recorded these as
errors, the new model does not. We now correctly report a validation
error for this case in the new model.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33984).
* #33993
* #33991
* __->__ #33984
2025-07-25 10:07:24 -07:00
Sebastian Markbåge
b2c30493ce [DevTools] Use the hard coded url instead of the local storage url for presets (and make VSCode default) (#33995)
Stacked on #33983.

Previously, the source of truth is the url stored in local storage but
that means if we change the presets then they don't take effect (e.g.
#33994). This PR uses the hardcoded value instead when a preset is
selected.

This also has the benefit that if you switch between custom and vs code
in the selector, then the custom url is preserved instead of getting
reset when you checkout other options.

Currently the default is custom with empty string, which means that
there's no code editor configured at all by default. It doesn't make a
lot of sense that we have it not working by default when so many people
use VS Code. So this also makes VS Code the default if there's no
EDITOR_URL env specified.
2025-07-25 10:27:27 -04:00
Sebastian Markbåge
36c2bf5c3e [DevTools] Allow all file links in Chrome DevTools to open in external editor (#33985)
Stacked on #33983.

Allow React to be configured as the default handler of all links in
Chrome DevTools. To do this you need to configure the Chrome DevTools
setting for "Link Handling:" to be set to "React Developer Tools". By
default this doesn't do anything but if you then check the box added in
#33983 it starts open local files directly in the external editor.

This needs docs to show how to enable that option.

(As far as I can tell this broke in Chrome Canary 🙄 but hopefully fixed
before stable.)
2025-07-25 10:27:09 -04:00
Sebastian Markbåge
190758e623 [DevTools] Add column to vscode editor preset (#33994)
We should jump to the right column.

Unfortunately, the way presets are set up now you have to switch off and
switch to the preset for this to take effect.
2025-07-25 10:21:00 -04:00
Sebastian Markbåge
b1a6f03f8a [DevTools] Rerender when the browser theme changes (#33992)
When the browser theme changes, we don't immediately rerender the UI so
we don't pick up the new theme if the React devtools are set to auto.

This picks up the change immediately.
2025-07-25 10:19:09 -04:00
Sebastian Markbåge
142fd27bf6 [DevTools] Add Option to Open Local Files directly in External Editor (#33983)
The `useOpenResource` hook is now used to open links. Currently, the
`<>` icon for the component stacks and the link in the bottom of the
components stack. But it'll also be used for many new links like stacks.
If this new option is configured, and this is a local file then this is
opened directly in the external editor. Otherwise it fallbacks to open
in the Sources tab or whatever the standalone or inline is configured to
use.

<img width="453" height="252" alt="Screenshot 2025-07-24 at 4 09 09 PM"
src="https://github.com/user-attachments/assets/04cae170-dd30-4485-a9ee-e8fe1612978e"
/>

I prominently surface this option in the Source pane to make it
discoverable.

<img width="588" height="144" alt="Screenshot 2025-07-24 at 4 03 48 PM"
src="https://github.com/user-attachments/assets/0f3a7da9-2fae-4b5b-90ec-769c5a9c5361"
/>

When this is configured, the "Open in Editor" is hidden since that's
just the default. I plan on deprecating this button to avoid having the
two buttons going forward.

Notably there's one exception where this doesn't work. When you click an
Action or Event listener it takes you to the Sources tab and you have to
open in editor from there. That's because we use the `inspect()`
mechanism instead of extracting the source location. That's because we
can't do the "throw trick" since these can have side-effects. The Chrome
debugger protocol would solve this but it pops up an annoying dialog. We
could maybe only attach the debugger only for that case. Especially if
the dialog disappears before you focus on the browser again.
2025-07-25 10:16:43 -04:00
Sebastian "Sebbie" Silbermann
7ca2d4cd2e Work around Chrome DevTools crash on performance.measure (#33997) 2025-07-25 12:32:30 +02:00
Sebastian Markbåge
99be14c883 [Flight] Promote enableAsyncDebugInfo to stable without enableComponentPerformanceTrack (#33996)
There's a lot of overlap between `enableComponentPerformanceTrack` and
`enableAsyncDebugInfo` because they both rely on timing information. The
former is mainly emit timestamps for how long server components and
awaits took. The latter how long I/O took.

`enableAsyncDebugInfo` is currently primarily for the component
performance track but its meta data is useful for other debug tools too.
This promotes that flag to stable.

However, `enableComponentPerformanceTrack` needs more work due to
performance concerns with Chrome DevTools so I need to separate them.
This keeps doing most of the timing tracking on the server but doesn't
emit the per-server component time stamps when
`enableComponentPerformanceTrack` is false.
2025-07-25 04:59:46 -04:00
Josh Story
5a04619f60 [Flight] Properly close stream when no chunks need to be written after prerender (#33982)
There is an edge case when prerendering where if you have nothing to
write you can end up in a state where the prerender is in status closed
before you can provide a destination. In this case the destination is
never closed becuase it assumes it already would have been.

This condition can happen now because of the introduction of the deubg
stream. Before this a request would never entere closed status if there
was no active destination. When a destination was added it would perform
a flush and possibly close the stream. Now, it is possible to flush
without a destination because you might have debug chunks to stream and
you can end up closing the stream independent of an active destination.

There are a number of ways we can solve this but the one that seems to
adhere best to the original design is to only set the status to CLOSED
when a destination is active. This means that if you don't have an
active destination when the pendingChunks count hits zero it will not
enter CLOSED status until you startFlowing.
2025-07-24 19:38:31 -07:00
Joseph Savona
129aa85e16 [compiler] Use diagnostic for "found suppression" error (#33981) 2025-07-24 15:54:24 -07:00
Joseph Savona
bcea86945c [compiler][rfc] Enable more validations in playground. (#33777)
This is mostly to kick off conversation, i think we should go with a
modified version of the implemented approach that i'll describe here.

The playground currently serves two roles. The primary one we think
about is for verifying compiler output. We use it for this sometimes,
and developers frequently use it for this, including to send us repros
if they have a potential bug. The second mode is to help developers
learn about React. Part of that includes learning how to use React
correctly — where it's helpful to see feedback about problematic code —
and also to understand what kind of tools we provide compared to other
frameworks, to make an informed choice about what tools they want to
use.

Currently we primarily think about the first role, but I think we should
emphasize the second more. In this PR i'm doing the worst of both:
enabling all the validations used by both the compiler and the linter by
default. This means that code that would actually compile can fail with
validations, which isn't great.

What I think we should actually do is compile twice, one in
"compilation" mode and once in "linter" mode, and combine the results as
follows:
* If "compilation" mode succeeds, show the compiled output _and_ any
linter errors.
* If "compilation" mode fails, show only the compilation mode failures.

We should also distinguish which case it is when we show errors:
"Compilation succeeded", "Compilation succeeded with linter errors",
"Compilation failed".

This lets developers continue to verify compiler output, while also
turning the playground into a much more useful tool for learning React.
Thoughts?

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33777).
* #33981
* __->__ #33777
2025-07-24 15:52:45 -07:00
Joseph Savona
2ae8b3dacf [compiler] Use new diagnostic printing in playground (#33767)
Per title

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33767).
* #33981
* #33777
* __->__ #33767
2025-07-24 15:47:56 -07:00
Joseph Savona
7f510554ad [compiler] Cleanup diagnostic messages (#33765)
Minor sytlistic cleanup

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33765).
* #33981
* #33777
* #33767
* __->__ #33765
2025-07-24 15:45:17 -07:00
Joseph Savona
a39da6c61f [compiler] Use new diagnostics for core inference errors (#33760)
Uses the new diagnostic type for errors created during mutation/aliasing
inference, such as errors for mutating immutable values like props or
state, reassigning globals, etc.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33760).
* #33981
* #33777
* #33767
* #33765
* __->__ #33760
2025-07-24 15:43:08 -07:00
Joseph Savona
48bc166428 [compiler] Update diagnostics for ValidatePreservedManualMemoization (#33759)
Uses the new diagnostic infrastructure for this validation, which lets
us provide a more targeted message on the text that we highlight (eg
"This dependency may be mutated later") separately from the overall
error message.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33759).
* #33981
* #33777
* #33767
* #33765
* #33760
* __->__ #33759
* #33758
2025-07-24 15:39:53 -07:00
Joseph Savona
72848027a5 [compiler] Improve more error messages (#33758)
This PR uses the new diagnostic type for most of the error messages
produced in our explicit validation passes (`Validation/` directory).
One of the validations produced multiple errors as a hack to showing
multiple related locations, which we can now consolidate into a single
diagnostic.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33758).
* #33981
* #33777
* #33767
* #33765
* #33760
* #33759
* __->__ #33758
2025-07-24 15:39:42 -07:00
Joseph Savona
707e321f8f [compiler][wip] Improve diagnostic infra (#33751)
Work in progress, i'm experimenting with revamping our diagnostic infra.
Starting with a better format for representing errors, with an ability
to point ot multiple locations, along with better printing of errors. Of
course, Babel still controls the printing in the majority case so this
still needs more work.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33751).
* #33981
* #33777
* #33767
* #33765
* #33760
* #33759
* #33758
* __->__ #33751
* #33752
* #33753
2025-07-24 15:37:06 -07:00
Joseph Savona
0d39496eab [compiler] Enable additional lints by default (#33752)
Enable more validations to help catch bad patterns, but only in the
linter. These rules are already enabled by default in the compiler _if_
violations could produce unsafe output.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33752).
* #33981
* #33777
* #33767
* #33765
* #33760
* #33759
* #33758
* #33751
* __->__ #33752
* #33753
2025-07-24 15:36:54 -07:00
Joseph Savona
6f4294af9b [compiler] Validate against setState in all effect types (#33753)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33753).
* #33981
* #33777
* #33767
* #33765
* #33760
* #33759
* #33758
* #33751
* #33752
* __->__ #33753
2025-07-24 15:36:40 -07:00
Joseph Savona
448f781a52 [compiler] Fix for false positive mutation of destructured spread object (#33786)
When destructuring, spread creates a new mutable object that _captures_
part of the original rvalue. This new value is safe to modify.

When making this change I realized that we weren't inferring array
pattern spread as creating an array (in type inference) so I also added
that here.
2025-07-24 15:16:28 -07:00
Sebastian Markbåge
5020d48d28 [DevTools] Feature detect createSidebarPane (#33988)
Same as #33987 but for the sidebar pane creation.
2025-07-24 17:42:06 -04:00
Sebastian Markbåge
3082604bdc [DevTools] Feature detect sources panel (#33987)
I broke Firefox DevTools extension in #33968.

It turns out the Firefox has a placeholder object for the sources panel
which is empty. We need to detect the actual event handler.
2025-07-24 17:38:12 -04:00
Sebastian Markbåge
4f34cc4a2e [Fiber] Don't throw away the Error object retaining the owner stack (#33976)
We currently throw away the Error once we've used to the owner stack of
a Fiber once. This maybe helps a bit with memory and redoing it but we
really don't expect most Fibers to hit this at all. It's not very hot.

If we throw away the Error, then we can't use native debugger protocols
to inspect the native stack. Instead, we'd have to maintain a url to
resource map indefinitely like what Chrome DevTools does to map a url to
a resource. Technically it's not even technically correct since the file
path might not be reversible and could in theory conflict.
2025-07-24 13:33:03 -04:00
Sebastian Markbåge
3d14fcf03f [Flight] Use about: protocol instead of rsc: protocol for fake evals (#33977)
Chrome DevTools Extensions has a silly problem where they block access
to load Resources from all protocols except [an allow
list](eb970fbc64/front_end/models/extensions/ExtensionServer.ts (L60)).

https://issues.chromium.org/issues/416196401

Even though these are `eval()` and not actually loaded from the network
they're blocked. They can really be any string. We just have to pick one
of:

```js
'http:', 'https:', 'file:', 'data:', 'chrome-extension:', 'about:'
```

That way React DevTools extensions can load this content to source map
them.

Webpack has the same issue with its `webpack://` and
`webpack-internal://` urls.
2025-07-24 11:07:11 -04:00
Sebastian Markbåge
edac0dded9 [DevTools] Add a Code Editor Sidebar Pane in the Chrome Sources Tab (#33968)
This adds a "Code Editor" pane for the Chrome extension in the bottom
right corner of the "Sources" panel. If you end up getting linked to the
"Sources" panel from stack traces in console, performance tab, stacks in
React Component tab like the one added in #33954 basically everywhere
there's a link to source code. Then going from there to open in a code
editor should be more convenient. This adds a button to open the current
file.

<img width="1387" height="389" alt="Screenshot 2025-07-22 at 10 22
19 PM"
src="https://github.com/user-attachments/assets/fe01f84c-83c2-4639-9b64-4af1a90c3f7d"
/>

This only makes sense in the extensions since in standalone it needs to
always open by default in an editor. Unfortunately Firefox doesn't
support extending the Sources panel.

Chrome is also a bit buggy where it doesn't send a selection update
event when you switch tabs in the Sources panel. Only when the actual
cursor position changes. This means that the link can be lagging behind
sometimes. We also have some general bugs where if React DevTools loses
connection it can break the UI which includes this pane too.

This has a small inline configuration too so that it's discoverable:

<img width="559" height="143" alt="Screenshot 2025-07-22 at 10 22 42 PM"
src="https://github.com/user-attachments/assets/1270bda8-ce10-4f9d-9fcb-080c0198366a"
/>

<img width="527" height="123" alt="Screenshot 2025-07-22 at 10 22 30 PM"
src="https://github.com/user-attachments/assets/45848c95-afd8-495f-a7cf-eb2f46e698f2"
/>

Since we can't add a separate link to open-in-editor or open-in-sources
everywhere I plan on adding an option to open in editor by default in a
follow up. That option needs to be even more discoverable.

I moved the configuration from the Components settings to the General
settings since this is now a much more general features for opening
links to resources in all types of panes.

<img width="673" height="311" alt="Screenshot 2025-07-22 at 10 22 57 PM"
src="https://github.com/user-attachments/assets/ea2c0871-942c-4b55-a362-025835d2c2bd"
/>
2025-07-23 10:28:11 -04:00
Sebastian Markbåge
3586a7f9e8 [DevTools] Allow file:/// urls to be opened in editor (#33965)
If a `file:///` path is specified as the url of a file, like after
source mapping into an ESM file, then we should be able to open it in a
code editor.
2025-07-23 10:21:50 -04:00
Sebastian "Sebbie" Silbermann
f6fb1a07a5 [Flight] Remove superfluous whitespace when console method is called with non-strings (#33953) 2025-07-23 10:07:37 +02:00
Sebastian Markbåge
7513996f20 [DevTools] Unify by using ReactFunctionLocation type instead of Source (#33955)
In RSC and other stacks now we use a lot of `ReactFunctionLocation` type
to represent the location of a function. I.e. the location of the
beginning of the function (the enclosing line/col) that is represented
by the "Source" of the function. This is also what the parent Component
Stacks represents.

As opposed to `ReactCallSite` which is what normal stack traces and
owner stacks represent. I.e. the line/column number of the callsite into
the next function.

We can start sharing more code by using the `ReactFunctionLocation` type
to represent the component source location and it also helps clarify
which ones are function locations and which ones are callsites as we
start adding more stack traces (e.g. for async debug info and owner
stack traces).
2025-07-22 10:53:08 -04:00
Sebastian Markbåge
bb4418d647 [DevTools] Linkify Source View (#33954)
This makes it so you can click the source location itself to view the
source. This is similar styling as the link to jump to function props
like events and actions. We're going to need a lot more linkifying to
jump to various source locations. Also, I always was trying to click
this file anyway.

Hover state:

<img width="485" height="382" alt="Screenshot 2025-07-21 at 4 36 10 PM"
src="https://github.com/user-attachments/assets/1f0f8f8c-6866-4e62-ab84-1fb5ba012986"
/>
2025-07-21 17:36:37 -04:00
Jordan Brown
074e92777c Change autodeps configuration (#33800) 2025-07-21 13:04:02 -07:00
Sebastian "Sebbie" Silbermann
ac7da9d46d [Flight] Make it more obvious what the short name in the I/O description represents (#33944) 2025-07-21 19:53:58 +02:00
Sebastian Markbåge
0dca9c2471 [Flight] Use the Promise of the first await even if that is cut off (#33948)
We need a "value" to represent the I/O that was loaded. We don't
normally actually use the Promise at the callsite that started the I/O
because that's usually deep inside internals. Instead we override the
value of the I/O entry with the Promise that was first awaited in user
space. This means that you could potentially have different values
depending on if multiple things await the same I/O. We just take one of
them. (Maybe we should actually just write the first user space awaited
Promise as the I/O entry? This might instead have other implications
like less deduping.)

When you pass a Promise forward, we may skip the awaits that happened in
earlier components because they're not part of the currently rendering
component. That's mainly for the stack and time stamps though. The value
is still probably conceptually the best value because it represents the
I/O value as far user space is concerned.

This writes the I/O early with the first await we find in user space
even if we're not going to use that particular await for the stack.
2025-07-21 13:22:10 -04:00
Sebastian Markbåge
b9af1404ea [Flight] Use the JSX as the await stack if an await is not available (#33947)
If you pass a promise to a client component to be rendered `<Client
promise={promise} />` then there's an internal await inside Flight.
There might also be user space awaits but those awaits may already have
happened before we render this component. Conceptually they were part of
the parent component and not this component. It's tricky to attribute
which await should be used for the stack in this case.

If we can't find an await we can use the JSX callsite as the stack
frame.

However, we don't want to do this for simple cases like if you return a
non-native Promise from a Server Component. Since that would now use the
stack of the thing that rendered the Server Component which is worse
than the stack of the I/O. To fix this, I update the
`debugOwner`/`debugTask`/`debugStack` when we start rendering inside the
Server Component. Conceptually these represent the "parent" component
and is used for errors referring to the parent like when we serialize
client component props the parent is the JSX of the client component.
However, when we're directly inside the Server Component we don't have a
callsite of the parent really. Conceptually it would be the return call
of the Server Component. This might negatively affect other types of
errors but I think this is ok since this feature mainly exists for the
case when you enter the child JSX.
2025-07-21 13:21:17 -04:00
Rubén Norte
e9638c33d7 Clean up feature flag to use lazy public instances in Fabric (#33943)
## Summary

We have thoroughly tested this flag in production and proved stability
and performance, so we can clean it up and "ship it".
2025-07-21 10:27:46 +01:00
Sebastian Markbåge
28d4bc496b [Flight] Make debug info and console log resolve in predictable order (#33665)
This resolves an outstanding issue where it was possible for debug info
and console logs to become out of order if they up blocked. E.g. by a
future reference or a client reference that hasn't loaded yet. Such as
if you console.log a client reference followed by one that doesn't. This
encodes the order similar to how the stream chunks work.

This also blocks the main chunk from resolving until the last debug info
has fully loaded, including future references and client references.
This also ensures that we could send some of that data in a different
stream, since then it can come out of order.
2025-07-19 20:13:26 -04:00
Jordan Brown
dffacc7b80 InferEffectDeps takes a React.AUTODEPS sigil (#33799)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33799).
* #33800
* __->__ #33799
2025-07-17 05:31:52 -07:00
Sebastian Markbåge
da7487b681 [Flight] Skip the stack frame of built-in wrappers that create or await Promises (#33798)
We already do this with `"new Promise"` and `"Promise.then"`. There are
also many helpers that both create promises and awaits other promises
inside of it like `Promise.all`.

The way this is filtered is different from just filtering out all
anonymous stacks since they're used to determine where the boundary is
between ignore listed and user space.

Ideally we'd cover more wrappers that are internal to Promise libraries.
2025-07-16 15:57:22 -04:00
Ruslan Lesiutin
9fec565a9b fix: log renders from passive effects for only newly finished work (#33797)
This fixes displaying incorrect component render entries on a timeline,
when we are reconnecting passive effects.

### Before
<img width="2318" height="1127" alt="1"
src="https://github.com/user-attachments/assets/9b6b2824-d2de-43a3-8615-2c45d67c3668"
/>

The cloned nodes will persist original `actualStartTime`, when these
were first mounted. When we "replay", the end time will be "now" or
whatever the actual start time of the sibling. Depending on when this is
being recorded, the diff between end and start could be tens of seconds
and doesn't represent what React was doing.

We shouldn't log these entries at all.

### After
We are only logging newly finished renders, but could potentially loose
renders that never commit.
2025-07-16 18:09:35 +01:00
Jack Pope
996d0eb055 Allow runtime_build_and_test action to trigger manually (#33796) 2025-07-16 12:41:35 -04:00
Sebastian "Sebbie" Silbermann
d85ec5f5bd [Flight] Assume __turbopack_load_by_url__ returns a cached Promise (#33792) 2025-07-16 13:20:10 +02:00
Henry Q. Dineen
fe813143e2 [compiler] Check TSAsExpression and TSNonNullExpression reorderability (#33788)
## Summary

The `TSAsExpression` and `TSNonNullExpression` nodes are supported by
`lowerExpression()` but `isReorderableExpression()` does not check if
they can be reordered. This PR updates `isReorderableExpression()` to
handle these two node types by adding cases that fall through to the
existing `TypeCastExpression` case.

We ran `react-compiler-healthcheck` at scale on several of our repos and
found dozens of `` (BuildHIR::node.lowerReorderableExpression)
Expression type `TSAsExpression` cannot be safely reordered`` errors and
a handful for `TSNonNullExpression`.


## How did you test this change?

In this case I added two fixture tests
2025-07-15 11:50:20 -07:00
Sebastian Markbåge
2f0e7e570d [Flight] Don't block on debug channel if it's not wired up (#33757)
React Elements reference debug data (their stack and owner) in the debug
channel. If the debug channel isn't wired up this can block the client
from resolving.

We can infer that if there's no debug channel wired up and the reference
wasn't emitted before the element, then it's probably because it's in
the debug channel. So we can skip it.

This should also apply to debug chunks but they're not yet blocking
until #33665 lands.
2025-07-15 11:45:34 -04:00
Sebastian "Sebbie" Silbermann
56d0ddae18 [Flight] Switch to __turbopack_load_by_url__ (#33791) 2025-07-15 16:55:31 +02:00
Sebastian "Sebbie" Silbermann
345ca24f13 [Flight] Remove unused fork configs (#33785) 2025-07-15 07:23:00 +02:00
Jordan Brown
97cdd5d3c3 [eslint] Do not allow useEffectEvent fns to be called in arbitrary closures (#33544)
Summary:

useEffectEvent is meant to be used specifically in combination with
useEffect, and using
the feature in arbitrary closures can lead to surprising reactivity
semantics. In order to
minimize risk in the experimental rollout, we are going to restrict its
usage to being
called directly inside an effect or another useEffectEvent, effectively
enforcing the function
coloring statically. Without an effect system this is the best we can
do.
2025-07-10 16:51:12 -04:00
Sebastian Markbåge
eb7f8b42c9 [Flight] Add Separate Outgoing Debug Channel (#33754)
This lets us pass a writable on the server side and readable on the
client side to send debug info through a separate channel so that it
doesn't interfere with the main payload as much. The main payload refers
to chunks defined in the debug info which means it's still blocked on it
though. This ensures that the debug data has loaded by the time the
value is rendered so that the next step can forward the data.

This will be a bit fragile to race conditions until #33665 lands.
Another follow up needed is the ability to skip the debug channel on the
receiving side. Right now it'll block forever if you don't provide one
since we're blocking on the debug data.
2025-07-10 16:22:44 -04:00
Sebastian Markbåge
eed2560762 [Flight] Treat empty message as a close signal (#33756)
We typically treat an empty message as closing the debug channel stream
but for the Noop renderer we don't use an intermediate stream but just
pass the message through.


bbc13fa17b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js (L59-L60)

For that simple case we should just treat it as a close without an
intermediate stream.
2025-07-10 16:16:57 -04:00
Josh Story
463b808176 [Fizz] Reset the segent id assignment when postponing the root (#33755)
When postponing the root we encode the segment Id into the postponed
state but we should really be reseting it to zero so we can restart the
counter from the beginning when the resume is actually just a re-render.

This also no longer assigns the root segment id based on the postponed
state when resuming the root for the same reason. In the future we may
use the embedded replay segment id if we implement resuming the root
without re-rendering everything but that is not yet implemented or
planned.
2025-07-10 12:12:09 -07:00
Joseph Savona
96c61b7f1f [compiler] Add CompilerError.UnsupportedJS variant (#33750)
We use this variant for syntax we intentionally don't support: with
statements, eval, and inline class declarations.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33750).
* #33753
* #33752
* #33751
* __->__ #33750
* #33748
2025-07-09 22:24:20 -07:00
Joseph Savona
0bfa404bac [compiler] More precise errors for invalid import/export/namespace statements (#33748)
import, export, and TS namespace statements can only be used at the
top-level of a module, which is enforced by parsers already. Here we add
a backup validation of that. As of this PR, we now have only major
statement type (class declarations) listed as a todo.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33748).
* #33753
* #33752
* #33751
* #33750
* __->__ #33748
2025-07-09 22:24:07 -07:00
Joseph Savona
81e1ee7476 [compiler] Support inline enums (flow/ts), type declarations (#33747)
Supports inline enum declarations in both Flow and TS by treating the
node as pass-through (enums can't capture values mutably). Related, this
PR extends the set of type-related declarations that we ignore.
Previously we threw a todo for things like DeclareClass or
DeclareVariable, but these are type related and can simply be dropped
just like we dropped TypeAlias.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33747).
* #33753
* #33752
* #33751
* #33750
* #33748
* __->__ #33747
2025-07-09 22:21:02 -07:00
Joseph Savona
4a3ff8eed6 [compiler] Errors for eval(), with statments, class declarations (#33746)
* Error for `eval()`
* More specific error message for `with (expr) { ... }` syntax
* More specific error message for class declarations

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33746).
* #33752
* #33751
* #33750
* #33748
* #33747
* __->__ #33746
2025-07-09 22:18:30 -07:00
Joseph Savona
ec4374c387 [compiler] Show logged errors in playground (#33740)
In playground it's helpful to show all errors, even those that don't
completely abort compilation. For example, to help demonstrate that the
compiler catches things like setState in effects. This detects these
errors and ensures we show them.
2025-07-09 09:22:49 -07:00
Sebastian Markbåge
60b5271a9a [Flight] Call finishHaltedTask on sync aborted tasks in stream abort listeners (#33743)
This is the same as we do for currently rendering tasks. They get
effectively sync aborted when the listener is invoked.

We potentially miss out on some debug info in that case but that would
only apply to any entries inside the stream which doesn't really have
their own debug info anyway.
2025-07-09 10:43:56 -04:00
Sebastian Markbåge
033edca721 [Flight] Yolo Retention of Promises (#33737)
Follow up to #33736.

If we need to save on CPU/memory pressure, we can instead just pray and
hope that a Promise doesn't get garbage collected before we need to read
it.

This can cause fragile access to the Promise value in devtools
especially if it's a slow and pressured render.

Basically, you'd have to hope that GC doesn't run after the inner await
finishes its microtask callback and before the resolution of the
component being rendered is invoked.
2025-07-09 10:39:08 -04:00
Sebastian Markbåge
e6dc25daea [Flight] Always defer Promise values if they're not already resolved (#33742)
If we have the ability to lazy load Promise values, i.e. if we have a
debug channel, then we should always use it for Promises that aren't
already resolved and instrumented.

There's little downside to this since they're async anyway.

This also lets us avoid adding `.then()` listeners too early. E.g. if
adding the listener would have side-effect. This avoids covering up
"unhandled rejection" errors. Since if we listen to a promise eagerly,
including reject listeners, we'd have marked that Promise's rejection as
handled where as maybe it wouldn't have been otherwise.

In this mode we can also indefinitely wait for the Promise to resolve
instead of just waiting a microtask for it to resolve.
2025-07-09 09:08:27 -04:00
Sebastian Markbåge
150f022444 [Flight] Ignore async stack frames when determining if a Promise was created from user space (#33739)
We use the stack of a Promise as the start of the I/O instead of the
actual I/O since that can symbolize the start of the operation even if
the actual I/O is batched, deduped or pooled. It can also group multiple
I/O operations into one.

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

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

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

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

---------

Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
2025-07-09 09:08:09 -04:00
Sebastian Markbåge
49ded1d12a [Flight] Optimize Retention of Weak Promises Abit (#33736)
We don't really need to retain a reference to whatever Promise another
Promise was created in. Only awaits need to retain both their trigger
and their previous context.
2025-07-09 09:07:06 -04:00
Sebastian Markbåge
3a43e72d66 [Flight] Create a fast path parseStackTrace which skips generating a string stack (#33735)
When we know that the object that we pass in is immediately parsed, then
we know it couldn't have been reified into a unstructured stack yet. In
this path we assume that we'll trigger `Error.prepareStackTrace`.

Since we know that nobody else will read the stack after us, we can skip
generating a string stack and just return empty. We can also skip
caching.
2025-07-09 09:06:55 -04:00
Sebastian Markbåge
8ba3501cd9 [Flight] Don't dedupe references to deferred objects (#33741)
If we're about to defer an object, then we shouldn't store a reference
to it because then we can end up deduping by referring to the deferred
string. If in a different context, we should still be able to emit the
object.
2025-07-08 21:47:33 -04:00
Joseph Savona
956d770adf [compiler] Improve IIFE inlining (#33726)
We currently inline IIFEs by creating a temporary and a labeled block w
the original code. The original return statements turn into an
assignment to the temporary and break out of the label. However, many
cases of IIFEs are due to inlining of manual `useMemo()`, and these
cases often have only a single return statement. Here, the output is
cleaner if we avoid the temporary and label - so that's what we do in
this PR.

Note that the most complex part of the change is actually around
ValidatePreserveExistingMemo - we have some logic to track the IIFE
temporary reassignmetns which needs to be updated to handle the simpler
version of inlining.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33726).
* __->__ #33726
* #33725
2025-07-08 16:36:57 -07:00
Joseph Savona
d35fef9e21 [compiler] Fix for consecutive DCE'd branches with phis (#33725)
This is an optimized version of @asmjmp0's fix in
https://github.com/facebook/react/pull/31940. When we merge consecutive
blocks we need to take care to rewrite later phis whose operands will
now be different blocks due to merging. Rather than iterate all the
blocks on each merge as in #31940, we can do a single iteration over all
the phis at the end to fix them up.

Note: this is a redo of #31959

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33725).
* #33726
* __->__ #33725
2025-07-08 16:36:47 -07:00
Sebastian Markbåge
a7a116577d [Flight] Don't track Promise stack if there's no owner (#33734)
This is a compromise because there can be a lot of Promise instances
created. They're useful because they generally provide a better stack
when batching/pooled connections are used.

This restores stack collection for I/O nodes so we have something to
fallback on if there's no owner.

That way we can at least get a name or something out of I/O that was
spawned outside a render but mostly avoids collecting starting I/O
outside of render.
2025-07-08 13:02:29 -04:00
Sebastian Markbåge
777264b4ef [Flight] Fix stack getting object limited (#33733)
Because the object limit is unfortunately depth first due to limitations
of JSON stringify, we need to ensure that things we really don't want
outlined are first in the enumeration order.

We add the stack length to the object limit to ensure that the stack
frames aren't outlined. In console all the user space arguments are at
the end of the args. In server component props, the props are at the end
of the properties of the element.

For the `value` of I/O we had it before the stack so it could steal the
limit from the stack. The fix is to put it at the end.
2025-07-08 12:54:29 -04:00
Josh Story
befc1246b0 [Fizz] Render preamble eagerly (#33730)
We unnecessarily render the preamble in a task. This updates the
implementation to perform this render inline.

Testing this is tricky because one of the only ways you could assert
this was even happening is based on how things error if you abort while
rendering the root.

While adding a test for this I discovered that not all abortable tasks
report errors when aborted during a normal render. I've asserted the
current behavior and will address the other issue at another time and
updated the assertion later as necessary
2025-07-08 08:20:12 -07:00
Sebastian Markbåge
bbea677b77 [Flight] Lazy load objects from the debug channel (#33728)
When a debug channel is available, we now allow objects to be lazily
requested though the debug channel and only then will the server send
it.

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

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

Currently debug chunks are eagerly parsed, which means that if you have
things like server component props that are lazy they can end up being
immediately asked for, but I'm trying to move to make the debug chunks
lazy.
2025-07-08 10:49:25 -04:00
Sebastian Markbåge
f1ecf82bfb [Flight] Optimize Async Stack Collection (#33727)
We need to optimize the collection of debug info for dev mode. This is
an incredibly hot path since it instruments all I/O and Promises in the
app.

These optimizations focus primarily on the collection of stack traces.
They are expensive to collect because we need to eagerly collect the
stacks since they can otherwise cause memory leaks. We also need to do
some of the processing of them up front. We also end up only using a few
of them in the end but we don't know which ones we'll use.

The first compromise here is that I now only collect the stacks of
"awaits" if they were in a specific request's render. In some cases it's
useful to collect them even outside of this if they're part of a
sequence that started early. I still collect stacks for the created
Promises outside of this though which can still provide some context.

The other optimization to awaits, is that since we'll only use the inner
most one that had an await directly in userspace, we can stop collecting
stacks on a chain of awaits after we find one. This requires a quick
filter on a single callsite to determine. Since we now only collect
stacks from awaits that belongs to a specific Request we can use that
request's specific filter option. Technically this might not be quite
correct if that same thing ends up deduped across Requests but that's an
edge case.

Additionally, I now stop collecting stack for I/O nodes. They're almost
always superseded by the Promise that wraps them anyway. Even if you
write mostly Promise free code, you'll likely end up with a Promise at
the root of the component eventually anyway and then you end up using
its stack anyway. You have to really contort the code to end up with
zero Promises at which point it's not very useful anyway. At best it's
maybe mostly useful for giving a name to the I/O when the rest is just
stuff like `new Promise`.

However, a possible alternative optimization could be to *only* collect
the stack of spawned I/O and not the stack of Promises. The issue with
Promises (not awaits) is that we never know what will end up resolving
them in the end when they're created so we have to always eagerly
collect stacks. This could be an issue when you have a lot of
abstractions that end up not actually be related to I/O at all. The
issue with collecting stacks only for I/O is that the actual I/O can be
pooled or batched so you end up not having the stack when the conceptual
start of each operation within the batch started. Which is why I decided
to keep the Promise stack.
2025-07-08 10:49:08 -04:00
Sebastian Markbåge
b44a99bf58 [Fiber] Name content inside "Suspense fallback" (#33724)
Same as #33723 but for Fiber.
2025-07-08 00:00:00 -04:00
Ricky
e4314a0a0f [tests] Assert on component stack for Maximum Update error (#33686)
Good to assert these include the component stack
2025-07-07 13:58:03 -04:00
Ricky
e43986f1f3 Finally remove favorSafetyOverHydrationPerf (#33619)
This is rolled out to 100%.

Let me merge it though.
2025-07-07 13:57:51 -04:00
Sebastian Markbåge
c932e45780 [Fizz] Name content inside "Suspense fallback" (#33723)
Content in Suspense fallbacks are really not considered part of the
Suspense but since it does have some behavior it should be marked
somehow separately from the Suspense content.

A follow up would be to do the same in Fiber.
2025-07-07 13:48:33 -04:00
Sebastian Markbåge
223f81d877 [Flight] Flush performance track once we have no more pending chunks (#33719)
Stacked on #33718. Alternative to #33716.

The issue with flushing the Server Components track in its current form
is that we need to decide how long to wait before flushing whatever we
have. That's because the root's end time will be determined by the end
time of that last child.

However, if a child isn't actually used then we don't necessarily need
to include it in the Server Components track since it wasn't blocking
the initial render.

This waits for 100ms after the last pending chunk is resolved and if
nothing is invoking any more lazy initializers after that then we log
the Server Components track with the information we have at that point.
We also don't eagerly initialize any chunks that wasn't already
initialized so if nothing was rendered, then nothing will be logged.

This is somewhat an artifact of the current visualization. If we did
another transposed form we wouldn't necessarily need to wait until the
end and can log things as they're discovered.
2025-07-07 11:42:30 -04:00
Sebastian Markbåge
8a6c589be7 [Flight] Keep a separate ref count for debug chunks (#33717)
Same as #33716 but without the separate close signal.

We'll need the ref count for separate debug channel anyway but I'm not
sure we'll need the separate close signal.
2025-07-07 11:42:20 -04:00
Sebastian Markbåge
7cafeff340 [Flight] Close Debug Channel when All Lazy References Have Been GC:ed (#33718)
When we have a debug channel open that can ask for more objects. That
doesn't close until all lazy objects have been explicitly asked for. If
you GC an object before the lazy references inside of it before asking
for or releasing the objects, then it'll never close.

This ensures that if there are no more PendingChunk and no more
ResolvedModelChunk then we can close the connection.

There's two sources of retaining the Response object. On one side we
have a handle to it from the stream coming from the server. On the other
side we have a handle to it from ResolvedModelChunk to ask for more data
when we lazily parse a model.

This PR makes a weak handle from the stream to the Response. However, it
keeps a strong reference alive whenever we're waiting on a pending chunk
because then the stream might be the root if the only listeners are the
callbacks passed to the promise and no references to the promise itself.

The pending chunks count can end up being zero even if we might get more
data because the references might be inside lazy chunks. In this case
the lazy chunks keeps the Response alive. When the lazy chunk gets
parsed it can find more chunks that then end up pending to keep the
response strongly alive until they resolve.
2025-07-07 11:28:15 -04:00
Sebastian Markbåge
0378b46e7e [Flight] Include I/O not awaited in user space (#33715)
If I/O is not awaited in user space in a "previous" path we used to just
drop it on the floor. There's a few strategies we could apply here. My
first commit just emits it without an await but that would mean we don't
have an await stack when there's no I/O in a follow up.

I went with a strategy where the "previous" I/O is used only if the
"next" didn't have I/O. This may still drop I/O on the floor if there's
two back to back within internals for example. It would only log the
first one even though the outer await may have started earlier.

It may also log deeper in the "next" path if that had user space stacks
and then the outer await will appear as if it awaited after.

So it's not perfect.
2025-07-07 10:33:27 -04:00
Sebastian "Sebbie" Silbermann
bb402876f7 [Flight] Pass line/column to filterStackFrame (#33707) 2025-07-07 13:51:53 +02:00
Sebastian Markbåge
9a645e1d10 [Flight] Ignore "new Promise" and async_hooks even if they're not ignore listed (#33714)
These are part of the internals of Promises and async functions even if
anonymous functions are otherwise not ignore listed.
2025-07-06 17:05:15 -04:00
Sebastian Markbåge
2d7f0c4259 [Flight] Insert an extra await node for awaiting on the promise returned by then callback (#33713)
When a `.then()` callback returns another Promise, there's effectively
another "await" on that Promise that happens in the internals but that
was not modeled. In effect the Promise returned by `.then()` is blocked
on both the original Promise AND the promise returned by the callback.

This models that by cloning the original node and treat that as the
await on the original Promise. Then we use the existing Node to await
the new Promise but its "previous" points to the clone. That way we have
a forked node that awaits both.

---------

Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
2025-07-06 15:34:36 -04:00
Sebastian "Sebbie" Silbermann
4aad5e45ba [Flight] Consistent format of virtual rsc: sources (#33706) 2025-07-06 09:45:43 +02:00
Sebastian Markbåge
453a19a107 [Flight] Collect Debug Info from Rejections in Aborted Render (#33708)
This delays the abort by splitting the abort into a first step that just
flags a task as abort and tracks the time that we aborted. This first
step also invokes the `cacheSignal()` abort handler.

Then in a macrotask do we finish flushing the abort (or halt). This
ensures that any microtasks after the abort signal can finish flushing
which may emit rejections or fulfill (e.g. if you try/catch the abort or
if it was allSettled). These rejections are themselves signals for which
promise was blocked on what promise which forms a graph that we can use
for debug info. Notably this doesn't include any additional data in the
output since we don't include any data produced after the abort. It just
uses the additional execution to collect more debug info.

The abort itself might not have been spawned from I/O but it's still
interesting to mark Promises that aborted as interesting since they may
have been blocked on I/O. So we take the inner most Promise that
resolved after the end time (presumably due to the abort signal but also
could've just finished after but that's still after the abort).

Since the microtasks can spawn new Promises after the ones that reject
we ignore any of those that started after the abort.
2025-07-05 17:01:41 -04:00
Ruslan Lesiutin
5d87cd2244 React DevTools 6.1.4 -> 6.1.5 (#33702)
Same as 6.1.4, but with 2 hotfixes:
* fix: check if profiling for all profiling hooks
([hoxyq](https://github.com/hoxyq) in
[#33701](https://github.com/facebook/react/pull/33701))
* fix: fallback to reading string stack trace when failed
([hoxyq](https://github.com/hoxyq) in
[#33700](https://github.com/facebook/react/pull/33700))
2025-07-04 16:31:00 +01:00
Ruslan Lesiutin
5f71eed2eb [devtools] fix: check if profiling for all profiling hooks (#33701)
Follow-up to https://github.com/facebook/react/pull/33652.

Don't know how the other were missed. Double-checked that Profiler works
in dev mode.

Now all hooks start with `!isProfiling` check and return, if true.
2025-07-04 16:21:51 +01:00
Ruslan Lesiutin
455424dbf3 [devtools] fix: fallback to reading string stack trace when failed (#33700)
Discovered while testing with Hermes.
2025-07-04 15:36:52 +01:00
Ruslan Lesiutin
9fd4c09d68 React DevTools 6.1.3 -> 6.1.4 (#33699)
Changes from 6.1.3:
* feat: static Components panel layout
([hoxyq](https://github.com/hoxyq) in
[#33696](https://github.com/facebook/react/pull/33696))
* fix: support optionality of structured stack trace function name
([hoxyq](https://github.com/hoxyq) in
[#33697](https://github.com/facebook/react/pull/33697))
* fix: rename bottom stack frame ([hoxyq](https://github.com/hoxyq) in
[#33680](https://github.com/facebook/react/pull/33680))
2025-07-04 12:55:53 +01:00
Ruslan Lesiutin
d45db667d4 feat: static Components panel layout (#33696)
## Summary

Follow-up to https://github.com/facebook/react/pull/33517.

With https://github.com/facebook/react/pull/33517, we now preserve at
least some minimal indent. This actually doesn't work with the current
setup, because we don't allow the container to overflow, so basically
deeply nested elements will go off the screen.

With these changes, we completely change the approach:
- The layout will be static and it will have a constant indentation that
will always be preserved.
- The container will allow overflows, so users will be able to scroll
horizontally and vertically.
- We will implement automatic horizontal and vertical scrolls, if
selected element is not in a viewport.
- New: added vertical delimiter that can be used for simpler visual
navigation.

## Demo
### Current public release

https://github.com/user-attachments/assets/58645d42-c6b8-408b-b76f-95fb272f2e1e

### With https://github.com/facebook/react/pull/33517 

https://github.com/user-attachments/assets/845285c8-5a01-4739-bcd7-ffc089e771bf

### This PR

https://github.com/user-attachments/assets/72086b84-8d84-4626-94b3-e22e114e028e
2025-07-04 12:29:19 +01:00
Ruslan Lesiutin
3fc1bc6f28 [devtools] fix: support optionality of structured stack trace function name (#33697)
Follow-up to https://github.com/facebook/react/pull/33680.

Turns out `.getFunctionName` not always returns string.
2025-07-04 10:32:09 +01:00
Sebastian Markbåge
ef8b6fa257 [Flight] Don't double badge consoles that are replayed from a third party (#33685)
If a FlightClient runs inside a FlightServer like fetching from a third
party and that logs, then we currently double badge them since we just
add on another badge. The issue is that this might be unnecessarily
noisy but we also transfer the original format of the current server
into the second badge.

This extracts our own badge and then adds the environment name as
structured data which lets the client decide how to format it.

Before:

<img width="599" alt="Screenshot 2025-07-02 at 2 30 07 PM"
src="https://github.com/user-attachments/assets/4bf26a29-b3a8-4024-8eb9-a3f90dbff97a"
/>

After:

<img width="590" alt="Screenshot 2025-07-02 at 2 32 56 PM"
src="https://github.com/user-attachments/assets/f06bbb6d-fbb1-4ae6-b0e3-775849fe3c53"
/>
2025-07-02 18:22:14 -04:00
Sebastian Markbåge
0b78161d7d [Fiber] Highlight a Component with Deeply Equal Props in the Performance Track (#33660)
Stacked on #33658 and #33659.

If we detect that a component is receiving only deeply equal objects,
then we highlight it as potentially problematic and worth looking into.

<img width="1055" alt="Screenshot 2025-06-27 at 4 15 28 PM"
src="https://github.com/user-attachments/assets/e96c6a05-7fff-4fd7-b59a-36ed79f8e609"
/>

It's fairly conservative and can bail out for a number of reasons:

- We only log it on the first parent that triggered this case since
other children could be indirect causes.
- If children has changed then we bail out since this component will
rerender anyway. This means that it won't warn for a lot of cases that
receive plain DOM children since the DOM children won't themselves get
logged.
- If the component's total render time including children is 100ms or
less then we skip warning because rerendering might not be a big deal.
- We don't warn if you have shallow equality but could memoize the JSX
element itself since we don't typically recommend that and React
Compiler doesn't do that. It only warns if you have nested objects too.
- If the depth of the objects is deeper than like the 3 levels that we
print diffs for then we wouldn't warn since we don't know if they were
equal (although we might still warn on a child).
- If the component had any updates scheduled on itself (e.g. setState)
then we don't warn since it would rerender anyway. This should really
consider Context updates too but we don't do that atm. Technically you
should still memoize the incoming props even if you also had unrelated
updates since it could apply to deeper bailouts.
2025-07-02 17:33:07 -04:00
Sebastian Markbåge
dcf83f7c2d Disable ScrollTimeline in Safari (#33499)
Stacked on #33501.

This disables the use of ScrollTimeline when detected in Safari in the
recommended SwipeRecognizer approach. I'm instead using a polyfill using
touch events on iOS.

Safari seems set to [release ScrollTimeline
soon](https://webkit.org/blog/16993/news-from-wwdc25-web-technology-coming-this-fall-in-safari-26-beta/).
Unfortunately it's not really what you'd expect.

First of all, [it's not running in sync with the
scroll](https://bugs.webkit.org/show_bug.cgi?id=288402) which is kind of
its main point. Instead, it is running at 60fps and out of sync with the
scroll just like JS. In fact, it is worse than JS because with JS you
can at least spawn CSS animations that run at 120fps. So our polyfill
can respond to touches at 60fps while gesturing and then run at 120fps
upon release. That's better than with ScrollTimeline.

Second, [there's a bug which interrupts scrolling if you start a
ViewTransition](https://bugs.webkit.org/show_bug.cgi?id=288795) when the
element is being removed as part of that. The element can still respond
to touches so in a polyfill this isn't an issue. But it essentially
makes it useless to use ScrollTimeline with swipe-away gestures.

So we're better off in every scenario by not using it.

The UA detection is a bit unfortunate. Not sure if there's something
more specific but we also had to do a UA detection for Chrome for View
Transitions. Those are the only two we have in all of React.


![safarimeme](https://github.com/user-attachments/assets/d4ca9eba-489e-4ade-b462-2ffeee3a470c)
2025-07-02 17:01:49 -04:00
Sebastian Markbåge
94fce500bc [Flight] Use a heuristic to extract a useful description of I/O from the Promise value (#33662)
It's useful to be able to distinguish between different invocations of
common helper libraries (like fetch) without having to click through
each one.

This adds a heuristic to extract a useful description of I/O from the
Promise value. We try to find things like getUser(id) -> User where
User.id is the id or fetch(url) -> Response where Response.url is the
url.

For urls we use the filename (or hostname if there is none) as the short
name if it can fit. The full url is in the tooltip.

<img width="845" alt="Screenshot 2025-06-27 at 7 58 20 PM"
src="https://github.com/user-attachments/assets/95f10c08-13a8-449e-97e8-52f0083a65dc"
/>
2025-07-02 16:12:37 -04:00
Sebastian Markbåge
508f7aa78f [Fiber] Switch back to using performance.measure for trigger logs (#33659)
Stacked on #33658.

Unfortunately `console.timeStamp` has the same bug that
`performance.measure` used to have where equal start/end times stack in
call order instead of reverse call-order. We rely on that in general so
we should really switch back all.

But there is one case in particular where we always add the same
start/time and that's for the "triggers" -
Mount/Unmount/Reconnect/Disconnect. Switching to `console.timeStamp`
broke this because they now showed below the thing that mounted.

After:

<img width="726" alt="Screenshot 2025-06-27 at 3 31 16 PM"
src="https://github.com/user-attachments/assets/422341c8-bef6-4909-9403-933d76b71508"
/>

Also fixed a bug where clamped update times could end up logging zero
width entries that stacked up on top of each other causing a two row
scheduler lane which should always be one row.
2025-07-02 16:10:52 -04:00
Sebastian Markbåge
e104795f63 [Fiber] Show Diff Render Props in Performance Track in DEV (#33658)
<img width="634" alt="Screenshot 2025-06-27 at 1 13 20 PM"
src="https://github.com/user-attachments/assets/dc8c488b-4a23-453f-918f-36b245364934"
/>

We have to be careful with performance in DEV. It can slow down DX since
these are ran whether you're currently running a performance trace or
not. It can also show up as misleading since these add time to the
"Remaining Effects" entry.

I'm not adding all props to the entries. Instead, I'm only adding the
changed props after diffing and none for initial mount. I'm trying to as
much as possible pick a fast path when possible. I'm also only logging
this for the "render" entries and not the effects. If we did something
for effects, it would be more like checking with dep changed.

This could still have a negative effect on dev performance since we're
now also using the slower `performance.measure` API when there's a diff.
2025-07-02 16:10:07 -04:00
Sebastian Markbåge
c0d151ce7e Clear width/height from Keyframes to Optimize View Transitions (#33576)
View Transitions has this annoying quirk where it adds `width` and
`height` to keyframes automatically when generating keyframes even when
it's not needed. This causes them to deopt from running on the
compositor thread in both Chrome and Safari. @bramus has a [good article
on
it](https://www.bram.us/2025/02/07/view-transitions-applied-more-performant-view-transition-group-animations/).

In React we can automatically rewrite the keyframes when we're starting
a View Transition to drop the `width` and `height` from the keyframes
when they have the same value and the same value as the pseudo element.

To compare it against the pseudo element we first apply the new
keyframes without the width/height and then read it back to see if it
has changed. For gestures, we have already cancelled the previous
animation so we can just read out from that.
2025-07-02 16:09:26 -04:00
Sebastian Markbåge
fc41c24aa6 Add ScrollTimeline Polyfill for Swipe Recognizer using a new CustomTimeline protocol (#33501)
The React API is just that we now accept this protocol as an alternative
to a native `AnimationTimeline` to be passed to
`startGestureTransition`. This is specifically the DOM version.

```js
interface CustomTimeline {
  currentTime: number;
  animate(animation: Animation): void | (() => void);
}
```

Instead, of passing this to the `Animation` that we start to control the
View Transition keyframes, we instead inverse the control and pass the
`Animation` to this one. It lets any custom implementation drive the
updates. It can do so by updating the time every frame or letting it run
a time based animation (such as momentum scroll).

In this case I added a basic polyfill for `ScrollTimeline` in the
example but we'll need a better one.
2025-07-02 16:07:46 -04:00
Jan Kassens
73aa744b70 Remove now dead argument from resolveClassComponentProps (#33682)
No longer used after https://github.com/facebook/react/pull/33648
2025-07-02 10:45:37 -04:00
Jan Kassens
602917c8cb Cleanup disableDefaultPropsExceptForClasses flag (#33648) 2025-07-01 15:52:56 -04:00
Ruslan Lesiutin
91d097b2c5 fix: rename bottom stack frame (#33680)
`react-stack-bottom-frame` -> `react_stack_bottom_frame`.

This survives `@babel/plugin-transform-function-name`, but now frames
will be displayed as `at Object.react_stack_bottom_frame (...)` in V8.
Checks that were relying on exact function name match were updated to
use either `.indexOf()` or `.includes()`

For backwards compatibility, both React DevTools and Flight Client will
look for both options. I am not so sure about the latter and if React
version is locked.
2025-07-01 18:06:26 +01:00
Sebastian Markbåge
7216c0f002 [Flight] Don't assume _debugStack and _owner is defined for prod elements (#33675)
We generally treat these types of fields as optional on ReactDebugInfo
and should on ReactElement too.

That way we can consume prod payloads from third parties.
2025-06-30 16:15:19 -04:00
Jan Kassens
6a3d16ca74 Back out "Remove Dead Code in WWW JS" (#33673)
Original commit changeset: 65c4decb56

This was removed by dead code removal. Adding back the TODO with
commented out code.
2025-06-30 15:26:45 -04:00
Facebook Community Bot
65c4decb56 Remove Dead Code in WWW JS
Differential Revision: D77531947

Pull Request resolved: https://github.com/facebook/react/pull/33672
2025-06-30 08:24:29 -07:00
Dawid Małecki
1e0d12b6f2 Align AttributeConfiguration type in ReactNativeTypes (#33671) 2025-06-30 15:36:49 +01:00
Sebastian Markbåge
e9cab42ece Special case printing Promises in Performance Track Properties (#33670)
Before:
<img width="266" alt="Screenshot 2025-06-30 at 8 32 23 AM"
src="https://github.com/user-attachments/assets/98aae5e1-4b2c-49bd-9b71-040b788c36ba"
/>

After:
<img width="342" alt="Screenshot 2025-06-30 at 8 39 17 AM"
src="https://github.com/user-attachments/assets/cd91c4a6-f6ae-4bec-9cd9-f42f4af468fe"
/>
2025-06-30 09:21:04 -04:00
Sebastian Markbåge
3cfcdfb307 [Flight] Resolve Deep Cycles (#33664)
Stacked on #33666.

If we ever get a future reference to a cycle and that reference gets
eagerly parsed before the target has loaded then we can end up with a
cycle that never gets resolved. That's because our cycle resolution only
works if the cyclic future reference is created synchronously within the
parsing path of the child.

I haven't been able to construct a normal scenario where this would
break. So this doesn't fail any tests. However, I can construct it with
debug info since those are eagerly evaluated. It's also a prerequisite
if the debug data can come out of order, like if it's on a different
stream.

The fix here is to make all the internal dependencies in the "listener"
list into introspectable objects instead of closures. That way we can
traverse the list of dependencies of a blocked reference to see if it
ends up in a cycle and therefore skip the reference.

It would be nice to address this once and for all to be more resilient
to server changes, but I'm not sure if it's worth this complexity and
the extra CPU cost of tracing the dependencies. Especially if it's just
for debug data.

closes #32316
fixes vercel/next.js#72104

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
2025-06-29 10:56:16 -04:00
Sebastian Markbåge
9c2a8dd5f8 [Flight] Ensure we dedupe references if we later discover that it's the model root (#33666)
I noticed we weren't deduping these cases.
2025-06-29 10:47:33 -04:00
Sebastian Markbåge
811e203ed4 [Flight] Don't replay performance logs when replayConsoleLogs is false (#33656)
This is the same principle. They're both side-effects and go to the
`console.*` namespace.
2025-06-27 16:27:45 -04:00
Ruslan Lesiutin
d92056efb3 React DevTools 6.1.2 -> 6.1.3 (#33657)
Full list of changes:

* devtools: emit performance entries only when profiling
([hoxyq](https://github.com/hoxyq) in
[#33652](https://github.com/facebook/react/pull/33652))
* Get Server Component Function Location for Parent Stacks using Child's
Owner Stack ([sebmarkbage](https://github.com/sebmarkbage) in
[#33629](https://github.com/facebook/react/pull/33629))
* Added minimum indent size to Component Tree
([jsdf](https://github.com/jsdf) in
[#33517](https://github.com/facebook/react/pull/33517))
* [devtools-shell] layout options for testing
([jsdf](https://github.com/jsdf) in
[#33516](https://github.com/facebook/react/pull/33516))
* Remove feature flag enableRenderableContext
([kassens](https://github.com/kassens) in
[#33505](https://github.com/facebook/react/pull/33505))
* refactor[devtools]: update css for settings and support css variables
in shadow dom scnenario ([hoxyq](https://github.com/hoxyq) in
[#33487](https://github.com/facebook/react/pull/33487))
* [mcp] Add MCP tool to print out the component tree of the currently
open React App ([jorge-cab](https://github.com/jorge-cab) in
[#33305](https://github.com/facebook/react/pull/33305))
* [scripts] Switch back to flow parser for prettier
([rickhanlonii](https://github.com/rickhanlonii) in
[#33414](https://github.com/facebook/react/pull/33414))
* upgrade json5 ([rickhanlonii](https://github.com/rickhanlonii) in
[#33358](https://github.com/facebook/react/pull/33358))
* Get source location from structured callsites in prepareStackTrace
([sebmarkbage](https://github.com/sebmarkbage) in
[#33143](https://github.com/facebook/react/pull/33143))
* Clean up enableSiblingPrerendering flag
([jackpope](https://github.com/jackpope) in
[#32319](https://github.com/facebook/react/pull/32319))
2025-06-27 16:17:08 +01:00
Ruslan Lesiutin
58ac15cdc9 devtools: emit performance entries only when profiling (#33652)
## Summary

This floods Timings track in dev mode and also hurts performance in dev.

Making sure we are buffering Performance entries (all of them are marks)
only when profiling in RDT. This should be removed once we roll out Perf
tracks.
2025-06-27 15:32:08 +01:00
Sebastian Markbåge
bfc8801e0f [Flight] Write Debug Info to Separate Priority Queue (#33654)
This writes all debug info to a separate priority queue. In the future
I'll put this on a different channel.

Ideally I think we'd put it in the bottom of the stream but because it
actually blocks the elements from resolving anyway it ends up being
better to put them ahead. At least for now.

When we have two separate channels it's not possible to rely on the
order for consistency Even then we might write to that queue first for
this reason. We can't rely on it though. Which will show up like things
turning into Lazy instead of Element similar to how outlining can.
2025-06-27 09:45:11 -04:00
Sebastian Markbåge
d2a288febf Include Component Props in Performance Track (#33655)
Similar to how we can include a Promise resolved value we can include
Component Props.

For now I left out props for Client Components for perf unless they
error. I'll try it for Client Components in general in a separate PR.

<img width="730" alt="Screenshot 2025-06-26 at 5 54 29 PM"
src="https://github.com/user-attachments/assets/f0c86911-2899-4b5f-b45f-5326bdbc630f"
/>
<img width="762" alt="Screenshot 2025-06-26 at 5 54 12 PM"
src="https://github.com/user-attachments/assets/97540d19-5950-4346-99e6-066af086040e"
/>
2025-06-27 08:45:56 -04:00
Dhruv
4db4b21c63 Fix typo "Complier" to "Compiler" and remove duplicate issue reference (#33653)
<!--
  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
Fixed a typo in the changelog.md file: corrected "Complier" to
"Compiler" and removed a duplicate issue reference for improved clarity.
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

## How did you test this change?
Manually reviewed the changelog text to ensure correctness. No code
changes were made.
<!--
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.
-->
2025-06-26 08:34:45 -07:00
Sebastian Markbåge
31d91651e0 [Fizz] Rename ReactFizzContext to ReactFizzLegacyContext (#33649)
#33622 forgot these.
2025-06-25 21:18:25 -04:00
Sebastian Markbåge
9406162bc9 [Flight] Emit start time before an await if one wasn't emitted already (#33646)
There's a special case where if we create a new task, e.g. to serialize
a promise like `<div>{promise}</div>` then that row doesn't have any
start time emitted but it has a `task.time` inherited. We mostly don't
need this because every other operation emits its own start time. E.g.
when we started rendering a Server Component or the real start time of a
real `await`.

For these implied awaits we don't have a start time. Ideally it would
probably be when we started the serialization, like when we called
`.then()` but we can't just emit that eagerly and we can't just advance
the `task.time` because that time represents the last render or previous
await and we use that to cut off awaits. However for this case we don't
want to cut off any inner awaits inside the node we're serializing if
they happened before the `.then()`.

Therefore, I just use the time of the previous operation - which is
likely either the resolution of a previous promise that blocked the
`<div>` like the promise of the Server Component that rendered it, or
just the start of the Server Component if it was sync.
2025-06-25 17:28:59 -04:00
Hendrik Liebau
9b2a545b32 [Flight] Add tests for component and owner stacks of halted components (#33644)
This PR adds tests for the Node.js and Edge builds to verify that
component stacks and owner stacks of halted components appear as
expected, now that recent enhancements for those have been implemented
(the latest one being #33634).

---------

Co-authored-by: Sebastian "Sebbie" Silbermann <silbermann.sebastian@gmail.com>
2025-06-25 22:34:35 +02:00
Sebastian Markbåge
bb6c9d521e [Flight] Log aborted await and component renders (#33641)
<img width="926" alt="Screenshot 2025-06-25 at 1 02 14 PM"
src="https://github.com/user-attachments/assets/1877d13d-5259-4cc4-8f48-12981e3073fe"
/>

The I/O entry doesn't show as aborted in the Server Request track
because technically it wasn't. The end time is just made up. It's still
going. It's not aborted until the abort signal propagates and if we do
get that signal wired up before it emits, it instead would show up as
rejected.

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
2025-06-25 16:28:54 -04:00
Joseph Savona
123ff13b19 [compiler] Consolidate HIRFunction return information (#33640)
We now have `HIRFunction.returns: Place` as well as `returnType: Type`.
I want to add additional return information, so as a first step i'm
consolidating everything under an object at `HIRFunction.returns:
{place: Place}`. We use the type of this place as the return type. Next
step is to add more properties to this object to represent things like
the return kind.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33640).
* #33643
* #33642
* __->__ #33640
* #33625
* #33624
2025-06-25 11:10:38 -07:00
Joseph Savona
e130c08b06 [compiler] Avoid empty switch cases (#33625)
Small cosmetic win, found this when i was looking at some code
internally with lots of cases that all share the same logic. Previously,
all the but last one would have an empty block.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33625).
* #33643
* #33642
* #33640
* __->__ #33625
* #33624
2025-06-25 11:10:26 -07:00
Joseph Savona
9894c488e0 [compiler] Fix bug with reassigning function param in destructuring (#33624)
Closes #33577, a bug with ExtractScopeDeclarationsFromDestructuring and
codegen when a function param is reassigned.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33624).
* #33643
* #33642
* #33640
* #33625
* __->__ #33624
2025-06-25 11:10:09 -07:00
Sebastian Markbåge
cee7939b00 [Fizz] Push a stalled await from debug info to the ownerStack/debugTask (#33634)
If an aborted task is not rendering, then this is an async abort.
Conceptually it's as if the abort happened inside the async gap. The
abort reason's stack frame won't have that on the stack so instead we
use the owner stack and debug task of any halted async debug info.

One thing that's a bit awkward is that if you do have a sync abort and
you use that error as the "reason" then that thing still has a sync
stack in a different component. In another approach I was exploring
having different error objects for each component but I don't think
that's worth it.
2025-06-25 11:14:49 -04:00
Sebastian Markbåge
b42341ddc7 [Flight] Use cacheController instead of abortListeners for Streams (#33633)
Now that we have `cacheSignal()` we can just use that instead of the
`abortListeners` concept which was really just the same thing for
cancelling the streams (ReadableStream, Blob, AsyncIterable).
2025-06-25 09:41:21 -04:00
Pieter De Baets
7a3ffef703 [react-native] Consume ReactNativeAttributePayloadFabric from ReactNativePrivateInterface (#33616)
## Summary

ReactNativeAttributePayloadFabric was synced to react-native in
0e42d33cbc.
We should now consume these methods from the
ReactNativePrivateInterface.

Moving these methods to the React Native repo gives us more flexibility
to experiment with new techniques for bridging and diffing props
payloads.

I did have to leave some stub implementations for existing unit tests,
but moved all detailed tests to the React Native repo.

## How did you test this change?

* `yarn prettier`
* `yarn test ReactFabric-test`
2025-06-25 10:23:36 +01:00
Sebastian Markbåge
e67b4fe22e [Flight] Emit Partial Debug Info if we have any at the point of aborting a render (#33632)
When we abort a render we don't really have much information about the
task that was aborted. Because before a Promise resolves there's no
indication about would have resolved it. In particular we don't know
which I/O would've ultimately called resolve().

However, we can at least emit any information we do have at the point
where we emit it. At the least the stack of the top most Promise.

Currently we synchronously flush at the end of an `abort()` but we
should ideally schedule the flush in a macrotask and emit this debug
information right before that. That way we would give an opportunity for
any `cacheSignal()` abort to trigger rejections all the way up and those
rejections informs the awaited stack.

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
2025-06-24 16:36:21 -04:00
Sebastian Markbåge
4a523489b7 Get Server Component Function Location for Parent Stacks using Child's Owner Stack (#33629)
This is using the same trick as #30798 but for runtime code too. It's
essential zero cost.

This lets us include a source location for parent stacks of Server
Components when it has an owned child's location. Either from JSX or
I/O.

Ironically, a Component that throws an error will likely itself not get
the stack because it won't have any JSX rendered yet.
2025-06-24 16:35:28 -04:00
Joseph Savona
94cf60bede [compiler] New inference repros/fixes (#33584)
Substantially improves the last major known issue with the new inference
model's implementation: inferring effects of function expressions. I
knowingly used a really simple (dumb) approach in
InferFunctionExpressionAliasingEffects but it worked surprisingly well
on a ton of code. However, investigating during the sync I saw that we
the algorithm was literally running out of memory, or crashing from
arrays that exceeded the maximum capacity. We were accumluating data
flow in a way that could lead to lists of data flow captures compounding
on themselves and growing very large very quickly. Plus, we were
incorrectly recording some data flow, leading to cases where we reported
false positive "can't mutate frozen value" for example.

So I went back to the drawing board. InferMutationAliasingRanges already
builds up a data flow graph which it uses to figure out what values
would be affected by mutations of other values, and update mutable
ranges. Well, the key question that we really want to answer for
inferring a function expression's aliasing effects is which values
alias/capture where. Per the docs I wrote up, we only have to record
such aliasing _if they are observable via mutations_. So, lightbulb:
simulate mutations of the params, free variables, and return of the
function expression and see which params/free-vars would be affected!
That's what we do now, giving us precise information about which such
values alias/capture where. When the "into" is a param/context-var we
use Capture, iwhen the destination is the return we use Alias to be
conservative.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33584).
* #33626
* #33625
* #33624
* __->__ #33584
2025-06-24 10:01:58 -07:00
Sebastian Markbåge
bbc13fa17b [Flight] Add Debug Channel option for stateful connection to the backend in DEV (#33627)
This adds plumbing for opening a stream from the Flight Client to the
Flight Server so it can ask for more data on-demand. In this mode, the
Flight Server keeps the connection open as long as the client is still
alive and there's more objects to load. It retains any depth limited
objects so that they can be asked for later. In this first PR it just
releases the object when it's discovered on the server and doesn't
actually lazy load it yet. That's coming in a follow up.

This strategy is built on the model that each request has its own
channel for this. Instead of some global registry. That ensures that
referential identity is preserved within a Request and the Request can
refer to previously written objects by reference.

The fixture implements a WebSocket per request but it doesn't have to be
done that way. It can be multiplexed through an existing WebSocket for
example. The current protocol is just a Readable(Stream) on the server
and WritableStream on the client. It could even be sent through a HTTP
request body if browsers implemented full duplex (which they don't).

This PR only implements the direction of messages from Client to Server.
However, I also plan on adding Debug Channel in the other direction to
allow debug info (optionally) be sent from Server to Client through this
channel instead of through the main RSC request. So the `debugChannel`
option will be able to take writable or readable or both.

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
2025-06-24 11:16:09 -04:00
Ricky
12eaef7ef5 [refactor] remove unused fiberstack functions (#33623) 2025-06-23 20:07:04 -04:00
Sebastian Markbåge
c80c69fa96 [Flight] Remove back pointers to the Response from the Chunks (#33620)
This frees some memory that will be even more important in a follow up.

Currently, all `ReactPromise` instances hold onto their original
`Response`. The `Response` holds onto all objects that were in that
response since they're needed in case the parsed content ends up
referring to an existing object. If everything you retain are plain
objects then that's fine and the `Response` gets GC:ed, but if you're
retaining a `Promise` itself then it holds onto the whole `Response`.

The only thing that needs this reference at all is a
`ResolvedModelChunk` since it will lazily initialize e.g. by calling
`.then` on itself and so we need to know where to find any sibling
chunks it may refer to. However, we can just store the `Response` on the
`reason` field for this particular state.

That way when all lazy values are touched and initialized the `Response`
is freed. We also free up some memory by getting rid of the extra field.
2025-06-23 18:37:52 -04:00
Jan Kassens
aab72cb1cb rename ReactFiberContext to ReactFiberLegacyContext (#33622)
It wasn't immediately obvious to me, that all the exports here are
related to legacy context, so renaming for clarity.

Modern context lives in `ReactFiberNewContext` which we could probably
also raname in a separate step to just Context.
2025-06-23 17:21:18 -04:00
Sebastian "Sebbie" Silbermann
fa3feba672 Fix prelease workflows for dry: false (#33582)
## Summary

Follow-up to https://github.com/facebook/react/pull/33525

Fixes `Unsupported tag: "false"`
(https://github.com/facebook/react/actions/runs/15773778995/job/44463562733#step:13:12)
which also affects nightly releases.

## How did you test this change?

- [x] Run successful, manual prerelease from this branch:
https://github.com/facebook/react/actions/runs/15774083406
2025-06-23 11:47:07 -04:00
Sebastian Markbåge
2a911f27dd [Flight] Send the awaited Promise to the client as additional debug information (#33592)
Stacked on #33588, #33589 and #33590.

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

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

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

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

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

Additionally, to get the best possible value we don't want to get a
Promise that's used by internals of a third-party function. We want the
value that the first party gets to observe. To do this I had to change
the logic for which "await" to use, to be the one that is the first
await that happened in user space. It's not enough that the await has
any first party at all on the stack - it has to be the very first frame.
This is a little sketchy because it relies on the `.then()` call or
`await` call not having any third party wrappers. But it gives the best
object since it hides all the internals. For example when you call
`fetch()` we now log that actual `Response` object.
2025-06-23 10:12:45 -04:00
Sebastian Markbåge
18ee505e77 [Flight] Support classes in renderDebugModel (#33590)
This adds better support for serializing class instances as Debug
values.

It adds a new marker on the object `{ "": "$P...", ... }` which
indicates which constructor's prototype to use for this object's
prototype. It doesn't encode arbitrary prototypes and it doesn't encode
any of the properties on the prototype. It might get some of the
properties from the prototype by virtue of `toString` on a `class`
constructor will include the whole class's body.

This will ensure that the instance gets the right name in logs.

Additionally, this now also invokes getters if they're enumerable on the
prototype. This lets us reify values that can only be read from native
classes.

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
2025-06-22 18:00:08 -04:00
Sebastian Markbåge
1d1b26c701 [Flight] Serialize already resolved Promises as debug models (#33588)
We already support serializing the values of instrumented Promises as
debug values such as in console logs. However, we don't support plain
native promises.

This waits a microtask to see if we can read the value within a
microtask and if so emit it. This is so that we can still close the
connection.

Otherwise, we emit a "halted" row into its row id which replaces the old
"Infinite Promise" reference.

We could potentially wait until the end of the render before cancelling
so that if it resolves before we exit we can still include its value but
that would require a bit more work. Ideally we'd have a way to get these
lazily later anyway.
2025-06-22 17:51:31 -04:00
Sebastian Markbåge
fe3f0ec037 [Flight] Don't use object property initializer for async iterable (#33591)
It turns out this was being compiled to a `_defineProperty` helper by
Babel or Closure. We're supposed to have it error the build when we use
features like this that might get compiled.

We should stick to simple ES5 features.
2025-06-22 10:40:56 -04:00
Sebastian Markbåge
d70ee32b88 [Flight] Eagerly parse stack traces in DebugNode (#33589)
There's a memory leak in DebugNode where the `Error` objects that we
instantiate retains their callstacks which can have Promises on them. In
fact, it's very likely since the current callsite has the "resource" on
it which is the Promise itself. If those Promises are retained then
their `destroy` async hook is never fired which doesn't clean up our map
which can contains the `Error` object. Creating a cycle that can't be
cleaned up.

This fix is just eagerly reifying and parsing the stacks.

I totally expect this to be crazy slow since there's so many Promises
that we end up not needing to visit otherwise. We'll need to optimize it
somehow. Perhaps by being smarter about which ones we might need stacks
for. However, at least it doesn't leak indefinitely.
2025-06-22 10:40:33 -04:00
Sebastian Markbåge
6c7b1a1d98 Rename serializeConsoleMap/Set to serializeDebugMap/Set (#33587)
Follow up to #33583. I forgot to rename these too.
2025-06-21 10:36:07 -04:00
Sebastian Markbåge
ed077194b5 [Flight] Dedupe objects serialized as Debug Models in a separate set (#33583)
Stacked on #33539.

Stores dedupes of `renderConsoleValue` in a separate set. This allows us
to dedupe objects safely since we can't write objects using this
algorithm if they might also be referenced by the "real" serialization.

Also renamed it to `renderDebugModel` since it's not just for console
anymore.
2025-06-20 13:36:39 -04:00
Devon Govett
643257ca52 [Flight] Serialize functions by reference (#33539)
On pages that have a high number of server components (e.g. common when
doing syntax highlighting), the debug outlining can produce extremely
large RSC payloads. For example a documentation page I was working on
had a 13.8 MB payload. I noticed that a majority of this was the source
code for the same function components repeated over and over again (over
4000 times) within `$E()` eval commands.

This PR deduplicates the same functions by serializing by reference,
similar to what is already done for objects. Doing this reduced the
payload size of my page from 13.8 MB to 4.6 MB, and resulted in only 31
evals instead of over 4000. As a result it reduced development page load
and hydration time from 4 seconds to 1.5 seconds. It also means the
deserialized functions will have reference equality just as they did on
the server.
2025-06-20 13:36:07 -04:00
Sebastian "Sebbie" Silbermann
06e89951be [Fizz] Ignore error if content node is gone before reveal (#33531) 2025-06-20 14:21:57 +02:00
Sebastian Markbåge
79d9aed7ed [Fizz] Clean up the replay nodes if we're already rendered past an element (#33581) 2025-06-20 09:26:26 +02:00
Sebastian "Sebbie" Silbermann
c8822e926b Make it clearer what runtime release failed (#33579) 2025-06-20 09:11:27 +02:00
Sebastian "Sebbie" Silbermann
a947eba4f2 Fix CI (#33578) 2025-06-19 23:40:59 +02:00
Ruslan Lesiutin
374dfe8edf build: make enableComponentPerformanceTrack dynamic for native-fb (#33560)
## Summary

Make this flag dynamic, so it can be controlled internally.

## How did you test this change?

Build, observe that `console.timeStamp` is only present in FB artifacts
and `enableComponentPerformanceTrack` is referenced.
2025-06-19 09:47:23 +01:00
Joseph Savona
2bee34867d [compiler] Cleanup debugging code (#33571)
Removes unnecessary debugging code in the new inference passes now that
they've stabilized more.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33571).
* __->__ #33571
* #33558
* #33547
2025-06-18 16:00:55 -07:00
Joseph Savona
d37faa041b [compiler] Preserve Create effects, guarantee effects initialize once (#33558)
Ensures that effects are well-formed with respect to the rules:
* For a given instruction, each place is only initialized once (w one of
Create, CreateFrom, Assign)
* Ensures that Alias targets are already initialized within the same
instruction (should have a Create before them)
* Preserves Create and similar instructions
* Avoids duplicate instructions when inferring effects of function
expressions

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33558).
* #33571
* __->__ #33558
* #33547
2025-06-18 16:00:45 -07:00
Joseph Savona
3a2ff8b51b [compiler] Fix <ValidateMemoization> (#33547)
By accident we were only ever checking the compiled output, but the
intention was in general to be able to compare memoization with/without
forget.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33547).
* #33571
* #33558
* __->__ #33547
2025-06-18 16:00:36 -07:00
Joseph Savona
cc3806377a [compiler] Tests for different orders of createfrom/capture w/wo function expressions (#33543)
Adds some typed helpers to represent aliasing, assign, capture,
createfrom, and mutate effects along with representative runtime
behavior, and then adds tests to demonstrate that we model
capture->createfrom and createfrom->capture correctly.

There is one case (createfrom->capture in a lambda) where we infer a
less precise effect, but in the more conservative direction (we include
more code/deps than necesssary rather than fewer).

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33543).
* #33571
* #33558
* #33547
* __->__ #33543
2025-06-18 15:56:27 -07:00
Joseph Savona
4f543f326c [compiler] Docs describing new inference model (#33533)
Start of docs describing the effects and the inference rules.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33533).
* #33571
* #33558
* #33547
* #33543
* __->__ #33533
* #33532
* #33530
2025-06-18 15:48:01 -07:00
Joseph Savona
7ceb10035f [compiler] Rename InferFunctionExprAliasingEffectsSignature (#33532)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33532).
* #33571
* #33558
* #33547
* #33543
* #33533
* __->__ #33532
* #33530
2025-06-18 15:47:52 -07:00
Joseph Savona
4335f69987 [compiler] More readable alias signature declarations (#33530)
Now that we have support for defining aliasing signatures in
moduleTypeProvider, which uses string names for
receiver/args/returns/etc, we can reuse that same form for builtin
declarations. The declarations are written in the unparsed form and than
parsed/validated when registered (in the addFunction/addHook call).

This also required flushing out configs/schemas for more effect types.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33530).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* __->__ #33530
2025-06-18 15:47:43 -07:00
Joseph Savona
34179fe344 [compiler] moduleTypeProvider support for aliasing signatures (#33526)
This allows us to type things like `nullthrows()` or `identity()`
functions where the return type is polymorphic on the input.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33526).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* __->__ #33526
* #33522
* #33518
2025-06-18 15:43:48 -07:00
Joseph Savona
0e7cdebb32 [compiler] Repro for case of lost precision in new inference (#33522)
In comparing compilation output of the old/new inference models I found
this case (heavily distilled into a fixture). Roughly speaking the
scenario is:

* Create a mutable object `x`
* Extract part of that object and pass it to a hook/jsx so that _part_
becomes frozen
* Mutate `x`, even indirectly.

In the old model we can still independently memoize the value from the
middle step, since we assume that part of the larger value is not
changing. In the new model, the mutation from the later step effectively
overrides the freeze effect in step 2, and considers the value to have
changed later anyway.

We've already rolled out and vetted the previous behavior, confirming
that the heuristic of "that part of the mutable object is fozen now" is
generally safe. I'll fix in a follow-up.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33522).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* __->__ #33522
* #33518
2025-06-18 15:43:33 -07:00
Joseph Savona
81d8115116 [compiler] Fix infinite loop due to uncached applied signatures (#33518)
When we apply new aliasing signatures we can generate new temporaries,
which causes the abstract memory model to not converge. The fix is to
make sure we cache the applications of these signatures.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33518).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* __->__ #33518
2025-06-18 15:43:23 -07:00
Joseph Savona
8f4ce72f0b [commit] Improve error for hoisting violations (#33514)
The previous error for hoisting violations pointed only to the variable
declaration, but didn't show where the value was accessed before that
declaration. We now track where each hoisted variable is first accessed
and report two errors, one for the reference and one for the
declaration. When we improve our diagnostic infra to support reporting
errors at multiple locations we can merge these into a single conceptual
error.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33514).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* __->__ #33514
* #33573
2025-06-18 15:24:41 -07:00
Joseph Savona
7ce2a63acc [compiler] update fixtures (#33573)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33573).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* __->__ #33573
2025-06-18 15:24:30 -07:00
Joseph Savona
b067c6fe79 [compiler] Improve error message for mutating hook args/return (#33513)
The previous error message was generic, because the old style function
signature didn't support a way to specify a reason alongside a freeze
effect. This meant we could only say why a value was frozen for
instructions, but not hooks which use function signatures. By defining a
new aliasing signature for custom hooks we can specify a reason and
provide a better error message.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33513).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* __->__ #33513
2025-06-18 13:04:53 -07:00
Joseph Savona
e081cb3446 [compiler] FunctionExpression context locations point to first reference (#33512)
This has always been awkward: `FunctionExpression.context` places have
locations set to the declaration of the identifier, whereas other
references have locations pointing to the reference itself. Here, we
update context operands to have their location point to the first
reference of that variable within the function.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33512).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* __->__ #33512
* #33504
* #33500
* #33497
* #33496
2025-06-18 13:02:43 -07:00
Joseph Savona
7b67dc92b0 [commit] Better error message for invalid hoisting (#33504)
We're already tracking which variables are hoisted context variables, so
if we see a mutation of a frozen value we can emit a custom error
message to help users identify the problem.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33504).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* #33512
* __->__ #33504
* #33500
* #33497
* #33496
2025-06-18 13:02:32 -07:00
Joseph Savona
7c28c15465 [compiler] Fix AnalyzeFunctions to fully reset context identifiers (#33500)
AnalyzeFunctions had logic to reset the mutable ranges of context
variables after visiting inner function expressions. However, there was
a bug in that logic: InferReactiveScopeVariables makes all the
identifiers in a scope point to the same mutable range instance. That
meant that it was possible for a later function expression to indirectly
cause an earlier function expressions' context variables to get a
non-zero mutable range.

The fix is to not just reset start/end of context var ranges, but assign
a new range instance. Thanks for the help on debugging, @mofeiz!

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33500).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* #33512
* #33504
* __->__ #33500
* #33497
* #33496
2025-06-18 13:02:23 -07:00
Joseph Savona
90ccbd71c1 [compiler] Enable new inference by default (#33497)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33497).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* #33512
* #33504
* #33500
* __->__ #33497
* #33496
2025-06-18 13:02:12 -07:00
Joseph Savona
0cf6d0c929 [compiler] Update fixtures for new inference (#33496)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33496).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* #33512
* #33504
* #33500
* #33497
* __->__ #33496
2025-06-18 13:01:56 -07:00
Joseph Savona
df080d228b [compiler] Copy fixtures affected by new inference (#33495)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33495).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* #33512
* #33504
* #33500
* #33497
* #33496
* __->__ #33495
* #33494
* #33572
2025-06-18 12:58:16 -07:00
Joseph Savona
66cfe048d3 [compiler] New mutability/aliasing model (#33494)
Squashed, review-friendly version of the stack from
https://github.com/facebook/react/pull/33488.

This is new version of our mutability and inference model, designed to
replace the core algorithm for determining the sets of instructions
involved in constructing a given value or set of values. The new model
replaces InferReferenceEffects, InferMutableRanges (and all of its
subcomponents), and parts of AnalyzeFunctions. The new model does not
use per-Place effect values, but in order to make this drop-in the end
_result_ of the inference adds these per-Place effects.

I'll write up a larger document on the model, first i'm doing some
housekeeping to rebase the PR.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33494).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* #33512
* #33504
* #33500
* #33497
* #33496
* #33495
* __->__ #33494
* #33572
2025-06-18 12:58:06 -07:00
Joseph Savona
ae962653d6 [compiler] Remove unnecessary fixture (#33572)
This is covered by iife-inline-ternary

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33572).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* #33512
* #33504
* #33500
* #33497
* #33496
* #33495
* #33494
* __->__ #33572
2025-06-18 12:57:54 -07:00
Sebastian Markbåge
e1dc03492e Expose cacheSignal() alongside cache() (#33557)
This was really meant to be there from the beginning. A `cache()`:ed
entry has a life time. On the server this ends when the render finishes.
On the client this ends when the cache of that scope gets refreshed.

When a cache is no longer needed, it should be possible to abort any
outstanding network requests or other resources. That's what
`cacheSignal()` gives you. It returns an `AbortSignal` which aborts when
the cache lifetime is done based on the same execution scope as a
`cache()`ed function - i.e. `AsyncLocalStorage` on the server or the
render scope on the client.

```js
import {cacheSignal} from 'react';
async function Component() {
  await fetch(url, { signal: cacheSignal() });
}
```

For `fetch` in particular, a patch should really just do this
automatically for you. But it's useful for other resources like database
connections.

Another reason it's useful to have a `cacheSignal()` is to ignore any
errors that might have triggered from the act of being aborted. This is
just a general useful JavaScript pattern if you have access to a signal:

```js
async function getData(id, signal) {
  try {
     await queryDatabase(id, { signal });
  } catch (x) {
     if (!signal.aborted) {
       logError(x); // only log if it's a real error and not due to cancellation
     }
     return null;
  }
}
```

This just gets you a convenient way to get to it without drilling
through so a more idiomatic code in React might look something like.

```js
import {cacheSignal} from "react";

async function getData(id) {
  try {
     await queryDatabase(id);
  } catch (x) {
     if (!cacheSignal()?.aborted) {
       logError(x);
     }
     return null;
  }
}
```

If it's called outside of a React render, we normally treat any cached
functions as uncached. They're not an error call. They can still load
data. It's just not cached. This is not like an aborted signal because
then you couldn't issue any requests. It's also not like an infinite
abort signal because it's not actually cached forever. Therefore,
`cacheSignal()` returns `null` when called outside of a React render
scope.

Notably the `signal` option passed to `renderToReadableStream` in both
SSR (Fizz) and RSC (Flight Server) is not the same instance that comes
out of `cacheSignal()`. If you abort the `signal` passed in, then the
`cacheSignal()` is also aborted with the same reason. However, the
`cacheSignal()` can also get aborted if the render completes
successfully or fatally errors during render - allowing any outstanding
work that wasn't used to clean up. In the future we might also expand on
this to give different
[`TaskSignal`](https://developer.mozilla.org/en-US/docs/Web/API/TaskSignal)
to different scopes to pass different render or network priorities.

On the client version of `"react"` this exposes a noop (both for
Fiber/Fizz) due to `disableClientCache` flag but it's exposed so that
you can write shared code.
2025-06-17 17:04:40 -04:00
Jordan Brown
90bee81902 [compiler] Do not inline IIFEs in value blocks (#33548)
As discussed in chat, this is a simple fix to stop introducing labels
inside expressions.

The useMemo-with-optional test was added in
d70b2c2c4e
and crashes for the same reason- an unexpected label as a value block
terminal.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33548).
* __->__ #33548
* #33546
2025-06-16 21:53:50 -04:00
Jordan Brown
75e78d243f [compiler] Add repro for IIFE in ternary causing a bailout (#33546)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33546).
* #33548
* __->__ #33546
2025-06-16 21:53:27 -04:00
Jan Kassens
5d24c64cc9 Remove feature flag enableDO_NOT_USE_disableStrictPassiveEffect (#33524) 2025-06-16 12:22:47 -04:00
lauren
6b7e207cab [ci] Don't skip experimental prerelease incorrectly (#33527)
Previously the experimental workflow relied on the canary one running
first to avoid race conditions. However, I didn't account for the fact
that the canary one can now be skipped.
2025-06-13 15:29:59 -04:00
lauren
d60f77a533 [ci] Update prerelease workflows to allow publishing specific packages (#33525)
It may be useful at times to publish only specific packages as an
experimental tag. For example, if we need to cherry pick some fixes for
an old release, we can first do so by creating that as an experimental
release just for that package to allow for quick testing by downstream
projects.

Similar to .github/workflows/runtime_releases_from_npm_manual.yml I
added three options (`dry`, `only_packages`, `skip_packages`) to
`runtime_prereleases.yml` which both the manual and nightly workflows
reuse. I also added a discord notification when the manual workflow is
run.
2025-06-13 14:22:55 -04:00
James Friend
12bc60f509 [devtools] Added minimum indent size to Component Tree (#33517)
## Summary

The devtools Components tab's component tree view currently has a
behavior where the indentation of each level of the tree scales based on
the available width of the view. If the view is narrow or component
names are long, all indentation showing the hierarchy of the tree scales
down with the view width until there is no indentation at all. This
makes it impossible to see the nesting of the tree, making the tree view
much less useful. With long component names and deep hierarchies this
issue is particularly egregious. For comparison, the Chrome Dev Tools
Elements panel uses a fixed indentation size, so it doesn't suffer from
this issue.

This PR adds a minimum pixel value for the indentation width, so that
even when the window is narrow some indentation will still be visible,
maintaining the visual representation of the component tree hierarchy.

Alternatively, we could match the behavior of the Chrome Dev Tools and
just use a constant indentation width.

## How did you test this change?

- tests (yarn test-build-devtools)
- tested in browser:
- added an alternate left/right split pane layout to
react-devtools-shell to test with
(https://github.com/facebook/react/pull/33516)
- tested resizing the tree view in different layout modes

### before this change:



https://github.com/user-attachments/assets/470991f1-dc05-473f-a2cb-4f7333f6bae4

with a long component name:



https://github.com/user-attachments/assets/1568fc64-c7d7-4659-bfb1-9bfc9592fb9d





### after this change:




https://github.com/user-attachments/assets/f60bd7fc-97f6-4680-9656-f0db3d155411

with a long component name:


https://github.com/user-attachments/assets/6ac3f58c-42ea-4c5a-9a52-c3b397f37b45
2025-06-13 15:28:31 +01:00
James Friend
ed023cfc73 [devtools-shell] layout options for testing (#33516)
## Summary

This PR adds a 'Layout' selector to the devtools shell main example, as
well as a resizable split pane, allowing more realistic testing of how
the devtools behaves when used in a vertical or horizontal layout and at
different sizes (e.g. when resizing the Chrome Dev Tools pane).

## How did you test this change?



https://github.com/user-attachments/assets/81179413-7b46-47a9-bc52-4f7ec414e8be
2025-06-13 15:25:04 +01:00
Sebastian "Sebbie" Silbermann
a00ca6f6b5 [Fizz] Delay detachment of completed boundaries until reveal (#33511) 2025-06-11 21:24:24 +02:00
lauren
888ea60d8e [compiler][repro] Postfix operator is incorrectly compiled (#33508)
This bug was reported via our wg and appears to only affect values
created as a ref.

Currently, postfix operators used in a callback gets compiled to:

```js
modalId.current = modalId.current + 1; // 1
const id = modalId.current; // 1
return id;
```

which is semantically incorrect. The postfix increment operator should
return the value before incrementing. In other words something like this
should have been compiled instead:

```js
const id = modalId.current; // 0
modalId.current = modalId.current + 1; // 1
return id;
```

This bug does not trigger when the incremented value is a plain
primitive, instead there is a TODO bailout.
2025-06-11 14:40:42 -04:00
Jan Kassens
b7e2de632b Stringify context as SomeContext instead of SomeContext.Provider (#33507)
This matches the change in React 19 to use `<SomeContext>` as the
preferred way to provide a context.
2025-06-11 12:08:04 -04:00
Sebastian Markbåge
ff93c4448c [Flight] Track Debug Info from Synchronously Unwrapped Promises (#33485)
Stacked on #33482.

There's a flaw with getting information from the execution context of
the ping. For the soft-deprecated "throw a promise" technique, this is a
bit unreliable because you could in theory throw the same one multiple
times. Similarly, a more fundamental flaw with that API is that it
doesn't allow for tracking the information of Promises that are already
synchronously able to resolve.

This stops tracking the async debug info in the case of throwing a
Promise and only when you render a Promise. That means some loss of data
but we should just warn for throwing a Promise anyway.

Instead, this also adds support for tracking `use()`d thenables and
forwarding `_debugInfo` from then. This is done by extracting the info
from the Promise after the fact instead of in the resolve so that it
only happens once at the end after the pings are done.

This also supports passing the same Promise in multiple places and
tracking the debug info at each location, even if it was already
instrumented with a synchronous value by the time of the second use.
2025-06-11 12:07:10 -04:00
Jan Kassens
6c86e56a0f Remove feature flag enableRenderableContext (#33505)
The flag is fully rolled out.
2025-06-11 11:53:04 -04:00
Sebastian Markbåge
56408a5b12 [Flight] Emit timestamps only in forwards advancing time in debug info (#33482)
Previously you weren't guaranteed to have only advancing time entries,
you could jump back in time, but now it omits unnecessary duplicates and
clamps automatically if you emit a previous time entry to enforce
forwards order only.

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

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

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

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

This can effectively create two parallel sequences:

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

This can now be flattened to either:

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

Or:

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

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

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

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

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
2025-06-10 11:03:20 -04:00
Sebastian Markbåge
c38e268978 [Fiber] Fix hydration of useId in SuspenseList (#33491)
Includes #31412.

The issue is that `pushTreeFork` stores some global state when reconcile
children. This gets popped by `popTreeContext` in `completeWork`.
Normally `completeWork` returns its own `Fiber` again if it wants to do
a second pass which will call `pushTreeFork` again in the next pass.
However, `SuspenseList` doesn't return itself, it returns the next child
to work on.

The fix is to keep track of the count and push it again it when we
return the next child to attempt.

There are still some outstanding issues with hydration. Like the
backwards test still has the wrong behavior in it because it hydrates
backwards and so it picks up the DOM nodes in reverse order.
`tail="hidden"` also doesn't work correctly.

There's also another issue with `useId` and `AsyncIterable` in
SuspenseList when there's an unknown number of children. We don't
support those showing one at a time yet though so it's not an issue yet.
To fix it we need to add variable total count to the `useId` algorithm.
E.g. by falling back to varint encoding.

---------

Co-authored-by: Rick Hanlon <rickhanlonii@fb.com>
Co-authored-by: Ricky <rickhanlonii@gmail.com>
2025-06-09 19:37:49 -04:00
Ruslan Lesiutin
80c03eb7e0 refactor[devtools]: update css for settings and support css variables in shadow dom scnenario (#33487)
## Summary

Minor changes around css and styling of Settings dialog.

1. `:root` selector was updated to `:is(:root, :host)` to make css
variables available on Shadow Root
2. CSS tweaks around Settings dialog: removed references to deleted
styles, removed unused styles, ironed out styling for cases when input
styles are enhanced by user agent stylesheet

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

## How did you test this change?

| Before | After |
|--------|--------|
| ![Screenshot 2025-06-09 at 15 35
55](https://github.com/user-attachments/assets/1ac5d002-744b-4b10-9501-d4f2a7c827d2)
| ![Screenshot 2025-06-09 at 15 26
12](https://github.com/user-attachments/assets/8cc07cda-99a5-4930-973b-b139b193e349)
|
| ![Screenshot 2025-06-09 at 15 36
02](https://github.com/user-attachments/assets/1af4257c-928d-4ec6-a614-801cc1936f4b)
| ![Screenshot 2025-06-09 at 15 26
25](https://github.com/user-attachments/assets/7a3a0f7c-5f3d-4567-a782-dd37368a15ae)
|
| ![Screenshot 2025-06-09 at 15 36
05](https://github.com/user-attachments/assets/a1e00381-2901-4e22-b1c6-4a3f66ba78c9)
| ![Screenshot 2025-06-09 at 15 26
30](https://github.com/user-attachments/assets/bdefce68-cbb5-4b88-b44c-a74f28533f7d)
|
| ![Screenshot 2025-06-09 at 15 36
12](https://github.com/user-attachments/assets/4eda6234-0ef0-40ca-ad9d-5990a2b1e8b4)
| ![Screenshot 2025-06-09 at 15 26
37](https://github.com/user-attachments/assets/5cac305e-fd29-460c-b0b8-30e477b8c26e)
|
2025-06-09 18:25:19 +01:00
Wesley LeMahieu
b6c0aa8814 [compiler]: fix link compiler & 4 broken tests from path containing spaces (#33409)
## Summary

Problem #1: Running the `link-compiler.sh` bash script via `"prebuild"`
script fails if a developer has cloned the `react` repo into a folder
that contains _any_ spaces. 3 tests fail because of this.

<img width="1003" alt="fail-1"
src="https://github.com/user-attachments/assets/1fbfa9ce-4f84-48d7-b49c-b6e967b8c7ca"
/>
<img width="1011" alt="fail-2"
src="https://github.com/user-attachments/assets/0a8c6371-a2df-4276-af98-38f4784cf0da"
/>
<img width="1027" alt="fail-3"
src="https://github.com/user-attachments/assets/1c4f4429-800c-4b44-b3da-a59ac85a16b9"
/>

For example, my current folder is:
`/Users/wes/Development/Open Source Contributions/react`

The link compiler error returns:
`./scripts/react-compiler/link-compiler.sh: line 15: cd:
/Users/wes/Development/Open: No such file or directory`

Problem #2: 1 test in `ReactChildren-test.js` fails due the existing
stack trace regex which should be lightly revised.

`([^(\[\n]+)[^\n]*/g` is more robust for stack traces: it captures the
function/class name (with dots) and does not break on spaces in file
paths.
`([\S]+)[^\n]*/g` is simpler but breaks if there are spaces and doesn't
handle dotted names well.

Additionally, we trim the whitespace off the name to resolve extra
spaces breaking this test as well:

```
-     in div (at **)
+     in div  (at **)
```

<img width="987" alt="fail-4"
src="https://github.com/user-attachments/assets/56a673bc-513f-4458-95b2-224129c77144"
/>

All of the above tests pass if I hyphenate my local folder:
`/Users/wes/Development/Open-Source-Contributions/react`

I selfishly want to keep spaces in my folder names. 🫣

## How did you test this change?

**npx yarn prebuild**

Before:
<img width="896" alt="Screenshot at Jun 01 11-42-56"
src="https://github.com/user-attachments/assets/4692775c-1e5c-4851-9bd7-e12ed5455e47"
/>

After:
<img width="420" alt="Screenshot at Jun 01 11-43-42"
src="https://github.com/user-attachments/assets/4e303c00-02b7-4540-ba19-927b2d7034fb"
/>

**npx yarn test**
**npx yarn test
./packages/react/src/\_\_tests\_\_/ReactChildren-test.js**
**npx yarn test -r=xplat --env=development --variant=true --ci
--shard=3/5**

Before:
<img width="438" alt="before"
src="https://github.com/user-attachments/assets/f5eedb22-18c3-4124-a04b-daa95c0f7652"
/>

After:
<img width="439" alt="after"
src="https://github.com/user-attachments/assets/a94218ba-7c6a-4f08-85d3-57540e9d0029"
/>

<img width="650" alt="Screenshot at Jun 02 18-03-39"
src="https://github.com/user-attachments/assets/3eae993c-a56b-46c8-ae02-d249cb053fe7"
/>

<img width="685" alt="Screenshot at Jun 03 12-53-47"
src="https://github.com/user-attachments/assets/5b2caa33-d3dc-4804-981d-52cb10b6226f"
/>
2025-06-09 08:40:27 -07:00
Sebastian Markbåge
428ab82001 [Flight] Simulate fetch to third party in fixture (#33484)
This adds some I/O to go get the third party thing to test how it
overlaps.

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

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

This doesn't fully simulate everything because it should actually also
simulate each chunk of the stream coming back too. We could wrap the
ReadableStream to simulate that. In that scenario, it would probably get
some awaits on the chunks at the end too.
2025-06-09 10:04:40 -04:00
Jordan Brown
4df098c4c2 [compiler] Don't include useEffectEvent values in autodeps (#33450)
Summary: useEffectEvent values are not meant to be added to the dep
array
2025-06-09 09:26:45 -04:00
Hendrik Liebau
95bcf87e6b Format ReactNativeAttributePayloadFabric.js with Prettier (#33486)
The prettier check for this file is currently failing on `main`, after
#32119 was merged.
2025-06-09 12:42:10 +01:00
Hanno J. Gödecke
911dbd9e34 feat(ReactNative): prioritize attribute config process function to allow processing function props (#32119)
## Summary

In react-native props that are passed as function get converted to a
boolean (`true`). This is the default pattern for event handlers in
react-native.
However, there are reasons for why you might want to opt-out of this
behavior, and instead, pass along the actual function as the prop.
Right now, there is no way to do this, and props that are functions
always get set to `true`.
The `ViewConfig` attributes already have the API for a `process`
function. I simply moved the check for the process function up, so if a
ViewConfig's prop attribute configured a process function this is always
called first.
This provides an API to opt out of the default behavior. 

This is the accompanied PR for react-native:

- https://github.com/facebook/react-native/pull/48777

## 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.
-->

I modified the code manually in a template react-native app and
confirmed its working. This is a code path you only need in very special
cases, thus it's a bit hard to provide a test for this. I recorded a
video where you can see that the changes are active and the prop is
being passed as native value.

For this I created a custom native component with a view config that
looked like this:

```js
const viewConfig = {
  uiViewClassName: 'CustomView',
  bubblingEventTypes: {},
  directEventTypes: {},
  validAttributes: {
    nativeProp: {
      process: (nativeProp) => {
		// Identity function that simply returns the prop function callback
        // to opt out of this prop being set to `true` as its a function
        return nativeProp
      },
    },
  },
}
```



https://github.com/user-attachments/assets/493534b2-a508-4142-a760-0b1b24419e19

Additionally I made sure that this doesn't conflict with any existing
view configs in react native. In general, this shouldn't be a breaking
change, as for existing view configs it didn't made a difference if you
simply set `myProp: true` or `myProp: { process: () => {...} }` because
as soon as it was detected that the prop is a function the config
wouldn't be used (which is what this PR fixes).
Probably everyone, including the react-native core components use
`myProp: true` for callback props, so this change should be fine.
2025-06-09 10:55:28 +01:00
Hendrik Liebau
c0b5a0cad3 [Flight] Use Web Streams APIs for 3rd-party component in Flight fixture (#33481) 2025-06-08 06:33:25 +02:00
Hendrik Liebau
e4b88ae4c6 [Flight] Add Web Streams APIs to unbundled Node entries for Webpack (#33480) 2025-06-07 23:39:25 +02:00
Sebastian Markbåge
6c8bcdaf1b [Flight] Clarify Semantics for Awaiting Cached Data (#33438)
Technically the async call graph spans basically all the way back to the
start of the app potentially, but we don't want to include everything.
Similarly we don't want to include everything from previous components
in every child component. So we need some heuristics for filtering out
data.

We roughly want to be able to inspect is what might contribute to a
Suspense loading sequence even if it didn't this time e.g. due to a race
condition.

One flaw with the previous approach was that awaiting a cached promise
in a sibling that happened to finish after another sibling would be
excluded. However, in a different race condition that might end up being
used so I wanted to include an empty "await" in that scenario to have
some association from that component.

However, for data that resolved fully before the request even started,
it's a little different. This can be things that are part of the start
up sequence of the app or externally cached data. We decided that this
should be excluded because it doesn't contribute to the loading sequence
in the expected scenario. I.e. if it's cached. Things that end up being
cache misses would still be included. If you want to test externally
cached data misses, then it's up to you or the framework to simulate
those. E.g. by dropping the cache. This also helps free up some noise
since static / cached data can be excluded in visualizations.

I also apply this principle to forwarding debug info. If you reuse a
cached RSC payload, then the Server Component render time and its awaits
gets clamped to the caller as if it has zero render/await time. The I/O
entry is still back dated but if it was fully resolved before we started
then it's completely excluded.
2025-06-07 17:26:36 -04:00
Sebastian Markbåge
b367b60927 [Flight] Add "use ..." boundary after the change instead of before it (#33478)
I noticed that the ThirdPartyComponent in the fixture was showing the
wrong stack and the `"use third-party"` is in the wrong location.

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

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

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

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

After:

<img width="494" alt="Screenshot 2025-06-07 at 1 13 28 AM"
src="https://github.com/user-attachments/assets/e2e870ba-f125-4526-a853-bd29f164cf09"
/>
2025-06-07 11:28:57 -04:00
Sebastian Markbåge
9666605abf [Flight] Add Web Stream support to the Flight Server in Node (#33474)
This needs some tweaks to the implementation and a conversion but simple
enough.

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
2025-06-07 10:40:09 -04:00
Sebastian Markbåge
65ec57df37 [Fizz] Add Web Streams to Fizz Node entry point (#33475)
New take on #33441.

This uses a wrapper instead of a separate bundle.
2025-06-06 20:16:43 -04:00
Sebastian "Sebbie" Silbermann
b3d5e90786 [Fizz] Include unit of threshold in rel=expect deopt error (#33476) 2025-06-07 02:11:33 +02:00
Sebastian Markbåge
280ff6fed2 [Flight] Add Web Stream support to the Flight Client in Node (#33473)
This effectively lets us consume Web Streams in a Node build. In fact
the Node entry point is now just adding Node stream APIs.

For the client, this is simple because the configs are not actually
stream type specific. The server is a little trickier.
2025-06-06 17:14:15 -04:00
Sebastian Markbåge
82f3684c63 Revert Node Web Streams (#33472)
Reverts #33457, #33456 and #33442.

There are too many issues with wrappers, lazy init, stateful modules,
duplicate instantiation of async_hooks and duplication of code.

Instead, we'll just do a wrapper polyfill that uses Node Streams
internally.

I kept the client indirection files that I added for consistency with
the server though.
2025-06-06 16:26:36 -04:00
Josh Story
142aa0744d [Fizz] Support deeply nested Suspense inside fallback (#33467)
When deeply nested Suspense boundaries inside a fallback of another
boundary resolve it is possible to encounter situations where you either
attempt to flush an aborted Segment or you have a boundary without any
root segment. We intended for both of these conditions to be impossible
to arrive at legitimately however it turns out in this situation you
can. The fix is two-fold

1. allow flushing aborted segments by simply skipping them. This does
remove some protection against future misconfiguraiton of React because
it is no longer an invariant that you hsould never attempt to flush an
aborted segment but there are legitimate cases where this can come up
and simply omitting the segment is fine b/c we know that the user will
never observe this. A semantically better solution would be to avoid
flushing boudaries inside an unneeded fallback but to do this we would
need to track all boundaries inside a fallback or create back pointers
which add to memory overhead and possibly make GC harder to do
efficiently. By flushing extra we're maintaining status quo and only
suffer in performance not with broken semantics.

2. when queuing completed segments allow for queueing aborted segments
and if we are eliding the enqueued segment allow for child segments that
are errored to be enqueued too. This will mean that we can maintain the
invariant that a boundary must have a root segment the first time we
flush it, it just might be aborted (see point 1 above).

This change has two seemingly similar test cases to exercise this fix.
The reason we need both is that when you have empty segments you hit
different code paths within Fizz and so each one (without this fix)
triggers a different error pathway.

This change also includes a fix to our tests where we were not
appropriately setting CSPnonce back to null at the start of each test so
in some contexts scripts would not run for some tests
2025-06-06 11:59:15 -07:00
Sebastian Markbåge
6ccf328499 [Fizz] Shorten throttle to hit a specific target metric (#33463)
Adding throttling or delaying on images, can obviously impact metrics.
However, it's all in the name of better actual user experience overall.
(Note that it's not strictly worse even for metric. Often it's actually
strictly better due to less work being done overall thanks to batching.)

Metrics can impact things like search ranking but I believe this is on a
curve. If you're already pretty good, then a slight delay won't suddenly
make you rank in a completely different category. Similarly, if you're
already pretty bad then a slight delay won't make it suddenly way worse.
It's still in the same realm. It's just one weight of many. I don't
think this will make a meaningful practical impact and if it does,
that's probably a bug in the weights that will get fixed.

However, because there's a race to try to "make everything green" in
terms of web vitals, if you go from green to yellow only because of some
throttling or suspensey images, it can feel bad. Therefore this
implements a heuristic where if the only reason we'd miss a specific
target is because of throttling or suspensey images, then we shorten the
timeout to hit the metric. This is a worse user experience because it
can lead to extra flashing but feeling good about "green" matters too.

If you then have another reveal that happens to be the largest
contentful paint after that, then that's throttled again so that it
doesn't become flashy after that. If you've already missed the deadline
then you're not going to hit your metric target anyway. It can affect
average but not median.

This is mainly about LCP. It doesn't affect FCP since that doesn't have
a throttle. If your LCP is the same as your FCP then it also doesn't
matter.

We assume that `performance.now()`'s zero point starts at the "start of
the navigation" which makes this simple. Even if we used the
`PerformanceNavigationTiming` API it would just tell us the same thing.

This only implements for Fizz since these metrics tend to currently only
by tracked for initial loads, but with soft navs tracking we could
consider implementing the same for Fiber throttles.
2025-06-06 14:01:15 -04:00
lauren
a374e0ec87 [ci] Fix missing permissions for stale job (#33466)
Missed these the last time.
2025-06-06 13:32:51 -04:00
Sebastian Markbåge
ab859e31be [Flight] Build Node.js Web Streams builds for Turbopack and Parcel (#33457)
Same as #33456 and #33442 but for Turbopack and Parcel.
2025-06-06 11:07:40 -04:00
Sebastian Markbåge
e8d15fa19e [Flight] Build node-webstreams version of bundled webpack server (#33456)
Follow up to #33442. This is the bundled version.

To keep type check passes from exploding and the maintainance of the
annoying `paths: []` list small, this doesn't add this to flow type
checks. We might miss some config but every combination should already
be covered by other one passes.

I also don't add any jest tests because to test these double export
entry points we need conditional importing to cover builds and
non-builds which turns out to be difficult for the Flight builds so
these aren't covered by any basic build tests.

This approach is what I'm going for, for the other bundlers too.
2025-06-06 11:07:15 -04:00
Sebastian Markbåge
d177272802 [Fizz] Error and deopt from rel=expect for large documents without boundaries (#33454)
We want to make sure that we can block the reveal of a well designed
complete shell reliably. In the Suspense model, client transitions don't
have any way to implicitly resolve. This means you need to use Suspense
or SuspenseList to explicitly split the document. Relying on implicit
would mean you can't add a Suspense boundary later where needed. So we
highly encourage the use of them around large content.

However, if you have constructed a too large shell (e.g. by not adding
any Suspense boundaries at all) then that might take too long to render
on the client. We shouldn't punish users (or overzealous metrics
tracking tools like search engines) in that scenario.

This opts out of render blocking if the shell ends up too large to be
intentional and too slow to load. Instead it deopts to showing the
content split up in arbitrary ways (browser default). It only does this
for SSR, and not client navs so it's not reliable.

In fact, we issue an error to `onError`. This error is recoverable in
that the document is still produced. It's up to your framework to decide
if this errors the build or just surface it for action later.

What should be the limit though? There's a trade off here. If this limit
is too low then you can't fit a reasonably well built UI within it
without getting errors. If it's too high then things that accidentally
fall below it might take too long to load.

I came up with 512kB of uncompressed shell HTML. See the comment in code
for the rationale for this number. TL;DR: Data and theory indicates that
having this much content inside `rel="expect"` doesn't meaningfully
change metrics. Research of above-the-fold content on various websites
indicate that this can comfortable fit all of them which should be
enough for any intentional initial paint.
2025-06-06 10:29:48 -04:00
Sebastian Markbåge
22b929156c [Fizz] Suspensey Images for View Transition Reveals (#33433)
Block the view transition on suspensey images Up to 500ms just like the
client.

We can't use `decode()` because a bug in Chrome where those are blocked
on `startViewTransition` finishing we instead rely on sync decoding but
also that the image is live when it's animating in and we assume it
doesn't start visible.

However, we can block the View Transition from starting on the `"load"`
or `"error"` events.

The nice thing about blocking inside `startViewTransition` is that we
have already done the layout so we can only wait on images that are
within the viewport at this point. We might want to do that in Fiber
too. If many image doesn't have fixed size but need to load first, they
can all end up in the viewport. We might consider only doing this for
images that have a fixed size or only a max number that doesn't have a
fixed size.
2025-06-06 10:14:13 -04:00
Ricky
a3be6829c6 [tests] remove pretest compiler script (#33452)
This shouldn't be needed now that the lint rule was move
2025-06-06 09:16:58 -04:00
Hendrik Liebau
b1759882c0 [Flight] Bypass caches in Flight fixture if requested (#33445) 2025-06-06 06:42:58 +02:00
Timothy Yung
dddcae7a11 Enable the enableEagerAlternateStateNodeCleanup Feature Flag (#33447)
## Summary

Enables the `enableEagerAlternateStateNodeCleanup` feature flag for all
variants, while maintaining the `__VARIANT__` for the internal React
Native flavor for backtesting reasons.

## How did you test this change?

```
$ yarn test
```
2025-06-05 14:22:35 -07:00
Hendrik Liebau
43714eb4e9 Do not notify Discord for draft pull requests (#33446)
When I added the `ready_for_review` event in #32344, no notifications
for opened draft PRs were sent due to some other condition. This is not
the case anymore, so we need to exclude draft PRs from triggering a
notification when the workflow is run because of an `opened` event. This
event is still needed because the `ready_for_review` event only fires
when an existing draft PR is converted to a non-draft state. It does not
trigger for pull requests that are opened directly as ready-for-review.
2025-06-05 15:08:57 -04:00
Sebastian Markbåge
a5110b22f0 [Flight] Add a Node.js Web Streams bundle for unbundled client/server for Webpack (#33442)
Like #33441 but for Flight.

This is just one of the many combinations needed. I'm just starting with
one.
2025-06-05 14:29:02 -04:00
Hendrik Liebau
b4477d3800 [Flight] Add a cached 3rd-party component to the Flight fixture (#33443)
This should allow us to visualize what
https://github.com/facebook/react/pull/33438 is trying to convey.

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

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

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

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

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

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

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

The cached 3rd-party component starts before the current render, and is
also not excluded here, which is of course expected without #33438.
2025-06-05 17:19:54 +02:00
Sebastian Markbåge
93f1668045 [Fizz] Add Node Web Streams bundle for SSR (#33441)
We highly recommend using Node Streams in Node.js because it's much
faster and it is less likely to cause issues when chained in things like
compression algorithms that need explicit flushing which the Web Streams
ecosystem doesn't have a good solution for. However, that said, people
want to be able to use the worse option for various reasons.

The `.edge` builds aren't technically intended for Node.js. A Node.js
environments needs to be patched in various ways to support it. It's
also less optimal since it can't use [Node.js exclusive
features](https://github.com/facebook/react/pull/33388) and have to use
[the lowest common
denominator](https://github.com/facebook/react/pull/27399) such as JS
implementations instead of native.

This adds a Web Streams build of Fizz but exclusively for Node.js so
that in it we can rely on Node.js modules. The main difference compared
to Edge is that SSR now uses `createHash` from the `"crypto"` module and
imports `TextEncoder` from `"util"`. We use `setImmediate` instead of
`setTimeout`.

The public API is just `react-dom/server` which in Node.js automatically
imports `react-dom/server.node` which re-exports the legacy bundle, Node
Streams bundle and Node Web Streams bundle. The main downside is if your
bundler isn't smart to DCE this barrel file.

With Flight the difference is larger but that's a bigger lift.
2025-06-05 10:50:41 -04:00
Sebastian Markbåge
37054867c1 [Flight] Forward debugInfo from awaited instrumented Promises (#33415)
Stacked on #33403.

When a Promise is coming from React such as when it's passed from
another environment, we should forward the debug information from that
environment. We already do that when rendered as a child.

This makes it possible to also `await promise` and have the information
from that instrumented promise carry through to the next render.

This is a bit tricky because the current protocol is that we have to
read it from the Promise after it resolves so it has time to be assigned
to the promise. `async_hooks` doesn't pass us the instance (even though
it has it) when it gets resolved so we need to keep it around. However,
we have to be very careful because if we get this wrong it'll cause a
memory leak since we retain things by `asyncId` and then manually listen
for `destroy()` which can only be called once a Promise is GC:ed, which
it can't be if we retain it. We have to therefore use a `WeakRef` in
case it never resolves, and then read the `_debugInfo` when it resolves.
We could maybe install a setter or something instead but that's also
heavy.

The other issues is that we don't use native Promises in
ReactFlightClient so our instrumented promises aren't picked up by the
`async_hooks` implementation and so we never get a handle to our
thenable instance. To solve this we can create a native wrapper only in
DEV.
2025-06-04 00:49:03 -04:00
Sebastian Markbåge
d742611ce4 Replace Implicit Options on SuspenseList with Explicit Options (#33424)
We want to change the defaults for `revealOrder` and `tail` on
SuspenseList. This is an intermediate step to allow experimental users
to upgrade.

To explicitly specify these options I added `revealOrder="independent"`
and `tail="visible"`.

I then added warnings if `undefined` or `null` is passed. You must now
always explicitly specify them. However, semantics are still preserved
for now until the next step.

We also want to change the rendering order of the `children` prop for
`revealOrder="backwards"`. As an intermediate step I first added
`revealOrder="unstable_legacy-backwards"` option. This will only be
temporary until all users can switch to the new `"backwards"` semantics
once we flip it in the next step.

I also clarified the types that the directional props requires iterable
children but not iterable inside of those. Rows with multiple items can
be modeled as explicit fragments.
2025-06-03 17:40:30 -04:00
Sebastian Markbåge
1540081725 [Flight] Encode Async I/O Tasks using the Enclosing Line/Column (#33403)
Stacked on #33402.

There's a bug in Chrome Performance tracking which uses the enclosing
line/column instead of the callsite in stacks.

For our fake eval:ed functions that represents functions on the server,
we can position the enclosing function body at the position of the
callsite to simulate getting the right line.

Unfortunately, that doesn't give us exactly the right callsite when it's
used for other purposes that uses the callsite like console logs and
error reporting and stacks inside breakpoints. So I don't think we want
to always do this.

For ReactAsyncInfo/ReactIOInfo, the only thing we're going to use the
fake task for is the Performance tracking, so it doesn't have any
downsides until Chrome fixes the bug and we'd have to revert it.
Therefore this PR uses that techniques only for those entries.

We could do this for Server Components too but we're going to use those
for other things too like console logs. I don't think it's worth
duplicating the Task objects. That would also make it inconsistent with
Client Components.

For Client Components, we could in theory also generate fake evals but
that would be way slower since there's so many of them and currently we
rely on the native implementation for those. So doesn't seem worth
fixing.

But since we can at least fix it for RSC I/O/awaits we can do this hack.
2025-06-03 17:30:31 -04:00
Sebastian Markbåge
9cc74fec74 [Flight] Emit the time we awaited something inside a Server Component (#33402)
Stacked on #33400. 

<img width="1261" alt="Screenshot 2025-06-01 at 10 27 47 PM"
src="https://github.com/user-attachments/assets/a5a73ee2-49e0-4851-84ac-e0df6032efb5"
/>

This is emitted with the start/end time and stack of the "await". Which
may be different than the thing that started the I/O.

These awaits aren't quite as simple as just every await since you can
start a sequence in parallel there can actually be multiple overlapping
awaits and there can be CPU work interleaved with the await on the same
component.

```js
function getData() {
  await fetch(...);
  await fetch(...);
}
const promise = getData();
doWork();
await promise;
```

This has two "I/O" awaits but those are actually happening in parallel
with `doWork()`.

Since these also could have started before we started rendering this
sequence (e.g. a component) we have to clamp it so that we don't
consider awaits that start before the component.

What we're conceptually trying to convey is the time this component was
blocked due to that I/O resource. Whether it's blocked from completing
the last result or if it's blocked from issuing a waterfall request.
2025-06-03 17:29:41 -04:00
Sebastian Markbåge
157ac578de [Flight] Include env in ReactAsyncInfo and ReactIOInfo (#33400)
Stacked on #33395.

This lets us keep track of which environment this was fetched and
awaited.

Currently the IO and await is in the same environment. It's just kept
when forwarded. Once we support forwarding information from a Promise
fetched from another environment and awaited in this environment then
the await can end up being in a different environment.

There's a question of when the await is inside Flight itself such as
when you return a promise fetched from another environment whether that
should mean that the await is in the current environment. I don't think
so since the original stack trace is the best stack trace. It's only if
you `await` it in user space in this environment first that this might
happen and even then it should only be considered if there wasn't a
better await earlier or if reading from the other environment was itself
I/O.

The timing of *when* we read `environmentName()` is a little interesting
here too.
2025-06-03 17:28:46 -04:00
Sebastian Markbåge
45da4e055d [Flight] Track Owner on AsyncInfo and IOInfo (#33395)
Stacked on #33394.

This lets us create async stack traces to the owner that was in context
when the I/O was started or awaited.

<img width="615" alt="Screenshot 2025-06-01 at 12 31 52 AM"
src="https://github.com/user-attachments/assets/6ff5a146-33d6-4a4b-84af-1b57e73047d4"
/>

This owner might not be the immediate closest parent where the I/O was
awaited.
2025-06-03 16:12:26 -04:00
Sebastian Markbåge
d8919a0a68 [Flight] Log "Server Requests" Track (#33394)
Stacked on #33392.

This adds another track to the Performance Track called `"Server
Requests"`.

<img width="1015" alt="Screenshot 2025-06-01 at 12 02 14 AM"
src="https://github.com/user-attachments/assets/c4d164c4-cfdf-4e14-9a87-3f011f65fd20"
/>

This logs the flat list of I/O awaited on by Server Components. There
will be other views that are more focused on what data blocks a specific
Component or Suspense boundary but this is just the list of all the I/O
basically so you can get an overview of those waterfalls without the
noise of all the Component trees and rendering. It's similar to what the
"Network" track is on the client.

I've been going back and forth on what to call this track but I went
with `"Server Requests"` for now. The idea is that the name should
communicate that this is something that happens on the server and is a
pairing with the `"Server Components"` track. Although we don't use that
feature, since it's missing granularity, it's also similar to "Server
Timings".
2025-06-03 15:31:12 -04:00
Sebastian "Sebbie" Silbermann
2e9f8cd3e0 Clear bundler cache before bundling fixtures (#33426) 2025-06-03 21:10:13 +02:00
Sebastian Markbåge
65a46c7eeb [Flight] Track the function name that was called for I/O entries (#33392)
Stacked on #33390.

The stack trace doesn't include the thing you called when calling into
ignore listed content. We consider the ignore listed content
conceptually the abstraction that you called that's interesting.

This extracts the name of the first ignore listed function that was
called from user space. For example `"fetch"`. So we can know what kind
of request this is.

This could be enhanced and tweaked with heuristics in the future. For
example, when you create a Promise yourself and call I/O inside of it
like my `delay` examples, then we use that Promise as the I/O node but
its stack doesn't have the actual I/O performed. It might be better to
use the inner I/O node in that case. E.g. `setTimeout`. Currently I pick
the name from the first party code instead - in my example `delay`.

Another case that could be improved is the case where your whole
component is third-party. In that case we still log the I/O but it has
no context about what kind of I/O since the whole stack is ignored it
just gets the component name for example. We could for example look at
the first name that is in a different package than the package name of
the ignored listed component. So if
`node_modules/my-component-library/index.js` calls into
`node_modules/mysql/connection.js` then we could use the name from the
inner.
2025-06-03 15:04:28 -04:00
Sebastian Markbåge
3fb17d16a4 [Flight] Encode ReactIOInfo as its own row type (#33390)
Stacked on #33388.

This encodes the I/O entries as their own row type (`"J"`). This makes
it possible to parse them directly without first parsing the debug info
for each component. E.g. if you're just interested in logging the I/O
without all the places it was awaited.

This is not strictly necessary since the debug info is also readily
available without parsing the actual trees. (That's how the Server
Components Performance Track works.) However, we might want to exclude
this information in profiling builds while retaining some limited form
of I/O tracking.

It also allows for logging side-effects that are not awaited if we
wanted to.
2025-06-03 14:16:34 -04:00
Sebastian Markbåge
acee65d6d0 [Flight] Track Awaits on I/O as Debug Info (#33388)
This lets us track what data each Server Component depended on. This
will be used by Performance Track and React DevTools.

We use Node.js `async_hooks`. This has a number of downside. It is
Node.js specific so this feature is not available in other runtimes
until something equivalent becomes available. It's [discouraged by
Node.js docs](https://nodejs.org/api/async_hooks.html#async-hooks). It's
also slow which makes this approach only really viable in development
mode. At least with stack traces. However, it's really the only solution
that gives us the data that we need.

The [Diagnostic
Channel](https://nodejs.org/api/diagnostics_channel.html) API is not
sufficient. Not only is many Node.js built-in APIs missing but all
libraries like databases are also missing. Were as `async_hooks` covers
pretty much anything async in the Node.js ecosystem.

However, even if coverage was wider it's not actually showing the
information we want. It's not enough to show the low level I/O that is
happening because that doesn't provide the context. We need the stack
trace in user space code where it was initiated and where it was
awaited. It's also not each low level socket operation that we want to
surface but some higher level concept which can span a sequence of I/O
operations but as far as user space is concerned.

Therefore this solution is anchored on stack traces and ignore listing
to determine what the interesting span is. It is somewhat
Promise-centric (and in particular async/await) because it allows us to
model an abstract span instead of just random I/O. Async/await points
are also especially useful because this allows Async Stacks to show the
full sequence which is not supported by random callbacks. However, if no
Promises are involved we still to our best to show the stack causing
plain I/O callbacks.

Additionally, we don't want to track all possible I/O. For example,
side-effects like logging that doesn't affect the rendering performance
doesn't need to be included. We only want to include things that
actually block the rendering output. We also need to track which data
blocks each component so that we can track which data caused a
particular subtree to suspend.

We can do this using `async_hooks` because we can track the graph of
what resolved what and then spawned what.

To track what suspended what, something has to resolve. Therefore it
needs to run to completion before we can show what it was suspended on.
So something that never resolves, won't be tracked for example.

We use the `async_hooks` in `ReactFlightServerConfigDebugNode` to build
up an `ReactFlightAsyncSequence` graph that collects the stack traces
for basically all I/O and Promises allocated in the whole app. This is
pretty heavy, especially the stack traces, but it's because we don't
know which ones we'll need until they resolve. We don't materialize the
stacks until we need them though.

Once they end up pinging the Flight runtime, we collect which current
executing task that pinged the runtime and then log the sequence that
led up until that runtime into the RSC protocol. Currently we only
include things that weren't already resolved before we started rendering
this task/component, so that we don't log the entire history each time.

Each operation is split into two parts. First a `ReactIOInfo` which
represents an I/O operation and its start/end time. Basically the start
point where it was start. This is basically represents where you called
`new Promise()` or when entering an `async function` which has an
implied Promise. It can be started in a different component than where
it's awaited and it can be awaited in multiple places. Therefore this is
global information and not associated with a specific Component.

The second part is `ReactAsyncInfo`. This represents where this I/O was
`await`:ed or `.then()` called. This is associated with a point in the
tree (usually the Promise that's a direct child of a Component). Since
you can have multiple different I/O awaited in a sequence technically it
forms a dependency graph but to simplify the model these awaits as
flattened into the `ReactDebugInfo` list. Basically it contains each
await in a sequence that affected this part from unblocking.

This means that the same `ReactAsyncInfo` can appear in mutliple
components if they all await the same `ReactIOInfo` but the same Promise
only appears once.

Promises that are only resolved by other Promises or immediately are not
considered here. Only if they're resolved by an I/O operation. We pick
the Promise basically on the border between user space code and ignored
listed code (`node_modules`) to pick the most specific span but abstract
enough to not give too much detail irrelevant to the current audience.
Similarly, the deepest `await` in user space is marked as the relevant
`await` point.

This feature is only available in the `node` builds of React. Not if you
use the `edge` builds inside of Node.js.

---------

Co-authored-by: Sebastian "Sebbie" Silbermann <silbermann.sebastian@gmail.com>
2025-06-03 14:14:40 -04:00
Sebastian Markbåge
1ae0a845bd Use underscore instead of « » for useId algorithm (#33422)
Alternative to #33421. The difference is that this also adds an
underscore between the "R" and the ID.

The reason we wanted to use special characters is because we use the
full spectrum of A-Z 0-9 in our ID generation so we can basically
collide with any common word (or anyone using a similar algorithm,
base64 or even base16). It's a little less likely that someone would put
`_R_` specifically unless you generate like two IDs separated by
underscore.


![9w2ogt](https://github.com/user-attachments/assets/21b2d2ac-1a3a-4657-ba0b-1616e49dfdee)
2025-06-03 11:30:17 -04:00
Jorge Cabiedes
2b4064eb9b [mcp] Add MCP tool to print out the component tree of the currently open React App (#33305)
## Summary

This tool leverages DevTools to get the component tree from the
currently open React App. This gives realtime information to agents
about the state of the app.

## How did you test this change?

Tested integration with Claude Desktop
2025-06-02 21:42:34 -07:00
Ricky
3531b26729 [scripts] Switch back to flow parser for prettier (#33414)
Prettier 3.3 (which we're on) should support modern flow features
according to https://prettier.io/blog/2024/06/01/3.3.0
2025-06-03 00:00:28 -04:00
Sebastian "Sebbie" Silbermann
4a1f29079c [Fizz] Add Owner Stacks when render is aborted (#32735) 2025-06-02 19:27:49 +02:00
mofeiZ
526dd340b3 [compiler][patch] Emit unary expressions instead of negative numbers (#33383)
This is a babel bug + edge case.

Babel compact mode produces invalid JavaScript (i.e. parse error) when
given a `NumericLiteral` with a negative value.

See https://codesandbox.io/p/devbox/5d47fr for repro.
2025-06-02 11:43:45 -04:00
Wesley LeMahieu
ee76351917 fix typo in compiler validation filename (#33345)
## Summary

While investigating the root cause of #33208, I noticed a clear typo for
one of the validation files.

## How did you test this change?

Inside `/react/compiler/packages/babel-plugin-react-compiler` I ran the
test script successfully:

<img width="415" alt="Screenshot at May 22 16-43-06"
src="https://github.com/user-attachments/assets/3fe8c5e1-37ce-4a31-b35e-7e323e57cd9d"
/>
2025-05-30 16:31:16 -07:00
Pieter De Baets
8b55eb4e72 Cleanup props diffing experiments (#33381)
## Summary

We completed testing on these internally, so can cleanup the separate
fast and slow paths and remove the `enableShallowPropDiffing` flag which
we're not pursuing.

## How did you test this change?

```
yarn test ReactNativeAttributePayloadFabric
```
2025-05-30 17:17:59 +01:00
Mateusz Burzyński
14094f80cb Allow nonce to be used on hoistable styles (#32461)
fixes https://github.com/facebook/react/issues/32449

This is my first time touching this code. There are multiple systems in
place here and I wouldn't be surprised to learn that this has to be
handled in some other areas too. I have found some other style-related
code areas but I had no time yet to double-check them.

cc @gnoff
2025-05-29 08:17:10 -07:00
Sebastian "Sebbie" Silbermann
5717f1933f [react-dom] Enforce small gap between completed navigation and default Transition indicator (#33354) 2025-05-28 19:46:12 +02:00
Ricky
b07717d857 [devtools] upgrade json5 (#33358) 2025-05-28 10:31:09 -04:00
Jan Kassens
283f87f083 Revert "enableViewTransition in www" (#33362)
We need to do some more testing here.

Reverts facebook/react#33357
2025-05-27 17:17:45 -04:00
mofeiZ
f9ae0a4c2e [compiler][gating] Custom opt out directives (experimental option) (#33328)
Adding an experimental / unstable compiler config to enable custom
opt-out directives
2025-05-27 12:02:29 -04:00
Jan Kassens
f702620cea [fb-www] ship enableViewTransition (#33357) 2025-05-27 11:23:27 -04:00
Sebastian Markbåge
c0464aedb1 [Fizz] Block on Suspensey Fonts during reveal (#33342)
This is the same technique we do for the client except we don't check
whether this is newly created font loading to keep code small.

Unfortunately, we can't use this technique for Suspensey images. They'll
need to block before we call `startViewTransition` in a separate
refactor. This is due to a bug in Chrome where `img.decode()` doesn't
resolve until `startViewTransition` does.
2025-05-23 13:26:02 -04:00
Sebastian Markbåge
6a1dfe3777 Disable moveBefore experiment (#33348)
There seems to be some bugs still to work out in Chrome. See #33187.

Additionally, since you can't really rely on this function existing
across browsers, it's hard to depend on its behavior anyway. In fact,
you now have a source of inconsistent behaviors across browsers to deal
with.

Ideally it would also be more widely spread in fake DOM implementations
like JSDOM so that we can use it unconditionally. #33177.

We still want to enable this since it's a great feature but maybe not
until it's more widely available cross-browsers with fewer bugs.
2025-05-23 13:25:13 -04:00
Jordan Brown
99efc627a5 [eslint] Add an option to require dependencies on effect hooks (#33344)
Summary:

To prepare for automatic effect dependencies, some codebases may want to
codemod
existing useEffect calls with no deps to include an explicit undefined
second argument
in order to preserve the "run on every render" behavior. In sufficiently
large codebases,
this may require a temporary enforcement period where all effects
provide an explicit
dependencies argument.

Outside of migration, relying on a component to render can lead to real
bugs,
especially when working with memoization.
2025-05-23 10:09:41 -04:00
0xFango
bfaeb4a461 Fix incorrect use of NoLanes in executionContext check (#33170)
## Summary

This PR fixes a likely incorrect condition in the
`scheduleUpdateOnFiber` function inside `ReactFiberWorkLoop.js`.

Previously, the code checked:

```js
(executionContext & RenderContext) !== NoLanes
````

However, `NoLanes` is part of the lane priority system, not the
execution context flags. The intent here seems to be to detect whether
the current execution context includes `RenderContext`, which should be
compared against `NoContext`, not `NoLanes`.

This fix replaces `NoLanes` with `NoContext` for semantic correctness
and consistency with other checks throughout the codebase.

**Fixes
[[#33169](https://github.com/facebook/react/issues/33169)](https://github.com/facebook/react/issues/33169)**

---

## How did you test this change?

I ran the following commands to validate correctness and ensure nothing
was broken:

* `yarn lint`
* `yarn linc`
* `yarn test`
* `yarn test --prod`
* `yarn flow`
* `yarn prettier`

All checks passed. Since this is a minor internal logic fix and doesn't
change public behavior or APIs, no additional tests are necessary at
this time.
2025-05-22 22:02:39 -04:00
Christoph Nakazawa
3e9db65fc3 Fix typo in error message. (#33313)
## Summary

I am writing code that isn't so good, so I saw this error message many
times. It appears to have a typo. This PR fixes the typo.

## How did you test this change?

Ran the tests
2025-05-22 16:18:23 -04:00
mofeiZ
0d072884f9 [compiler] Inferred effect dependencies now include optional chains (#33326)
Inferred effect dependencies now include optional chains.

This is a temporary solution while
https://github.com/facebook/react/pull/32099 and its followups are
worked on. Ideally, we should model reactive scope dependencies in the
IR similarly to `ComputeIR` -- dependencies should be hoisted and all
references rewritten to use the hoisted dependencies.

`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33326).
* __->__ #33326
* #33325
* #32286
2025-05-22 16:14:49 -04:00
mofeiZ
abf9fd559d [compiler] Add reactive flag on scope dependencies (#33325)
When collecting scope dependencies, mark each dependency with `reactive:
true | false`. This prepares for later PRs
https://github.com/facebook/react/pull/33326 and
https://github.com/facebook/react/pull/32099 which rewrite scope
dependencies into instructions.

Note that some reactive objects may have non-reactive properties, but we
do not currently track this.

Technically, state[0] is reactive and state[1] is not. Currently, both
would be marked as reactive.
```js
const state = useState();
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33325).
* #33326
* __->__ #33325
* #32286
2025-05-22 16:14:05 -04:00
mofeiZ
13f20044f3 [compiler] Prepare HIRBuilder to be used by later passes (#32286)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32286).
* #33326
* #33325
* __->__ #32286
2025-05-22 16:13:50 -04:00
Sebastian Markbåge
8ce15b0f56 [Fizz] Apply View Transition Name and Class to SSR:ed View Transitions (#33332)
Stacked on #33330.

This walks the element tree to activate the various classes under
different scenarios. There are some edge case things that are a little
different since we can't express every scenario without virtual nodes.

The main thing that's still missing though is avoiding animating updates
if it can be contained to a layout or enter/exit/share if they're out of
the viewport. I.e. layout stuff.
2025-05-22 10:40:28 -04:00
Sebastian Markbåge
91ac1fea1a [Fizz] Pass batch as argument to revealCompletedBoundaries (#33330)
Follow up to #33293.

This solves a race condition when boundaries are added to the batch
after the `startViewTransition` call.

This doesn't matter yet but it will once we start assigning names before
the `startViewTransition` call.

A possible alternative solution might be to ensure the names are added
synchronously in the event that adds to the batch. It's possible to keep
adding to a batch until the snapshot has happened.
2025-05-22 10:25:13 -04:00
Sebastian Markbåge
08064ea671 [Fizz] Make ViewTransition enter/exit/share null the same as none (#33331)
I believe that these mean the same thing. We don't have to emit the
attribute if it's `none` for these cases because if there is no matching
scenario we won't apply the animation in this case.

The only case where we have to emit `none` in the attribute is for
`vt-update` because those can block updates from propagating upwards.
2025-05-22 10:21:28 -04:00
Sebastian Markbåge
99781d605b [Fizz] Track boundaries in future rows as postponed (#33329)
Follow up to #33321.

We can mark boundaries that were blocked in the prerender as postponed
but without anything to replayed inside them. That way they're not
emitted in the prerender but is unblocked when replayed.

Technically this does some unnecessary replaying of the path to the
otherwise already completed boundary but it simplifies our model by just
marking the boundary as needing replaying.
2025-05-22 10:20:13 -04:00
mofeiZ
459a2c4298 [compiler][gating] Experimental directive based gating (#33149)
Adds `dynamicGating` as an experimental option for testing rollout DX at
Meta. If specified, this enables dynamic gating which matches `use memo
if(...)` directives.

#### Example usage
Input file
```js
// @dynamicGating:{"source":"myModule"}
export function MyComponent() {
  'use memo if(isEnabled)';
   return <div>...</div>;
}
```
Compiler output
```js
import {isEnabled} from 'myModule';
export const MyComponent = isEnabled()
  ? <optimized version>
  : <original version>;
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33149).
* __->__ #33149
* #33148
2025-05-21 17:23:29 -04:00
Sebastian Markbåge
1c43d0aed7 Unify serverAct helpers (#33327)
This uses the richer `serverAct` helper that we already use in other
tests.

This avoids using the `Scheduler`. We don't use that package on the
server so it doesn't make sense to simulate going through it.
Additionally, we really should be getting rid of it on the client too to
favor `postTask` polyfills.
2025-05-21 16:13:54 -04:00
Jack Pope
1835b3f7d9 New children notify fragment instances in Fabric (#33093)
When a new child of a fragment instance is inserted, we need to notify
the instance to keep any relevant tracking up to date. For example, we
automatically observe the new child with any active
IntersectionObserver.

For mutable renderers (DOM), we reuse the existing traversal in
`commitPlacement` that does the insertions for HostComponents. Immutable
renderers (Fabric) exit this path before the traversal though, so
currently we can't notify the fragment instances.

Here I've created a separate traversal in `commitPlacement`,
specifically for immutable renders when `enableFragmentRefs` is on.
2025-05-21 15:47:47 -04:00
Sebastian Markbåge
f4041aa388 [Fizz] Unblock SuspenseList when prerendering (#33321)
There's an interesting case when a SuspenseList is partially prerendered
but some of the completed boundaries are blocked by rows to be resumed.

This handles it but just unblocking the future rows to avoid stalling.

However, the correct semantics will need special handling in the
postponed state.
2025-05-21 15:31:22 -04:00
Jack Pope
3710c4d4f9 Prevent errors from comment node roots with enableViewTransition (#33205)
We have many cases internally where the `containerInstance` resolves to
a comment node. `restoreRootViewTransitionName` is called when
`enableViewTransition` is on, even without introducing a
`<ViewTransition />`. So that means it can crash pages because
`containerInstance.style` is `undefined` just by turning on the flag.

This skips cancel/restore of root view transition name if a comment node is the root.
2025-05-21 13:57:35 -04:00
Sebastian Markbåge
2388481283 [Fizz] Set keyPath for SuspenseList (#33320)
I missed setting the `keyPath` because the `renderChildrenArray` that
this is forked from doesn't need to set a path but since this is
rendered from the `SuspenseList` element it needs it.
2025-05-20 21:08:47 -04:00
Sebastian Markbåge
9c7b10e22e [Fizz] Clean up row that was blocked by an aborted boundary (#33318)
Fixes a bug that we caused us to hang after an abort because we didn't
manage the ref count correctly.
2025-05-20 20:31:16 -04:00
Sebastian Markbåge
50389e1792 [Fizz] Hoist hoistables to each row and transfer the dependencies to future rows (#33312)
Stacked on #33311.

When a row contains Suspense boundaries that themselves depend on CSS,
they will not resolve until the CSS has loaded on the client. We need
future rows in a list to be blocked until this happens. We could do
something in the runtime but a simpler approach is to just add those CSS
dependencies to all those boundaries as well.

To do this, we first hoist the HoistableState from a completed boundary
onto its parent row. Then when the row finishes do we hoist it onto the
next row and onto any boundaries within that row.
2025-05-20 14:48:51 -04:00
Sebastian Markbåge
99aa685cef [Fizz] Support SuspenseList revealOrder="together" (#33311)
Stacked on #33308.

For "together" mode, we can be a self-blocking row that adds all its
boundaries to the blocked set, but there's no parent row that unblocks
it.

A particular quirk of this mode is that it's not enough to just unblock
them all on the server together. Because if one boundary downloads all
its html and then issues a complete instruction it'll appear before the
others while streaming in. What we actually want is to reveal them all
in a single batch.

This implementation takes a short cut by unblocking the rows in
`flushPartialBoundary`. That ensures that all the segments of every
boundary has a chance to flush before we start emitting any of the
complete boundary instructions. Once the last one unblocks, all the
complete boundary instructions are queued. Ideally this would be a
single `<script>` tag so that they can't be split up even if we get a
chunk containing some of them.

~A downside of this approach is that we always outline these boundaries.
We could inline them if they all complete before the parent flushes.
E.g. by checking if the row is blocked only by its own boundaries and if
all the boundaries would fit without getting outlined, then we can
inline them all at once.~ I went ahead and did this because it solves an
issue with `renderToString` where it doesn't support the script runtime
so it can only handle this if inlined.
2025-05-20 14:42:05 -04:00
Jan Kassens
d38c7e10d3 Remove leftover Rust script (#33314)
For now we removed Rust from the codebase, remove this leftover script.

Also remove some dupes and Rust related files from `.gitignore`.
2025-05-20 12:20:51 -04:00
Sebastian Markbåge
c4676e72a6 [Fizz] Handle nested SuspenseList (#33308)
Follow up to #33306.

If we're nested inside a SuspenseList and we have a row, then we can
point our last row to block the parent row and unblock the parent when
the last child unblocks.
2025-05-20 09:39:46 -04:00
Sebastian Markbåge
4c6967be29 [Fiber] Support AsyncIterable children in SuspenseList (#33299)
We support AsyncIterable (more so when it's a cached form like in coming
from Flight) as children.

This fixes some warnings and bugs when passed to SuspenseList.

Ideally SuspenseList with `tail="hidden"` should support unblocking
before the full result has resolved but that's an optimization on top.
We also might want to change semantics for this for
`revealOrder="backwards"` so it becomes possible to stream items in
reverse order.
2025-05-20 09:39:25 -04:00
Joseph Savona
c6c2a52ad8 [compiler] Fix error message for custom hooks (#33310)
We were printing "Custom" instead of "hook".
2025-05-19 15:29:58 -07:00
Sebastian Markbåge
5dc1b212c3 [Fizz] Support basic SuspenseList forwards/backwards revealOrder (#33306)
Basically we track a `SuspenseListRow` on the task. These keep track of
"pending tasks" that block the row. A row is blocked by:

- First itself completing rendering.
- A previous row completing.
- Any tasks inside the row and before the Suspense boundary inside the
row. This is mainly because we don't yet know if we'll discover more
SuspenseBoundaries.
- Previous row's SuspenseBoundaries completing.

If a boundary might get outlined, then we can't consider it completed
until we have written it because it determined whether other future
boundaries in the row can finish.

This is just handling basic semantics. Features not supported yet that
need follow ups later:

- CSS dependencies of previous rows should be added as dependencies of
future row's suspense boundary. Because otherwise if the client is
blocked on CSS then a previous row could be blocked but the server
doesn't know it.
- I need a second pass on nested SuspenseList semantics.
- `revealOrder="together"`
- `tail="hidden"`/`tail="collapsed"`. This needs some new runtime
semantics to the Fizz runtime and to allow the hydration to handle
missing rows in the HTML. This should also be future compatible with
AsyncIterable where we don't know how many rows upfront.
- Need to double check resuming semantics.

---------

Co-authored-by: Sebastian "Sebbie" Silbermann <silbermann.sebastian@gmail.com>
2025-05-19 15:16:42 -04:00
Jan Kassens
a3abf5f2f8 [eslint-plugin-react-hooks] add experimental_autoDependenciesHooks option (#33294) 2025-05-19 15:08:30 -04:00
Sebastian Markbåge
462d08f9ba Move SuspenseListProps into a shared/ReactTypes (#33298)
So they can be shared by server. Incorporates the types from definitely
typed too.
2025-05-17 20:00:56 -04:00
Sebastian Markbåge
6060367ef8 [Fizz] Wrap revealCompletedBoundaries in a ViewTransitions aware version (#33293)
When needed.

For the external runtime we always include this wrapper.

For others, we only include it if we have an ViewTransitions affecting.
If we discover the ViewTransitions late, then we can upgrade an already
emitted instruction.

This doesn't yet do anything useful with it, that's coming in a follow
up. This is just the mechanism for how it gets installed.
2025-05-17 18:18:24 -04:00
Sebastian Markbåge
c250b7d980 [Fizz] Should be considered complete inside onShellReady callback (#33295)
We decremented `allPendingTasks` after invoking `onShellReady`. Which
means that in that scope it wasn't considered fully complete.

Since the pattern for flushing in Node.js is to start piping in
`onShellReady` and that's how you can get sync behavior, this led us to
think that we had more work left to do. For example we emitted the
`writeShellTimeInstruction` in this scenario before.
2025-05-16 14:53:40 -04:00
Jan Kassens
4448b18760 [eslint-plugin-react-hooks] fix exhaustive deps lint rule with component syntax (#33182) 2025-05-15 12:51:18 -04:00
Ricky
4a45ba92c4 [sync] Fix noop for xplat (#33214)
Noop detection for xplat syncs broke because `eslint-plugin-react-hooks`
uses versions like:

- `0.0.0-experimental-d85f86cf-20250514`

But xplat expects them to be of the form:

- `19.2.0-native-fb-63d664b2-20250514`

This PR fixes the noop by ignoring
`eslint-plugin-react-hooks/package.json` changes. This means we won't
create a sync if only that package.json changes, but that should be rare
and we can follow up with better detection if needed.

[Example failed
action](https://github.com/facebook/react/actions/runs/15032346805/job/42247414406):

<img width="1031" alt="Screenshot 2025-05-15 at 11 31 17 AM"
src="https://github.com/user-attachments/assets/d902079c-1afe-4e18-af1d-25e60e28929e"
/>

I believe the regression was caused by
https://github.com/facebook/react/pull/33104
2025-05-15 12:12:51 -04:00
lauren
08cb2d7ee7 [ci] Log author_association (#33213)
For debugging purposes, log author_association
2025-05-15 11:49:56 -04:00
lauren
203df2c940 [compiler] Update changelog for 19.1.0-rc.2 (#33207)
Update the changelog.
2025-05-15 10:34:11 -04:00
Sebastian Markbåge
65b5aae010 [Fizz] Add vt- prefix attributes to annotate <ViewTransition> in HTML (#33206)
Stacked on #33194 and #33200.

When Suspense boundaries reveal during streaming, the Fizz runtime will
be responsible for animating the reveal if necessary (not in this PR).
However, for the future runtime to know what to do it needs to know
about the `<ViewTransition>` configuration to apply.

Ofc, these are virtual nodes that disappear from the HTML. We could
model them as comments like we do with other virtual nodes like Suspense
and Activity. However, that doesn't let us target them with
querySelector and CSS (for no-JS transitions). We also don't have to
model every ViewTransition since not every combination can happen using
only the server runtime. So instead this collapses `<ViewTransition>`
and applies the configuration to the inner DOM nodes.

```js
<ViewTransition name="hi">
  <div />
  <div />
</ViewTransition>
```

Becomes:

```html
<div vt-name="hi" vt-update="auto"></div>
<div vt-name="hi_1" vt-update="auto"></div>
```

I use `vt-` prefix as opposed to `data-` to keep these virtual
attributes away from user specific ones but we're effectively claiming
this namespace.

There are four triggers `vt-update`, `vt-enter`, `vt-exit` and
`vt-share`. The server resolves which ones might apply to this DOM node.
The value represents the class name (after resolving
view-transition-type mappings) or `"auto"` if no specific class name is
needed but this is still a trigger.

The value can also be `"none"`. This is different from missing because
for example an `vt-update="none"` will block mutations inside it from
triggering the boundary where as a missing `vt-update` would bubble up
to be handled by a parent.

`vt-name` is technically only necessary when `vt-share` is specified to
find a pair. However, since an explicit name can also be used to target
specific CSS selectors, we include it even for other cases.

We want to exclude as many of these annotations as possible.

`vt-enter` can only affect the first DOM node inside a Suspense
boundary's content since the reveal would cause it to enter but nothing
deeper inside. Similarly `vt-exit` can only affect the first DOM node
inside a fallback. So for every other case we can exclude them. (For
future MPA ViewTransitions of the whole document it might also be
something we annotate to children inside the `<body>` as well.) Ideally
we'd only include `vt-enter` for Suspense boundaries that actually
flushed a fallback but since we prepare all that content earlier it's
hard to know.

`vt-share` can be anywhere inside an fallback or content. Technically we
don't have to include it outside the root most Suspense boundary or for
boundaries that are inlined into the root shell. However, this is tricky
to detect. It would also not be correct for future MPA ViewTransitions
because in that case the shared scenario can affect anything in the two
documents so it needs to be in every node everywhere which is
effectively what we do. If a `share` class is specified but it has no
explicit name, we can exclude it since it can't match anything.

`vt-update` is only necessary if something below or a sibling might
update like a Suspense boundary. However, since we don't know when
rendering a segment if it'll later asynchronously add a Suspense
boundary later we have to assume that anywhere might have a child. So
these are always included. We collapse to use the inner most one when
directly nested though since that's the one that ends up winning.

There are some weird edge cases that can't be fully modeled by the lack
of virtual nodes.
2025-05-15 01:04:10 -04:00
Sebastian Markbåge
3f67d0857e [Fizz] Track whether we're in a fallback on FormatContext (#33194)
Removes the `isFallback` flag on Tasks and tracks it on the
formatContext instead.

Less memory and avoids passing and tracking extra arguments to all the
pushStartInstance branches that doesn't need it.

We'll need to be able to track more Suspense related contexts on this
for View Transitions anyway.
2025-05-15 00:06:06 -04:00
Sebastian Markbåge
96eb84e493 Claim the useId name space for every auto named ViewTransition (#33200)
This is a partial revert of #33094. It's true that we don't need the
server and client ViewTransition names to line up. However the server
does need to be able to generate deterministic names for itself. The
cheapest way to do that is using the useId algorithm. When it's used by
the server, the client needs to also materialize an ID even if it
doesn't use it.
2025-05-14 17:52:41 -04:00
Sebastian Markbåge
63d664b220 Don't consider Portals animating unless they're wrapped in a ViewTransition (#33191)
And that doesn't disable with `update="none"`.

The principle here is that we want the content of a Portal to animate if
other things are animating with it but if other things aren't animating
then we don't.
2025-05-14 17:50:56 -04:00
Jan Kassens
d85f86cf01 Delete stray file (#33199)
Not sure where this was coming from.
2025-05-14 11:27:36 -04:00
Sebastian Markbåge
3a5b326d81 [Fiber] Trigger default indicator for isomorphic async actions with no root associated (#33190)
Stacked on #33160, #33162, #33186 and #33188.

We have a special case that's awkward for default indicators. When you
start a new async Transition from `React.startTransition` then there's
not yet any associated root with the Transition because you haven't
necessarily `setState` on anything yet until the promise resolves.
That's what `entangleAsyncAction` handles by creating a lane that
everything entangles with until all async actions are done.

If there are no sync updates before the end of the event, we should
trigger a default indicator until either the async action completes
without update or if it gets entangled with some roots we should keep it
going until those roots are done.
2025-05-13 16:10:28 -04:00
Sebastian Markbåge
59440424d0 Implement Navigation API backed default indicator for DOM renderer (#33162)
Stacked on #33160.

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

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

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

There might be other routers on the page that might listen to intercept
Navigation Events. Typically you'd expect them not to trigger a refetch
when navigating to the same state. However, if they want to detect this
we provide the `"react-transition"` string in the `info` field for this
purpose.
2025-05-13 16:00:38 -04:00
Sebastian Markbåge
b480865db0 [Fiber] Always flush Default priority in the microtask if a Transition was scheduled (#33186)
Stacked on #33160.

The purpose of this is to avoid calling `onDefaultTransitionIndicator`
when a Default priority update acts as the loading indicator, but still
call it when unrelated Default updates happens nearby.

When we schedule Default priority work that gets batched with other
events in the same frame more or less. This helps optimize by doing less
work. However, that batching means that we can't separate work from one
setState from another. If we would consider all Default priority work in
a frame when determining whether to show the default we might never show
it in cases like when you have a recurring timer updating something.

This instead flushes the Default priority work eagerly along with the
sync work at the end of the event, if this event scheduled any
Transition work. This is then used to determine if the default indicator
needs to be shown.
2025-05-13 15:52:44 -04:00
Sebastian Markbåge
62d3f36ea7 [Fiber] Trigger default transition indicator if needed (#33160)
Stacked on #33159.

This implements `onDefaultTransitionIndicator`.

The sequence is:

1) In `markRootUpdated` we schedule Transition updates as needing
`indicatorLanes` on the root. This tracks the lanes that currently need
an indicator to either start or remain going until this lane commits.
2) Track mutations during any commit. We use the same hook that view
transitions use here but instead of tracking it just per view transition
scope, we also track a global boolean for the whole root.
3) If a sync/default commit had any mutations, then we clear the
indicator lane for the `currentEventTransitionLane`. This requires that
the lane is still active while we do these commits. See #33159. In other
words, a sync update gets associated with the current transition and it
is assumed to be rendering the loading state for that corresponding
transition so we don't need a default indicator for this lane.
4) At the end of `processRootScheduleInMicrotask`, right before we're
about to enter a new "event transition lane" scope, it is no longer
possible to render any more loading states for the current transition
lane. That's when we invoke `onDefaultTransitionIndicator` for any roots
that have new indicator lanes.
5) When we commit, we remove the finished lanes from `indicatorLanes`
and once that reaches zero again, then we can clean up the default
indicator. This approach means that you can start multiple different
transitions while an indicator is still going but it won't stop/restart
each time. Instead, it'll wait until all are done before stopping.

Follow ups:

- [x] Default updates are currently not enough to cancel because those
aren't flush in the same microtask. That's unfortunate. #33186
- [x] Handle async actions before the setState. Since these don't
necessarily have a root this is tricky. #33190
- [x] Disable for `useDeferredValue`. ~Since it also goes through
`markRootUpdated` and schedules a Transition lane it'll get a default
indicator even though it probably shouldn't have one.~ EDIT: Turns out
this just works because it doesn't go through `markRootUpdated` when
work is left behind.
- [x] Implement built-in DOM version by default. #33162
2025-05-13 15:45:11 -04:00
Sebastian Markbåge
0cac32d60d [Fiber] Stash the entangled async action lane on currentEventTransitionLane (#33188)
When we're entangled with an async action lane we use that lane instead
of the currentEventTransitionLane. Conversely, if we start a new async
action lane we reuse the currentEventTransitionLane.

So they're basically supposed to be in sync but they're not if you
resolve the async action and then schedule new stuff in the same event.
Then you end up with two transitions in the same event with different
lanes.

By stashing it like this we fix that but it also gives us an opportunity
to check just the currentEventTransitionLane to see if this event
scheduled any regular Transition updates or Async Transitions.
2025-05-13 15:20:59 -04:00
Sebastian Markbåge
676f0879f3 Reset currentEventTransitionLane after flushing sync work (#33159)
This keeps track of the transition lane allocated for this event. I want
to be able to use the current one within sync work flushing to know
which lane needs its loading indicator cleared.

It's also a bit weird that transition work scheduled inside sync updates
in the same event aren't entangled with other transitions in that event
when `flushSync` is.

Therefore this moves it to reset after flushing.

It should have no impact. Just splitting it out into a separate PR for
an abundance of caution.

The only thing this might affect would be if the React internals throws
and it doesn't reset after. But really it doesn't really have to reset
and they're all entangled anyway.
2025-05-13 15:18:02 -04:00
Sebastian Markbåge
997c7bc930 [DevTools] Get source location from structured callsites in prepareStackTrace (#33143)
When we get the source location for "View source for this element" we
should be using the enclosing function of the callsite of the child. So
that we don't just point to some random line within the component.

This is similar to the technique in #33136.

This technique is now really better than the fake throw technique, when
available. So I now favor the owner technique. The only problem it's
only available in DEV and only if it has a child that's owned (and not
filtered).

We could implement this same technique for the error that's thrown in
the fake throwing solution. However, we really shouldn't need that at
all because for client components we should be able to call
`inspect(fn)` at least in Chrome which is even better.
2025-05-13 12:39:10 -04:00
Sebastian Markbåge
b94603b955 [Fizz] Gate rel="expect" behind enableFizzBlockingRender (#33183)
Enabled in experimental channel.

We know this is critical semantics to enforce at the HTML level since if
you don't then you can't add explicit boundaries after the fact.
However, this might have to go in a major release to allow for
upgrading.
2025-05-13 10:17:53 -04:00
Jenny Steele
2bcf06b692 [ReactFlightWebpackPlugin] Add support for .mjs file extension (#33028)
## Summary
Our builds generate files with a `.mjs` file extension. These are
currently filtered out by `ReactFlightWebpackPlugin` so I am updating it
to support this file extension.

This fixes https://github.com/facebook/react/issues/33155

## How did you test this change?
I built the plugin with this change and used `yalc` to test it in my
project. I confirmed the expected files now show up in
`react-client-manifest.json`
2025-05-12 21:16:15 -04:00
Samuel Susla
5d04d73274 Add eager alternate.stateNode cleanup (#33161)
This is a fix for a problem where React retains shadow nodes longer than
it needs to. The behaviour is shown in React Native test:
https://github.com/facebook/react-native/blob/main/packages/react-native/src/private/__tests__/utilities/__tests__/ShadowNodeReferenceCounter-itest.js#L169

# Problem
When React commits a new shadow tree, old shadow nodes are stored inside
`fiber.alternate.stateNode`. This is not cleared up until React clones
the node again. This may be problematic if mutation deletes a subtree,
in that case `fiber.alternate.stateNode` will retain entire subtree
until next update. In case of image nodes, this means retaining entire
images.

So when React goes from revision A: `<View><View /></View>` to revision
B: `<View />`, `fiber.alternate.stateNode` will be pointing to Shadow
Node that represents revision A..


![image](https://github.com/user-attachments/assets/076b677e-d152-4763-8c9d-4f923212b424)


# Fix
To fix this, this PR adds a new feature flag
`enableEagerAlternateStateNodeCleanup`. When enabled,
`alternate.stateNode` is proactively pointed towards finishedWork's
stateNode, releasing resources sooner.

I have verified this fixes the issue [demonstrated by React Native
tests](https://github.com/facebook/react-native/blob/main/packages/react-native/src/private/__tests__/utilities/__tests__/ShadowNodeReferenceCounter-itest.js#L169).
All existing React tests pass when the flag is enabled.
2025-05-12 17:39:20 +01:00
mofeiZ
3820740a7f [compiler][entrypoint] Fix edgecases for noEmit and opt-outs (#33148)
Title
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33148).
* #33149
* __->__ #33148
2025-05-09 13:37:49 -04:00
mofeiZ
5069e18060 [compiler][be] Make program traversal more readable (#33147)
React Compiler's program traversal logic is pretty lengthy and complex
as we've added a lot of features piecemeal. `compileProgram` is 300+
lines long and has confusing control flow (defining helpers inline,
invoking visitors, mutating-asts-while-iterating, mutating global
`ALREADY_COMPILED` state).

- Moved more stuff to `ProgramContext`
- Separated `compileProgram` into a bunch of helpers

Tested by syncing this stack to a Meta codebase and observing no
compilation output changes (D74487851, P1806855669, P1806855379)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33147).
* #33149
* #33148
* __->__ #33147
2025-05-09 13:23:08 -04:00
Sebastian Markbåge
21fdf308a1 Use a shared noop function from shared/noop (#33154)
Stacked on #33150.

We use `noop` functions in a lot of places as place holders. I don't
think there's any real optimizations we get from having separate
instances. This moves them to use a common instance in `shared/noop`.
2025-05-08 21:33:18 -04:00
Jack Pope
4ca97e4891 Clean up enableSiblingPrerendering flag (#32319) 2025-05-08 20:49:23 -04:00
Sebastian Markbåge
9b79292ae7 Add plumbing for onDefaultTransitionIndicator (#33150)
This just adds the options at the root and wire it up to the root but it
doesn't do anything yet.
2025-05-08 20:42:50 -04:00
Niklas Mollenhauer
ac06829246 feat(compiler): Implement constant propagation for template literals (#33139)
New take on #29716

## Summary
Template literals consisting entirely of constant values will be inlined
to a string literal, effectively replacing the backticks with a double
quote.

This is done primarily to make the resulting instruction a string
literal, so it can be processed further in constant propatation. So this
is now correctly simplified to `true`:
```js
`` === "" // now true
`a${1}` === "a1" // now true
```

If a template string literal can only partially be comptime-evaluated,
it is not that useful for dead code elimination or further constant
folding steps and thus, is left as-is in that case. Same is true if the
literal contains an array, object, symbol or function.

## How did you test this change?

See added tests.
2025-05-08 09:24:22 -07:00
mofeiZ
38ef6550a8 [compiler][playground][tests] Standardize more pragmas (#33146)
(Almost) all pragmas are now one of the following:
- `@...TestOnly`: custom pragma for test fixtures
- `@<configName>` | `@<configName>:true`: enables with either true or a
default enabled value
- `@<configName>:<json value>`
2025-05-08 11:26:53 -04:00
mofeiZ
b629a865fb [compiler][be] Move test pragma to separate file (#33145)
`Environment.ts` is getting complex so let's separate test / playground
parsing logic from it
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33145).
* #33146
* __->__ #33145
2025-05-08 11:24:44 -04:00
mofeiZ
fbe7bc21b9 [compiler][be] repro edge cases for noEmit and module opt-outs (#33144)
see test fixtures
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33144).
* #33146
* #33145
* __->__ #33144
2025-05-08 11:18:16 -04:00
Dawid Małecki
9518f11856 Root import types from react-native in ReactNativeTypes (#33063) 2025-05-08 12:12:35 +01:00
Ruslan Lesiutin
557a64795c React DevTools 6.1.1 -> 6.1.2 (#33142)
Patch release to mitigate https://github.com/facebook/react/issues/32659

Essentially just 6.1.1 with:
* Restore all Transitions for Tree updates
([eps1lon](https://github.com/eps1lon) in
[#33042](https://github.com/facebook/react/pull/33042))
* Restore "double-click to view owners tree" functionality
([eps1lon](https://github.com/eps1lon) in
[#33039](https://github.com/facebook/react/pull/33039))
2025-05-08 08:01:17 +01:00
Jack Pope
8a8df5dbdd Add dispatchEvent to fragment instances (#32813)
`fragmentInstance.dispatchEvent(evt)` calls `element.dispatchEvent(evt)`
on the fragment's host parent. This mimics bubbling if the
`fragmentInstance` could receive an event itself.

If the parent is disconnected, there is a dev warning and no event is
dispatched.
2025-05-07 14:00:59 -04:00
Niklas Mollenhauer
946da518eb feat(compiler): implement constant folding for unary minus (#33140)
## Summary
`-constant` is represented as a `UnaryExpression` node that is currently
not part of constant folding. If the operand is a constant number, the
node is folded to `constant * -1`. This also coerces `-0` to `0`,
resulting in `0 === -0` being folded to `true`.

## How did you test this change?
See attached tests
2025-05-07 10:15:11 -07:00
Sebastian Markbåge
a437c99ff7 [Flight] Clarify that location field is a FunctionLocation not a CallSite (#33141)
Follow up to #33136.

This clarifies in the types where the conversion happens from a CallSite
which we use to simulate getting the enclosing line/col to a
FunctionLocation which doesn't represent a CallSite but actually just
the function which only has an enclosing line/col.
2025-05-07 13:02:41 -04:00
Jack Pope
4206fe4982 Allow fragment refs to attempt focus/focusLast on nested host children (#33058)
This enables `focus` and `focusLast` methods on FragmentInstances to
search nested host components, depth first. Attempts focus on each child
and bails if one is successful. Previously, only the first level of host
children would attempt focus.

Now if we have an example like

```
component MenuItem() {
  return (<div><a>{...}</a></div>)
}

component Menu() {
  return <Fragment>{items.map(i => <MenuItem i={i} />)}</Fragment>
}
```
We can target focus on the first or last a tag, rather than checking
each wrapping div and then noop.
2025-05-07 12:47:28 -04:00
Sebastian Markbåge
4a702865dd [Flight] Encode enclosing line/column numbers and use it to align the fake function (#33136)
Stacked on #33135.

This encodes the line/column of the enclosing function as part of the
stack traces. When that information is available.

I adjusted the fake function code generation so that the beginning of
the arrow function aligns with these as much as possible.

This ensures that when the browser tries to look up the line/column of
the enclosing function, such as for getting the function name, it gets
the right one. If we can't get the enclosing line/column, then we encode
it at the beginning of the file. This is likely to get a miss in the
source map identifiers, which means that the function name gets
extracted from the runtime name instead which is better.

Another thing where this is used is the in the Performance Track.
Ideally that would be fixed by
https://issues.chromium.org/u/1/issues/415968771 but the enclosing
information is useful for other things like the function name resolution
anyway.

We can also use this for the "View source for this element" in React
DevTools.
2025-05-07 12:34:55 -04:00
Sebastian Markbåge
0ff1d13b80 [Flight] Parse Stack Trace from Structured CallSite if available (#33135)
This is first step to include more enclosing line/column in the parsed
data.

We install our own `prepareStackTrace` to collect structured callsite
data and only fall back to parsing the string if it was already
evaluated or if `prepareStackTrace` doesn't work in this environment.

We still mirror the default V8 format for encoding the function name
part. A lot of this is covered by tests already.
2025-05-07 11:43:37 -04:00
YongSeok Jang (장용석)
53c9f81049 [DevTools] Use Popover API for TraceUpdates highlighting (#32614)
## Summary

When using React DevTools to highlight component updates, the highlights
would sometimes appear behind elements that use the browser's
[top-layer](https://developer.mozilla.org/en-US/docs/Glossary/Top_layer)
(such as `<dialog>` elements or components using the Popover API). This
made it difficult to see which components were updating when they were
inside or behind top-layer elements.

This PR fixes the issue by using the Popover API to ensure that
highlighting appears on top of all content, including elements in the
top-layer. The implementation maintains backward compatibility with
browsers that don't support the Popover API.

## How did you test this change?

I tested this change in the following ways:

1. Manually tested in Chrome (which supports the Popover API) with:
- Created a test application with React components inside `<dialog>`
elements and custom elements using the Popover API
- Verified that component highlighting appears above these elements when
they update
- Confirmed that highlighting displays correctly for nested components
within top-layer elements

2. Verified backward compatibility:
- Tested in browsers without Popover API support to ensure fallback
behavior works correctly
- Confirmed that no errors occur and highlighting still functions as
before

3. Ran the React DevTools test suite:
   - All tests pass successfully
   - No regressions were introduced

[demo-page](https://devtools-toplayer-demo.vercel.app/)
[demo-repo](https://github.com/yongsk0066/devtools-toplayer-demo)

### AS-IS

https://github.com/user-attachments/assets/dc2e1281-969f-4f61-82c3-480153916969

### TO-BE

https://github.com/user-attachments/assets/dd52ce35-816c-42f0-819b-0d5d0a8a21e5
2025-05-07 15:48:17 +01:00
Jack Pope
e5a8de81e5 Add compareDocumentPosition to fragment instances (#32722)
This adds `compareDocumentPosition(otherNode)` to fragment instances.

The semantics implemented are meant to match typical element
positioning, with some fragment specifics. See the unit tests for all
expectations.

- An element preceding a fragment is `Node.DOCUMENT_POSITION_PRECEDING`
- An element after a fragment is `Node.DOCUMENT_POSITION_FOLLOWING`
- An element containing the fragment is
`Node.DOCUMENT_POSITION_PRECEDING` and
`Node.DOCUMENT_POSITION_CONTAINING`
- An element within the fragment is
`Node.DOCUMENT_POSITION_CONTAINED_BY`
- An element compared against an empty fragment will result in
`Node.DOCUMENT_POSITION_DISCONNECTED` and
`Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC`

Since we assume a fragment instances target children are DOM siblings
and we want to compare the full fragment as a pseudo container, we can
compare against the first target child outside of handling the special
cases (empty fragments and contained elements).
2025-05-06 13:01:40 -04:00
Jorge Cabiedes
7a2c7045ae [mcp] Add proper web-vitals metric collection (#33109)
Multiple things here:
- Improve the mean calculation for metrics so we don't report 0 when
web-vitals fail to be retrieved
- improve ui chaos monkey to use puppeteer APIs since only those trigger
INP/CLS metrics since we need emulated mouse clicks
- Add logic to navigate to a temp page after render since some
web-vitals metrics are only calculated when the page is backgrounded
- Some readability improvements
2025-05-06 08:50:40 -07:00
Sebastian Markbåge
845d93742f Remove useId semantics from View Transition name generation (#33094)
Originally I thought it was important that SSR used the same View
Transition name as the client so that the Fizz runtime could emit those
names and then the client could pick up and take over. However, I no
longer believe that approach is feasible. Instead, the names can be
generated only during that particular animation.

Therefore we can simplify the auto name assignment to not have to
consider the hydration.
2025-05-06 10:33:03 -04:00
Sebastian Markbåge
54a50729cc [Fiber] Replay events between commits (#33130)
Stacked on #33129. Flagged behind `enableHydrationChangeEvent`.

If you type into a controlled input before hydration and something else
rerenders like a setState in an effect, then the controlled input will
reset to whatever React thought it was. Even with event replaying that
this is stacked on, if the second render happens before event replaying
has fired in a separate task.

We don't want to flush inside the commit phase because then things like
flushSync in these events wouldn't work since they're inside the commit
stack.

This flushes all event replaying between renders by flushing it at the
end of `flushSpawned` work. We've already committed at that point and is
about to either do subsequent renders or yield to event loop for passive
effects which could have these events fired anyway. This just ensures
that they've already happened by the time subsequent renders fire. This
means that there's now a type of event that fire between sync render
passes.
2025-05-06 00:23:27 -04:00
Sebastian Markbåge
587cb8f896 [Fiber] Replay onChange Events if input/textarea/select has changed before hydration (#33129)
This fixes a long standing issue that controlled inputs gets out of sync
with the browser state if it's changed before we hydrate.

This resolves the issue by replaying the change events (click, input and
change) if the value has changed by the time we commit the hydration.
That way you can reflect the new value in state to bring it in sync. It
does this whether controlled or uncontrolled.

The idea is that this should be ok to replay because it's similar to the
continuous events in that it doesn't replay a sequence but only reflects
the current state of the tree.

Since this is a breaking change I added it behind
`enableHydrationChangeEvent` flag.

There is still an additional issue remaining that I intend to address in
a follow up. If a `useLayoutEffect` triggers an sync rerender on
hydration (always a bad idea) then that can rerender before we have had
a chance to replay the change events. If that renders through a input
then that input will always override the browser value with the
controlled value. Which will reset it before we've had a change to
update to the new value.
2025-05-06 00:10:05 -04:00
Matt Carroll
79586c7eb6 Add test for multiple form submissions (#33059)
Test for #30041 and #33055
2025-05-05 14:47:47 -07:00
Jack Pope
edf550b679 Ship enableFabricCompleteRootInCommitPhase (#33064)
This was shipped internally. Cleaning up the flag.
2025-05-05 13:36:44 -04:00
Sebastian "Sebbie" Silbermann
b9cfa0d308 [Flight] Prevent serialized size leaking across requests (#33121) 2025-05-05 18:30:33 +02:00
mofeiZ
c129c2424b [compiler][repro] Nested fbt test fixture (#32779)
Ideally we should detect and bail out on this case to avoid babel build
failures.
2025-05-05 11:52:45 -04:00
mofeiZ
0c1575cee8 [compiler][bugfix] Bail out when a memo block declares hoisted fns (#32765)
Note that bailing out adds false positives for hoisted functions whose
only references are within other functions. For example, this rewrite
would be safe.
```js
// source program
  function foo() {
    return bar();
  }
  function bar() {
    return 42;
  }

// compiler output
let bar;
if (/* deps changed */) {
  function foo() {
    return bar();
  }
  bar = function bar() {
    return 42;
  }
}
```
These false positives are difficult to detect because any maybe-call of
foo before the definition of bar would be invalid.

Instead of bailing out, we should rewrite hoisted function declarations
to the following form.
```js
let bar$0;
if (/* deps changed */) {
  // All references within the declaring memo block
  // or before the function declaration should use
  // the original identifier `bar`
  function foo() {
    return bar();
  }
  function bar() {
    return 42;
  }
  bar$0 = bar;
}
// All references after the declaring memo block
// or after the function declaration should use
// the rewritten declaration `bar$0`
```
2025-05-05 11:45:58 -04:00
Sebastian Markbåge
52ea641449 [Flight] Don't increase serializedSize for every recursive pass (#33123)
I noticed that we increase this in the recursive part of the algorithm.
This would mean that we'd count a key more than once if it has Server
Components inside it recursively resolving. This moves it out to where
we enter from toJSON. Which is called once per JSON entry (and therefore
once per key).
2025-05-05 11:37:39 -04:00
Stephen Zhou
3ec88e797f [eslint-plugin-react-hooks] update doc url for rules of hooks (#33118) 2025-05-05 17:37:06 +02:00
Sebastian "Sebbie" Silbermann
0ca8420f9d [Flight] Use valid CSS selectors in useId format (#33099) 2025-05-04 13:47:32 +02:00
Joe Savona
0db8db178c [compiler] Validate against mutable functions being frozen
This revisits a validation I built a while ago, trying to make it more strict this time to ensure that it's high-signal.

We detect function expressions which are *known* mutable — they definitely can modify a variable defined outside of the function expression itself (modulo control flow). This uses types to look for known Store and Mutate effects only, and disregards mutations of effects. Any such function passed to a location with a Freeze effect is reported as a validation error.

This is behind a flag and disabled by default. If folks agree this makes sense to revisit, i'll test out internally and we can consider enabling by default.

ghstack-source-id: 075a731444ce95e52dbd5ea3be85c16d428927f5
Pull Request resolved: https://github.com/facebook/react/pull/33079
2025-05-03 09:15:32 +09:00
Joe Savona
8570116bd1 [compiler] Fix for uncalled functions that are known-mutable
If a function captures a mutable value but never gets called, we don't infer a mutable range for that function. This means that we also don't alias the function with its mutable captures.

This case is tricky, because we don't generally know for sure what is a mutation and what may just be a normal function call. For example:

```js
hook useFoo() {
  const x = makeObject();
  return () => {
    return readObject(x); // could be a mutation!
  }
}
```

If we pessimistically assume that all such cases are mutations, we'd have to group lots of memo scopes together unnecessarily. However, if there is definitely a mutation:

```js
hook useFoo(createEntryForKey) {
  const cache = new WeakMap();
  return (key) => {
    let entry = cache.get(key);
    if (entry == null) {
      entry = createEntryForKey(key);
      cache.set(key, entry); // known mutation!
    }
    return entry;
  }
}
```

Then we have to ensure that the function and its mutable captures alias together and end up in the same scope. However, aliasing together isn't enough if the function and operands all have empty mutable ranges (end = start + 1).

This pass finds function expressions and object methods that have an empty mutable range and known-mutable operands which also don't have a mutable range, and ensures that the function and those operands are aliased together *and* that their ranges are updated to end after the function expression. This is sufficient to ensure that a reactive scope is created for the alias set.

NOTE: The alternative is to reject these cases. If we do that we'd also want to similarly disallow cases like passing a mutable function to a hook.

ghstack-source-id: 5d8158246a320e80d8da3f0e395ac1953d8920a2
Pull Request resolved: https://github.com/facebook/react/pull/33078
2025-05-03 09:15:32 +09:00
Joe Savona
4f1d2ddf95 [compiler] Add types for WeakMap, WeakSet, and reanimated shared values
Building on mofeiz's recent work to type constructors. Also, types for reanimated values which are useful in the next PR.

ghstack-source-id: 1c81e213a11337ac7e9c85a429ecf3f1d1adef66
Pull Request resolved: https://github.com/facebook/react/pull/33077
2025-05-03 09:15:32 +09:00
Joe Savona
73d7e816b7 [compiler] ValidatePreservedManualMemoization reports detailed errors
This pass didn't previously report the precise difference btw inferred/manual dependencies unless a debug flag was set. But the error message is really good (nice job mofeiz): the only catch is that in theory the inferred dep could be a temporary that can't trivially be reported to the user.

But the messages are really useful for quickly verifying why the compiler couldn't preserve memoization. So here we switch to outputting a detailed message about the discrepancy btw inferred/manual deps so long as the inferred dep root is a named variable. I also slightly adjusted the message to handle the case where there is no diagnostic, which can occur if there were no manual deps but the compiler inferred a dependency.

ghstack-source-id: 534f6f1fec0855e05e85077eba050eb2ba254ef8
Pull Request resolved: https://github.com/facebook/react/pull/33095
2025-05-03 09:09:34 +09:00
Joe Savona
ac2cae5245 [compiler] Fix for string attribute values with emoji
If a JSX attribute value is a string that contains unicode or other characters that need special escaping, we wrap the attribute value in an expression container. However, our unicode to detect this only handled the basic unicode character plane, not the "astral" plane which includes emojis.

This PR updates the regex to detect such extended characters and also use an expression container.

ghstack-source-id: 6d9c8e4dd22285077108e2fa53d66154d1b781fb
Pull Request resolved: https://github.com/facebook/react/pull/33096
2025-05-03 09:07:50 +09:00
Jan Kassens
66de8e5a99 [eslint-plugin-react-hooks] move eslint plugin once again (#33110) 2025-05-02 18:14:56 -04:00
lauren
26ecc98a00 [mcp] Prompt improvements for smarter tool composition (#33108)
Our runtime results are still bogus, but so far this tweak let's the llm
use the tools a little more intelligently.

example:
---

```js
import React from 'react';

function App() {
  const [count, setCount] = React.useState(0);
  const [data, setData] = React.useState([]);

  React.useEffect(() => {
    const processData = () => {
      console.log("Processing data...");
      const newData = [];
      for (let i = 0; i < 1000000000; i++) {
        newData.push({ id: i, value: Math.random() * count });
      }
      return newData;
    };

    const processedData = processData();
    setData(processedData);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Data Length: {data.length}</p>
      <ul>
        {data.slice(0, 5).map((item) => (
          <li key={item.id}>Item {item.id}: {item.value.toFixed(2)}</li>
        ))}
      </ul>
    </div>
  );
};
```
why is this component slow? use the react tools to figure this out.

> I'll analyze why this component is slow using the React tools. Let's
first evaluate the performance of the original code.
>
[review-react-runtime ...]
>
> The performance measurement tool timed out, which is a strong
indication of just how severe the performance issue is in this
component. Let's now try to compile the code to see if React Compiler
can identify any issues:
>
[compile...]
>
> Now I understand the issue. Let's analyze why this component > is
extremely slow:
> Main Performance Issues:
>
> Massive Data Processing: The component is trying to create an array
with a billion (1,000,000,000) objects in the processData function. This
is an enormous amount of data that will:
>
> Consume massive amounts of memory
> Take an extremely long time to process
> Likely crash the browser
>
>
> Unnecessary Recalculation: The effect depends on [count], meaning
every time the count changes, it will reprocess this enormous dataset.
> Inefficient Data Handling: Even though only 5 items are displayed, the
component processes a billion items.
>
> Let's fix these issues:
>
[compile...]
>
> Now let's check if our modified code performs better:
>
[review-react-runtime...]
>
> Excellent! The performance has dramatically improved. Now let's check
the React documentation to make sure we're following the best practices:
>
[query-react-dev-docs...]

(hit the maximum length conversation limit because i'm on a free plan)
2025-05-02 17:50:39 -04:00
Jan Kassens
f0ca53d133 [eslint-plugin-react-hooks] another CI update... (#33107)
We currently created a nested directory, this should remove that.

See:

https://github.com/facebook/react/tree/builds/facebook-fbsource/compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/eslint-plugin-react-hooks/eslint-plugin-react-hooks
2025-05-02 17:05:56 -04:00
lauren
e39b380a21 [mcp] Fix unresolved imports (#33105)
We need to explicitly import the modules so they'll be inlined correctly
into the bundle.
2025-05-02 16:54:17 -04:00
Jan Kassens
9de0304ad7 Add missing copyright header (#33106)
This made the build fail since there was no file header comment.
2025-05-02 16:52:17 -04:00
Jan Kassens
0d695bea10 [eslint-plugin-react-hooks] update fbsource build (#33104)
In order to sync the lint rules directly to internal, include the eslint
plugin in the build output for fbsource.
2025-05-02 16:03:06 -04:00
Jan Kassens
4c4a57c4f9 [eslint-plugin-react-hooks] updates for component syntax (#33089)
Adds support for Flow's component and hook syntax.
[docs](https://flow.org/en/docs/react/component-syntax/)
2025-05-02 15:04:45 -04:00
lauren
dc2b11817b [mcp] Refactor (#33085)
Just some cleanup. Mainly, we now take the number of iterations as an
argument. Everything else is just code movement and small tweaks.
2025-05-02 14:15:12 -04:00
lauren
b5450b0738 [mcp] Update prompts (#33084)
Some tweaks to the prompt to provide more context on how to use them.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33084).
* #33085
* __->__ #33084
* #33083
2025-05-02 14:06:20 -04:00
lauren
f150c046ec [mcp] Move to /tools (#33083)
Moves to a tools directory.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33083).
* #33085
* #33084
* __->__ #33083
2025-05-02 14:06:11 -04:00
lauren
12b094d2f6 [mcp] Update plugins (#33082)
Adds typescript support.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33082).
* #33085
* #33084
* #33083
* __->__ #33082
* #33101
2025-05-02 13:56:45 -04:00
lauren
e5f0315efa [mcp] Fix package.json (#33101)
Since we use esbuild we need to correctly move dependencies that are
required at runtime into `dependencies` and other packages that are only
used in development in to `devDependencies`. This ensures the correct
packages are included in the build.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33101).
* #33085
* #33084
* #33083
* #33082
* __->__ #33101
2025-05-02 13:56:01 -04:00
Sebastian Markbåge
f739642745 [Fizz] Always load the external runtime if one is provided (#33091)
Because we now decided whether to outline in the flushing phase, when
we're writing the preamble we don't yet know if we will make that
decision so we don't know if it's safe to omit the external runtime.

However, if you are providing an external runtime it's probably a pretty
safe bet you're streaming something dynamically that's likely to need it
so we can always include it.

The main thing is that this makes it hard to test it because it affects
our tests in ways it wouldn't otherwise so we have to add a bunch of
conditions.
2025-05-01 18:14:42 -04:00
Sebastian Markbåge
0ed6ceb9f6 [Fizz] Add "Queued" Status to SSR:ed Suspense Boundaries (#33087)
Stacked on #33076.

This fixes a bug where we used the "complete" status but the
DOMContentLoaded event. This checks for not "loading" instead.

We also add a new status where the boundary has been marked as complete
by the server but has not yet flushed either due to being throttled,
suspended on CSS or animating.
2025-05-01 16:11:54 -04:00
Sebastian Markbåge
ee7fee8f88 [Fizz] Batch Suspense Boundary Reveal with Throttle (#33076)
Stacked on #33073.

React semantics is that Suspense boundaries reveal with a throttle
(300ms). That helps avoid flashing reveals when a stream reveals many
individual steps back to back. It can also improve overall performance
by batching the layout and paint work that has to happen at each step.

Unfortunately we never implemented this for SSR streaming - only for
client navigations. This is highly noticeable on very dynamic sites with
lots of Suspense boundaries. It can look good with a client nav but feel
glitchy when you reload the page or initial load.

This fixes the Fizz runtime to be throttled and reveals batched into a
single paint at a time. We do this by first tracking the last paint
after the complete (this will be the first paint if `rel="expect"` is
respected). Then in the `completeBoundary` operation we queue the
operation and then flush it all into a throttled batch.

Another motivation is that View Transitions need to operate as a batch
and individual steps get queued in a sequence so it's extra important to
include as much content as possible in each animated step. This will be
done in a follow up for SSR View Transitions.
2025-05-01 16:09:37 -04:00
Sebastian Markbåge
ee077b6ccd [Fizz] Don't handle errors in completeBoundary instruction (#33073)
Stacked on #33066 and #33068.

Currently we're passing `errorDigest` to `completeBoundary` if there is
a client side error (only CSS loading atm). This only exists because of
`completeBoundaryWithStyles`. Normally if there's a server-side error
we'd emit the `clientRenderBoundary` instruction instead. This adds
unnecessary code to the common case where all styles are in the head.
This is about to get worse with batching because client render shouldn't
be throttled but complete should be.

The first commit moves the client render logic inline into
`completeBoundaryWithStyles` so we only pay for it when styles are used.

However, the approach I went with in the second commit is to reuse the
`$RX` instruction instead (`clientRenderBoundary`). That way if you have
both it ends up being amortized. However, it does mean we have to emit
the `$RX` (along with the `$RC` helper if any
`completeBoundaryWithStyles` instruction is needed.
2025-05-01 15:44:17 -04:00
Sebastian Markbåge
bb57fa7351 [Fizz] Share code between inline and external runtime (#33066)
Stacked on #33065.

The runtime is about to be a lot more complicated so we need to start
sharing some more code.

The problem with sharing code is that we want the inline runtime to as
much as possible be isolated in its scope using only a few global
variables to refer across runtimes.

A problem with Closure Compiler is that it refuses to inline functions
if they have closures inside of them. Which makes sense because of how
VMs work it can cause memory leaks. However, in our cases this doesn't
matter and code size matters more. So we can't use many clever tricks.

So this just favors writing the source in the inline form. Then we add
an extra compiler pass to turn those global variables into local
variables in the external runtime.
2025-05-01 14:25:10 -04:00
Joe Savona
e9db3cc2d4 [compiler] PruneNonEscapingScopes understands terminal operands
We weren't treating terminal operands as eligible for memoization in PruneNonEscapingScopes, which meant that they could end up un-memoized. Terminal operands can also be compound ReactiveValues like SequenceExpressions, so part of the fix is to make sure we don't just recurse into compound values but record the full aliasing information we would for top-level instructions.

Still WIP, this needs to handle terminals other than for..of.

ghstack-source-id: 09a29230514e3bc95d1833cd4392de238fabbeda
Pull Request resolved: https://github.com/facebook/react/pull/33062
2025-05-01 12:41:27 +09:00
Jorge Cabiedes
d8074cbc79 [mcp] Make tool more reliable and fix integration issues with babel (#33074)
## Summary

Fix babel presets, and add a bit more context to the tool so that it is
more reliable

## How did you test this change?

Manually tested the mcp integrated with claude desktop
2025-04-30 15:42:00 -07:00
Sebastian Markbåge
71797c871b [Fizz] Ignore error if content node is gone (#33068)
We normally expect the segment to exist whatever the client does while
streaming. However, when hydration errors at the root of the shell for a
whole document render, then we clear nodes from body which can include
our segments. We don't need them anymore because we switched to client
rendering.

It triggers an error accessing parent node which can safely be ignored.
This just helps avoid confusion in this scenario.

This also covers up the error in #33067. Which doesn't actually cause
any visible problems other than error logging. However, ideally we
wouldn't emit completeBoundary instructions if the boundary is inside a
cancelled fallback.
2025-04-30 17:51:39 -04:00
mofeiZ
9d795d3808 [compiler][bugfix] expand StoreContext to const / let / function variants (#32747)
```js
function Component() {
  useEffect(() => {
    let hasCleanedUp = false;
    document.addEventListener(..., () => hasCleanedUp ? foo() : bar());
    // effect return values shouldn't be typed as frozen
    return () => {
      hasCleanedUp = true;
    }
  };
}
```
### Problem
`PruneHoistedContexts` currently strips hoisted declarations and
rewrites the first `StoreContext` reassignment to a declaration. For
example, in the following example, instruction 0 is removed while a
synthetic `DeclareContext let` is inserted before instruction 1.

```js
// source
const cb = () => x; // reference that causes x to be hoisted

let x = 4;
x = 5;

// React Compiler IR
[0] DeclareContext HoistedLet 'x'
...
[1] StoreContext reassign 'x' = 4
[2] StoreContext reassign 'x' = 5
```

Currently, we don't account for `DeclareContext let`. As a result, we're
rewriting to insert duplicate declarations.
```js
// source
const cb = () => x; // reference that causes x to be hoisted

let x;
x = 5;

// React Compiler IR
[0] DeclareContext HoistedLet 'x'
...
[1] DeclareContext Let 'x'
[2] StoreContext reassign 'x' = 5
```

### Solution

Instead of always lowering context variables to a DeclareContext
followed by a StoreContext reassign, we can keep `kind: 'Const' | 'Let'
| 'Reassign' | etc` on StoreContext.
Pros:
- retain more information in HIR, so we can codegen easily `const` and
`let` context variable declarations back
- pruning hoisted `DeclareContext` instructions is simple.

Cons:
- passes are more verbose as we need to check for both `DeclareContext`
and `StoreContext` declarations

~(note: also see alternative implementation in
https://github.com/facebook/react/pull/32745)~

### Testing
Context variables are tricky. I synced and diffed changes in a large
meta codebase and feel pretty confident about landing this. About 0.01%
of compiled files changed. Among these changes, ~25% were [direct
bugfixes](https://www.internalfb.com/phabricator/paste/view/P1800029094).
The [other
changes](https://www.internalfb.com/phabricator/paste/view/P1800028575)
were primarily due to changed (corrected) mutable ranges from
https://github.com/facebook/react/pull/33047. I tried to represent most
interesting changes in new test fixtures

`
2025-04-30 17:18:58 -04:00
mofeiZ
12f4cb85c5 [compiler][bugfix] Returned functions are not always frozen (#33047)
Fixes an edge case in React Compiler's effects inference model.

Returned values should only be typed as 'frozen' if they are (1) local
and (2) not a function expression which may capture and mutate this
function's outer context. See test fixtures for details
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33047).
* #32765
* #32747
* __->__ #33047
2025-04-30 15:50:54 -04:00
Jorge Cabiedes
90a124a980 [mdn] Initial experiment for adding performance tool (#33045)
## Summary
Add a way for the agent to get some data on the performance of react
code

## How did you test this change?
Tested function independently and directly with claude desktop app

---------

Co-authored-by: Sebastian "Sebbie" Silbermann <sebastian.silbermann@vercel.com>
2025-04-30 12:44:05 -07:00
Sebastian Markbåge
49ea8bf569 [Flight] Defer Elements if the parent chunk is too large (#33030)
Same principle as #33029 but for Flight.

We pretty aggressively create separate rows for things in Flight (every
Server Component that's an async function create a microtask). However,
sync Server Components and just plain Host Components are not. Plus we
should ideally ideally inline more of the async ones in the same way
Fizz does.

This means that we can create rows that end up very large. Especially if
all the data is already available. We can't show the parent content
until the whole thing loads on the client.

We don't really know where Suspense boundaries are for Flight but any
Element is potentially a point that can be split.

This heuristic counts roughly how much we've serialized to block the
current chunk and once a limit is exceeded, we start deferring all
Elements. That way they get outlined into future chunks that are later
in the stream. Since they get replaced by Lazy references the parent can
potentially get unblocked.

This can help if you're trying to stream a very large document with a
client nav for example.
2025-04-30 14:21:28 -04:00
Sebastian Markbåge
9a52ad9fd9 [Fizz] Remove globals from external runtime (#33065)
We never emit any inline functions when we use external runtime so this
global shouldn't be needed.
2025-04-30 14:21:14 -04:00
Sebastian "Sebbie" Silbermann
fa8e3a251e [devtools] Restore all Transitions for Tree updates (#33042) 2025-04-30 19:51:40 +02:00
Jack Pope
408d055a3b Add Fragment Refs to Fabric with intersection observer support (#33056)
Adds Fragment Ref support to RN through the Fabric config, starting with
`observeUsing`/`unobserveUsing`. This is mostly a copy from the
implementation on DOM, and some of it can likely be shared in the future
but keeping it separate for now and we can refactor as we add more
features.

Added a basic test with Fabric, but testing specific methods requires so
much mocking that it doesn't seem valuable here.

I built Fabric and ran on the Catalyst app internally to test with
intersection observers end to end.
2025-04-30 10:47:18 -04:00
Sebastian "Sebbie" Silbermann
fbf29ccaa3 [devtools] Restore "double-click to view owners tree" functionality (#33039) 2025-04-30 11:11:33 +02:00
Sebastian Markbåge
62960c67c8 Run Component Track Logs in the console.createTask() of the Fiber (#32809)
Stacked on #32736.

That way you can find the owner stack of each component that rerendered
for context.

In addition to the JSX callsite tasks that we already track, I also
added tracking of the first `setState` call before rendering.

We then run the "Update" entries in that task. That way you can find the
callsite of the first setState and therefore the "cause" of a render
starting by selecting the "Update" track.

Unfortunately this is blocked on bugs in Chrome that makes it so that
these stacks are not reliable in the Performance tab. It basically just
doesn't work.
2025-04-29 22:17:17 -04:00
Sebastian Markbåge
cd4e4d7599 Use console.timeStamp instead of performance.measure in Component Performance Track (#32736)
This is a new extension that Chrome added to the existing
`console.timeStamp` similar to the extensions added to
`performance.measure`. This one should be significantly faster because
it doesn't have the extra object indirection, it doesn't return a
`PerformanceMeasure` entry and doesn't register itself with the global
system of entries.

I also use `performance.measure` in DEV for errors since we can attach
the error to the `properties` extension which doesn't exist for
`console.timeStamp`.

A downside of using this API is that there's no programmatic API for the
site itself to collect its own logs from React. Which the previous
allowed us to use the standard `performance.getEntries()` for. The
recommendation instead will be for the site to patch `console.timeStamp`
if it wants to collect measurements from React just like you're
recommended to patch `console.error` or `fetch` or whatever to collect
other instrumentation metrics.

This extension works in Chrome canary but it doesn't yet work fully in
Chrome stable. We might want to wait until it has propagated to Chrome
to stable. It should be in Chrome 136.
2025-04-29 21:40:10 -04:00
Sebastian Markbåge
18212ca960 [Fizz] Outline if a boundary would add too many bytes to the next completion (#33029)
Follow up to #33027.

This enhances the heuristic so that we accumulate the size of the
currently written boundaries. Starting from the size of the root (minus
preamble) for the shell.

This ensures that if you have many small boundaries they don't all
continue to get inlined. For example, you can wrap each paragraph in a
document in a Suspense boundary to regain document streaming
capabilities if that's what you want.

However, one consideration is if it's worth producing a fallback at all.
Maybe if it's like `null` it's free but if it's like a whole alternative
page, then it's not. It's possible to have completely useless Suspense
boundaries such as when you nest several directly inside each other. So
this uses a limit of at least 500 bytes of the content itself for it to
be worth outlining at all. It also can't be too small because then for
example a long list of paragraphs can never be outlined.

In the fixture I straddle this limit so some paragraphs are too small to
be considered. An unfortunate effect of that is that you can end up with
some of them not being outlined which means that they appear out of
order. SuspenseList is supposed to address that but it's unfortunate.

The limit is still fairly high though so it's unlikely that by default
you'd start outlining anything within the viewport at all. I had to
reduce the `progressiveChunkSize` by an order of magnitude in my fixture
to try it out properly.
2025-04-29 19:13:28 -04:00
Sebastian Markbåge
88b9767404 Hack to recover from reading the wrong Fiber (#33055)
`requestFormReset` incorrectly tries to get the current dispatch queue
from the Fiber. However, the Fiber might be the workInProgress which is
an inconsistent state.

This hack just tries the other Fiber if it detects one of the known
inconsistent states but there can be more.

Really we should stash the dispatch queue somewhere stateful which is
effectively what `setState` does by binding it to the closure.
2025-04-29 13:36:19 -04:00
Pieter De Baets
0038c501a3 [react-native] Pull up enableFastAddPropertiesInDiffing check (#33043)
## Summary

We don't need the isArray check for this experiment, as
`fastAddProperties` already does the same. Also renaming
slowAddProperties to make it clearer we can fully remove this codepath
once fastAddProperties is fully rolled out.

## How did you test this change?

```
yarn test packages/react-native-renderer -r=xplat --variant=true
```
2025-04-29 11:10:18 +01:00
Sebastian Markbåge
5dc00d6b2b [Fizz] Reset Instructions on ResumableState (#33046)
When we end up creating an incomplete state in the shell we end up not
flushing anything. As a hack, in this case we need to reset the
ResumableState because some of the ResumableState is still relevant
(e.g. any preloads that went into headers) but some of the
ResumableState needs to be reset since they assume that what we produced
actually flushed.

We didn't reset the instructions state but we haven't actually flushed
any of the instructions so it needs to reset.
2025-04-28 15:50:06 -04:00
Sebastian "Sebbie" Silbermann
c498bfce8b [devtools] Allow inspecting cause, name, message, stack of Errors in props (#33023) 2025-04-26 07:20:57 +02:00
Sebastian Markbåge
8e9a5fc6c1 [Fizz] Enable the progressiveChunkSize option (#33027)
Since the very beginning we have had the `progressiveChunkSize` option
but we never actually took advantage of it because we didn't count the
bytes that we emitted. This starts counting the bytes by taking a pass
over the added chunks each time a segment completes.

That allows us to outline a Suspense boundary to stream in late even if
it is already loaded by the time that back-pressure flow and in a
`prerender`. Meaning it gets inserted with script.

The effect can be seen in the fixture where if you have large HTML
content that can block initial paint (thanks to
[`rel="expect"`](https://github.com/facebook/react/pull/33016) but also
nested Suspense boundaries). Before this fix, the paint would be blocked
until the large content loaded. This lets us paint the fallback first in
the case that the raw bytes of the content takes a while to download.

You can set it to `Infinity` to opt-out. E.g. if you want to ensure
there's never any scripts. It's always set to `Infinity` in
`renderToHTML` and the legacy `renderToString`.

One downside is that if we might choose to outline a boundary, we need
to let its fallback complete.

We don't currently discount the size of the fallback but really just
consider them additive even though in theory the fallback itself could
also add significant size or even more than the content. It should maybe
really be considered the delta but that would require us to track the
size of the fallback separately which is tricky.

One problem with the current heuristic is that we just consider the size
of the boundary content itself down to the next boundary. If you have a
lot of small boundaries adding up, it'll never kick in. I intend to
address that in a follow up.
2025-04-25 16:10:53 -04:00
mofeiZ
89e8875ec4 [compiler] Fallback for inferred effect dependencies (#32984)
When effect dependencies cannot be inferred due to memoization-related
bailouts or unexpected mutable ranges (which currently often have to do
with writes to refs), fall back to traversing the effect lambda itself.

This fallback uses the same logic as PropagateScopeDependencies:
1. Collect a sidemap of loads and property loads
2. Find hoistable accesses from the control flow graph. Note that here,
we currently take into account the mutable ranges of instructions (see
`mutate-after-useeffect-granular-access` fixture)
3. Collect the set of property paths accessed by the effect
4. Merge to get the set of minimal dependencies
2025-04-25 15:44:39 -04:00
mofeiZ
2d0a5e399f [compiler] Patch for reactive refs in inferred effect dependencies (#32991)
Inferred effect dependencies and inlined jsx (both experimental
features) rely on `InferReactivePlaces` to determine their dependencies.


Since adding type inference for phi nodes
(https://github.com/facebook/react/pull/30796), we have been incorrectly
inferring stable-typed value blocks (e.g. `props.cond ? setState1 :
setState2`) as non-reactive. This fix patches InferReactivePlaces
instead of adding a new pass since we want non-reactivity propagated
correctly
2025-04-25 15:42:40 -04:00
mofeiZ
0c28a09eef [ci] Reduce non-deterministic builds for eslint-plugin-react-hooks (#33026)
See https://github.com/rollup/plugins/issues/1425

Currently, `@babel/helper-string-parser/lib/index.js` is either emitted
as a wrapped esmodule or inline depending on the ordering of async
functions in `rollup/commonjs`. Specifically,
`@babel/types/lib/definitions/core.js` is cyclic (i.e. transitively
depends upon itself), but sometimes
`@babel/helper-string-parser/lib/index.js` is emitted before this is
realized.


A relatively straightforward patch is to wrap all modules (see
https://github.com/rollup/plugins/issues/1425#issuecomment-1465626736).
This only regresses `eslint-plugin-react-hooks` bundle size by ~1.8% and
is safer (see
https://github.com/rollup/plugins/blob/master/packages/commonjs/README.md#strictrequires)

> The default value of true will wrap all CommonJS files in functions
which are executed when they are required for the first time, preserving
NodeJS semantics. This is the safest setting and should be used if the
generated code does not work correctly with "auto". Note that
strictRequires: true can have a small impact on the size and performance
of generated code, but less so if the code is minified.

(note that we're on an earlier version of `@rollup/commonjs` which does
not default to `strictRequires: true`)
2025-04-25 14:26:59 -04:00
Sebastian Markbåge
143d3e1b89 [Fizz] Emit link rel="expect" to block render before the shell has fully loaded (#33016)
The semantics of React is that anything outside of Suspense boundaries
in a transition doesn't display until it has fully unsuspended. With SSR
streaming the intention is to preserve that.

We explicitly don't want to support the mode of document streaming
normally supported by the browser where it can paint content as tags
stream in since that leads to content popping in and thrashing in
unpredictable ways. This should instead be modeled explictly by nested
Suspense boundaries or something like SuspenseList.

After the first shell any nested Suspense boundaries are only revealed,
by script, once they're fully streamed in to the next boundary. So this
is already the case there. However, for the initial shell we have been
at the mercy of browser heuristics for how long it decides to stream
before the first paint.

Chromium now has [an API explicitly for this use
case](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API/Using#stabilizing_page_state_to_make_cross-document_transitions_consistent)
that lets us model the semantics that we want. This is always important
but especially so with MPA View Transitions.

After this a simple document looks like this:

```html
<!DOCTYPE html>
<html>
  <head>
     <link rel="expect" href="#«R»" blocking="render"/>
  </head>
  <body>
    <p>hello world</p>
    <script src="bootstrap.js" id="«R»" async=""></script>
    ...
  </body>
</html>
```

The `rel="expect"` tag indicates that we want to wait to paint until we
have streamed far enough to be able to paint the id `"«R»"` which
indicates the shell.

Ideally this `id` would be assigned to the root most HTML element in the
body. However, this is tricky in our implementation because there can be
multiple and we can render them out of order.

So instead, we assign the id to the first bootstrap script if there is
one since these are always added to the end of the shell. If there isn't
a bootstrap script then we emit an empty `<template
id="«R»"></template>` instead as a marker.

Since we currently put as much as possible in the shell if it's loaded
by the time we render, this can have some negative effects for very
large documents. We should instead apply the heuristic where very large
Suspense boundaries get outlined outside the shell even if they're
immediately available. This means that even prerenders can end up with
script tags.

We only emit the `rel="expect"` if you're rendering a whole document.
I.e. if you rendered either a `<html>` or `<head>` tag. If you're
rendering a partial document, then we don't really know where the
streaming parts are anyway and can't provide such guarantees. This does
apply whether you're streaming or not because we still want to block
rendering until the end, but in practice any serialized state that needs
hydrate should still be embedded after the completion id.
2025-04-25 11:52:28 -04:00
Sebastian Markbåge
693803a9bb Rename Suspense unstable_name to name (#33014)
This was only used by Transition Tracing which isn't really used
anywhere.

However, we want to start using it for other DevTools.
2025-04-24 16:53:34 -04:00
lauren
24dfad3abb [compiler] Add changelog (#32983)
Adds CHANGELOG.md.

This entry contains changes from the very first beta
`19.0.0-beta-9ee70a1-20241017` to `19.1.0-rc.1`.
2025-04-24 16:20:02 -04:00
lauren
bb74190c26 [mcp] Convert docs resource to tool (#33009)
Seems to work better as a tool. Also it now returns plaintext instead of
markdown.
2025-04-24 14:57:44 -04:00
lauren
5010364d34 [chore] Update caniuse-lite (#33013)
silence annoying warnings

```
npx update-browserslist-db@latest
```
2025-04-24 13:50:03 -04:00
lauren
9938f83ca2 [compiler] Emit CompileSkip before CompileSuccess event (#33012)
Previously the CompileSuccess event would emit first before CompileSkip,
so the lsp's codelens would incorrectly flag skipped components/hooks
(via 'use no memo') as being optimized.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33012).
* __->__ #33012
* #33011
* #33010
2025-04-24 13:30:36 -04:00
lauren
2af218a728 [forgive][ez] Tweak logging (#33011)
Just some tweaks

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33011).
* #33012
* __->__ #33011
* #33010
2025-04-24 13:30:16 -04:00
lauren
b06bb35ce9 [forgive] Don't look up user babel configs (#33010)
Projects with existing babel config files may confuse the LSP, so
explictly opt out of looking them up.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33010).
* #33012
* #33011
* __->__ #33010
2025-04-24 13:29:56 -04:00
Sebastian "Sebbie" Silbermann
197d6a0403 [devtools] 1st class support of used Thenables (#32989)
Co-authored-by: Ruslan Lesiutin <rdlesyutin@gmail.com>
2025-04-24 13:46:31 +02:00
lauren
ad09027c16 [compiler] Add missing copyrights (#33004)
`yarn copyright`
2025-04-23 22:04:44 -04:00
lauren
8b9629c810 [compiler] Fix copyright script (#33003)
Don't try to open directories
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33003).
* #33004
* __->__ #33003
* #33002

---------

Co-authored-by: Jordan Brown <jmbrown@meta.com>
2025-04-23 21:55:24 -04:00
lauren
3a5335676f [forgive] Polish decorations (#33002)
Polishes up decorations.

Co-authored-by: Jordan Brown <jmbrown@meta.com>
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33002).
* #33004
* #33003
* __->__ #33002

Co-authored-by: Jordan Brown <jmbrown@meta.com>
2025-04-23 21:55:15 -04:00
lauren
b75af04670 [forgive] Don't crash if we couldn't compile (#33001)
Compiler shouldn't crash Forgive if it can't compile (eg parse error due
to being mid-typing).

Co-authored-by: Jordan Brown <jmbrown@meta.com>
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33001).
* #33002
* __->__ #33001
* #33000

---------

Co-authored-by: Jordan Brown <jmbrown@meta.com>
2025-04-23 21:32:11 -04:00
lauren
f765082996 [forgive] Add code action to remove dependency array (#33000)
Adds a new codeaction event in the compiler and handler in forgive. This
allows you to remove a dependency array when you're editing a range that
is within an autodep eligible function.

Co-authored-by: Jordan Brown <jmbrown@meta.com>
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33000).
* #33002
* #33001
* __->__ #33000

Co-authored-by: Jordan Brown <jmbrown@meta.com>
2025-04-23 21:31:57 -04:00
lauren
7b21c46489 [forgive] Refactor inferred deps (#32999)
Refactor.

Co-authored-by: Jordan Brown <jmbrown@meta.com>
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32999).
* #33002
* #33001
* #33000
* __->__ #32999

Co-authored-by: Jordan Brown <jmbrown@meta.com>
2025-04-23 21:27:22 -04:00
lauren
e25e8c7575 [forgive] Hacky first pass at adding decorations for inferred deps (#32998)
Draws basic decorations for inferred deps on hover.

Co-authored-by: Jordan Brown <jmbrown@meta.com>
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32998).
* #33002
* #33001
* #33000
* #32999
* __->__ #32998

Co-authored-by: Jordan Brown <jmbrown@meta.com>
2025-04-23 21:21:44 -04:00
lauren
cd7d236682 [forgive] Emit AutoDepsDecoration event when inferring effect deps (#32997)
Emits a new event for decorating inferred effect dependencies.

Co-authored-by: Jordan Brown <jmbrown@meta.com>
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32997).
* #33002
* #33001
* #33000
* #32999
* #32998
* __->__ #32997
* #32996

---------

Co-authored-by: Jordan Brown <jmbrown@meta.com>
2025-04-23 20:51:38 -04:00
lauren
71d0896a4a [forgive] Log inferEffectDependencies (#32996)
This was missed earlier.


Co-authored-by: Jordan Brown <jmbrown@meta.com>
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32996).
* #33002
* #33001
* #33000
* #32999
* #32998
* #32997
* __->__ #32996

Co-authored-by: Jordan Brown <jmbrown@meta.com>
2025-04-23 20:49:25 -04:00
Hendrik Liebau
914319ae59 [Flight] Don't hang forever when prerendering a rejected promise (#32953) 2025-04-23 11:02:43 +02:00
Sebastian Markbåge
3ef31d196a Implement Partial Hydration for Activity (#32863)
Stacked on #32862 and #32842.

This means that Activity boundaries now act as boundaries which can have
their effects mounted independently. Just like Suspense boundaries, we
hydrate the outer content first and then start hydrating the content in
an Offscreen lane. Flowing props or interacting with the content
increases the priority just like Suspense boundaries.

This skips emitting even the comments for `<Activity mode="hidden">` so
we don't hydrate those. Instead those are deferred to a later client
render.

The implementation are just forked copies of the SuspenseComponent
branches and then carefully going through each line and tweaking it.

The main interesting bit is that, unlike Suspense, Activity boundaries
don't have fallbacks so all those branches where you might commit a
suspended tree disappears. Instead, if something suspends while
hydration, we can just leave the dehydrated content in place. However,
if something does suspend during client rendering then it should bubble
up to the parent. Therefore, we have to be careful to only
pushSuspenseHandler when hydrating. That's really the main difference.

This just uses the existing basic Activity tests but I've started work
on port all of the applicable Suspense tests in SelectiveHydration-test
and PartialHydration-test to Activity versions.
2025-04-22 21:00:30 -04:00
Sebastian Markbåge
17f88c80ed Implement ActivityInstance in FiberConfigDOM (#32842)
Stacked on #32851 and #32900.

This implements the equivalent Configs for ActivityInstance as we have
for SuspenseInstance. These can be implemented as comments but they
don't have to be and can be implemented differently in the renderer.

This seems like a lot duplication but it's actually ends mostly just
calling the same methods underneath and the wrappers compiles out.

This doesn't leave the Activity dehydrated yet. It just hydrates into it
immediately.
2025-04-22 19:44:14 -04:00
Sebastian Markbåge
3fbd6b7b50 Set hidden Offscreen to the shellBoundary regardless of previous state (#32844)
I think this was probably just copy-paste from the Suspense path.

It shouldn't matter what the previous state of an Offscreen boundary
was. What matters is that it's now hidden and therefore if it suspends,
we can just leave it as is without the tree becoming inconsistent.
2025-04-22 19:39:09 -04:00
Sebastian Markbåge
ebf7318e87 Hide/unhide the content of dehydrated suspense boundaries if they resuspend (#32900)
Found this bug while working on Activity. There's a weird edge case when
a dehydrated Suspense boundary is a direct child of another Suspense
boundary which is hydrated but then it resuspends without forcing the
inner one to hydrate/delete.

It used to just leave that in place because hiding/unhiding didn't deal
with dehydrated fragments.

Not sure this is really worth fixing.
2025-04-22 19:29:12 -04:00
Hendrik Liebau
620c838fb6 Build react-server-dom-webpack for codesandbox (#32990)
This allows us to test Flight changes in a codesandbox.

[Example](https://codesandbox.io/p/devbox/zkjk7y)
2025-04-22 22:20:21 +02:00
lauren
7213509649 [compiler] Only append hash and date for experimental releases (#32981)
No need to append these for non experimental/beta releases.
2025-04-21 15:10:51 -04:00
lauren
4c54da77fb [ci] Change to string type (#32980)
to no one's surprise, the `number` type appears to be cursed in GH
actions for workflow dispatch. switch to string
2025-04-21 14:56:51 -04:00
lauren
efd890422d [compiler] Fix version name in publish script (#32979)
Add ability to specify an optional tagVersion which is appended to the
version name + tag, eg

19.1.0-rc.1
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32979).
* __->__ #32979
* #32978
2025-04-21 14:43:20 -04:00
lauren
b303610c33 [eprh] Bump stable version (#32978)
https://www.npmjs.com/package/eslint-plugin-react-hooks/v/6.0.0 was just
released, so we can bump this now.
2025-04-21 14:36:13 -04:00
lauren
fea92d8462 [ci] Remove compiler weekly release (#32977)
No longer needed.
2025-04-21 13:47:50 -04:00
Sebastian "Sebbie" Silbermann
bc6184dd99 [devtools] Fix "View source" for sources with URLs that aren't normalized (#32951) 2025-04-17 21:56:05 +02:00
lauren
ce578f9c59 [compiler] Update publish tags (#32952)
Adds missing tag.
2025-04-17 13:13:50 -04:00
lauren
45d942f94a [mcp] Also emit bailout messages with no loc (#32937)
Not every bailout will contain a loc (could be synthetic)
2025-04-17 13:11:55 -04:00
Jordan Brown
b8bedc267f [compiler][autodeps/fire] Do not include fire functions in autodep arrays (#32532)
Summary: We landed on not including fire functions in dep arrays. They
aren't needed because all values returned from the useFire hook call
will read from the same ref. The linter will error if you include a
fired function in an explicit dep array.

Test Plan: yarn snap --watch

--
2025-04-17 13:03:19 -04:00
lauren
4a36d3eab7 [ci] Only label on PR open (#32936)
No reason to label it every update, only do it once when it's first
opened.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32936).
* __->__ #32936
* #32935
2025-04-16 18:01:55 -04:00
lauren
2ddf8caa9d [ci] Fix check_access again (#32935)
I can see the value being output and set correctly but not sure why it's
skipping the 2nd job.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32935).
* #32936
* __->__ #32935
2025-04-16 18:00:25 -04:00
lauren
95ff37f5f5 [mcp] Iterate on prompt (#32932)
v2
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32932).
* __->__ #32932
* #32931
* #32930
* #32929
* #32928
2025-04-16 17:49:25 -04:00
lauren
3c75bf21dd [mcp] Fix bailout loc (#32931)
Use the correct loc line numbers and not [Object:object]
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32931).
* #32932
* __->__ #32931
* #32930
* #32929
* #32928
2025-04-16 17:49:15 -04:00
lauren
3e04b2a214 [mcp] Refine passes returned (#32930)
Adds some new options to request the HIR, ReactiveFunction passes
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32930).
* #32932
* #32931
* __->__ #32930
* #32929
* #32928
2025-04-16 17:49:04 -04:00
lauren
fc21d5a7db [mcp] Dedupe docs (#32929)
Previously the resource would return a bunch of dupes because the
algolia results would return multiple hashes (headings) for the same
url.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32929).
* #32932
* #32931
* #32930
* __->__ #32929
* #32928
2025-04-16 17:48:53 -04:00
lauren
35ab8ffef7 [mcp] Add inspect script (#32928)
Uses https://github.com/modelcontextprotocol/inspector to inspect and
debug the mcp server.

`yarn workspace react-mcp-server dev` will build the server in watch
mode and launch the inspector. Default address is http://127.0.0.1:6274.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32928).
* #32932
* #32931
* #32930
* #32929
* __->__ #32928
2025-04-16 17:48:38 -04:00
lauren
68013725ac [ci] Fix check_access fr (#32934)
💀
2025-04-16 17:39:24 -04:00
lauren
bf39780a06 [ci] Fix check_access output (#32933)
the joy of yml
2025-04-16 17:27:09 -04:00
Sebastian Markbåge
b04254fdce Don't try to hydrate a hidden Offscreen tree (#32862)
I found a bug even before the Activity hydration stuff.

If we're hydrating an Offscreen boundary in its "hidden" state it won't
have any content to hydrate so will trigger hydration errors (which are
then eaten by the Offscreen boundary itself). Leaving it not prewarmed.

This doesn't happen in the simple case because we'd be hydrating at a
higher priority than Offscreen at the root, and those are deferred to
Offscreen by not having higher priority. However, we've hydrating at the
Offscreen priority, which we do inside Suspense boundaries, then it
tries to hydrate against an empty set.

I ended up moving this to the Activity boundary in a future PR since
it's the SSR side that decided where to not render something and it only
has a concept of Activity, no Offscreen.


1dc05a5e22 (diff-d5166797ebbc5b646a49e6a06a049330ca617985d7a6edf3ad1641b43fde1ddfR1111)
2025-04-15 17:43:42 -04:00
Sebastian Markbåge
539bbdbd86 Warn if you pass a hidden prop to Activity (#32916)
Since `hidden` is a prop on arbitrary DOM elements it's a common mistake
to think that it would also work that way on `<Activity>` but it
doesn't. In fact, we even had this mistakes in our own tests.

Maybe there's an argument that we should actually just support it but we
also have more modes planned.

So this adds a warning. It should also already be covered by TypeScript.
2025-04-15 17:17:22 -04:00
lauren
e71d4205ae [ci] Don't run some checks for non-members/collaborators (#32918)
There's really no need to even run the workflow for non-members or
collaborators for the labeling and discord notification workflows. We
can exit early.
2025-04-15 13:02:16 -04:00
lauren
2ed34eba0d Update @playwright/test (#32917)
Routine update.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32917).
* #32918
* __->__ #32917
2025-04-15 12:52:43 -04:00
Jorge (Hezi) Cohen
707b3fc6b2 [DevTools] Make Toggle hover state more visible (#32914)
This change adds a background color to Toggles to make them easier to
see. This is especially important when DevTools are not in focus, and
it's harder to see.

Test plan:
1. `yarn build:chrome:local`
2. Inspect components 
3. Hover over "Select an Element in page to inspect it"
4. Observe background change
2025-04-15 11:20:29 +01:00
Piotr Tomczewski
7ff4d057b6 [DevTools] feat: show changed hooks names in the Profiler tab (#31398)
<!--
  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 adds support for displaying the names of changed hooks directly
in the Profiler tab, making it easier to identify specific updates.

A `HookChangeSummary` component has been introduced to show these hook
names, with a `displayMode` prop that toggles between `“compact”` for
tooltips and `“detailed”` for more in-depth views. This keeps tooltip
summaries concise while allowing for a full breakdown where needed.

This functionality also respects the `“Always parse hook names from
source”` setting from the Component inspector, as it uses the same
caching mechanism already in place for the Components tab. Additionally,
even without hook names parsed, the Profiler will now display hook types
(like `State`, `Callback`, etc.) based on data from `inspectedElement`.

To enable this across the DevTools, `InspectedElementContext` has been
moved higher in the component tree, allowing it to be shared between the
Profiler and Components tabs. This update allows hook name data to be
reused across tabs without duplication.

Additionally, a `getAlreadyLoadedHookNames` helper function was added to
efficiently access cached hook names, reducing the need for repeated
fetching when displaying changes.

These changes improve the ability to track specific hook updates within
the Profiler tab, making it clearer to see what’s changed.

### Before
Previously, the Profiler tab displayed only the IDs of changed hooks, as
shown below:
<img width="350" alt="Screenshot 2024-11-01 at 12 02 21_cropped"
src="https://github.com/user-attachments/assets/7a5f5f67-f1c8-4261-9ba3-1c76c9a88af3">

### After (without hook names parsed)
When hook names aren’t parsed, custom hooks and hook types are displayed
based on the inspectedElement data:
<img width="350" alt="Screenshot 2024-11-01 at 12 03 09_cropped"
src="https://github.com/user-attachments/assets/ed857a6d-e6ef-4e5b-982c-bf30c2d8a7e2">

### After (with hook names parsed)
Once hook names are fully parsed, the Profiler tab provides a complete
breakdown of specific hooks that have changed:
<img width="350" alt="Screenshot 2024-11-01 at 12 03 14_cropped"
src="https://github.com/user-attachments/assets/1ddfcc35-7474-4f4d-a084-f4e9f993a5bf">

This should resolve #21856 🎉
2025-04-15 11:10:00 +01:00
lauren
08075929f2 [compiler] Init react-mcp-server (#32859)
Just trying this out as a small hack for fun. Nothing serious is
planned.

Inits an MCP server that has 1 assistant prompt and two capabilities.
2025-04-14 18:39:00 -04:00
lauren
4eea4fcf41 [compiler] Update rimraf (#32868)
Just updating the compiler workspace package.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32868).
* #32859
* __->__ #32868
2025-04-14 15:15:14 -04:00
lauren
58e9a4b74f Upgrade node.js to 20 LTS (#32855)
Try to upgrade our node version.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32855).
* #32861
* #32860
* #32859
* __->__ #32855
2025-04-14 12:52:02 -04:00
Jordan Eldredge
39cad7afc4 Remove redundant __DEV__ condition (#32810)
It used to be that in `__DEV__` we wrapped this `renderWithHooks`,
`checkDidRenderIdHook` pair in calls to `setIsRendering()`. However,
that dev-only bookkeeping was removed in
https://github.com/facebook/react/pull/29206 leaving this redundant
check which runs identical code in dev and in prod.

## Test Plan

* Manually confirm both cases are the same
* GitHub CI tests
2025-04-11 14:39:36 -07:00
Sebastian Markbåge
1d6c8168db Clear Update flag for unchanged trees in the beginning of the commit phase (#32849)
We use the Update flag to track if a View Transition had any mutations
or relayout. Unlike the other usage of it, this is just temporary state
during the commit phase.

Normally the flags gets used in the render phase and we reset it when we
rerender but in the case of "nested" updates, those trees didn't update.
We're only looking for relayouts. So we need to manually reset it before
we start using it.

We probably shouldn't abuse the Update flag for this and instead use
something like temporary state on ViewTransitionState.
2025-04-11 10:54:06 -04:00
Sebastian Markbåge
961b625ab5 Try not. Do... or do not. Hydrate Suspense Boundaries. (#32851)
Assertively claim a SuspenseInstance. We already know we're hydrating.

If there's no match, it throws anyway. So there's no other code path.
2025-04-11 10:52:23 -04:00
Sebastian Markbåge
8a3c5e1a8d Emit Preamble Contribution inline instead of the end of a boundary (#32850)
This lets us write them early in the render phase.

This should be safe because even if we write them deeply, then they
still can't be wrapped by a element because then they'd no longer be in
the document scope anymore. They end up flat in the body and so when we
search the content we'll discover them.
2025-04-10 19:42:03 -04:00
Rubén Norte
5e9b48778c [RN] Map Fabric priorities to reconciler priorities correctly (#32847)
## Summary

This fixes how we map priorities between Fabric and the React
reconciler. At the moment, we're only considering default and discrete
priorities, when there's a larger range of priorities available.

In Fabric, we'll test supporting additional priorities soon. For that
test to do something useful, we need the new priorities to be mapped to
reconciler priorities correctly, which is what this change is done.

> [!IMPORTANT]
> At the moment, this is a no-op because Fabric is only reporting
default and discrete event priorities.

## How did you test this change?

Will test e2e on React Native on top of
https://github.com/facebook/react-native/pull/50627

The changes are gated in React Native, so we'll use that feature flag to
test this.
2025-04-10 17:35:15 +01:00
Sebastian Markbåge
c44e4a2505 Move Built-in Props Types to React Types (#32841)
Stacked on #32838.

We don't always type the Props of built-ins. This adds typing for most
of the built-ins.

When we did type them, we used to put it in the `ReactFiber...Component`
files but any public API like this can be implemented in other renderers
too such as Fizz. So I moved them to `shared/ReactTypes` which is where
we put other public API types (that are not already built-in to Flow).
That way Fizz can import them and assert properly when it accesses the
props.
2025-04-09 22:44:44 -04:00
Sebastian Markbåge
31ecc9804a Clarify that there's three different kinds of OffscreenProps (#32838)
ActivityProps - Public API
LegacyHiddenProps - Public Legacy API
OffscreenProps - Internal implementation detail
2025-04-09 22:22:45 -04:00
lauren
ff697fc58b [eprh] Temporarily disable ref access in render validation (#32839)
This rule currently has a few false positives, so let's disable it for
now (just in the eslint rule, it's still enabled in the compiler) while
we iterate on it.
2025-04-09 14:49:31 -04:00
Sebastian "Sebbie" Silbermann
096dd7385d Send notification to Discord if automated publish fails (#32840) 2025-04-09 19:14:28 +02:00
michael faith
717584167b docs(eslint-plugin-react-hooks): add 6.0 documentation (#32513)
This change adds the details for using the 6.0+ version of the flat
recommended config.

Co-authored-by: lauren <poteto@users.noreply.github.com>
2025-04-09 12:42:23 -04:00
Sebastian Markbåge
3fbfb9baaf Emit Activity boundaries as comments in Fizz (#32834)
Uses `&` for Activity as opposed to `$` for Suspense. This will be used
to delimitate which nodes we can skip hydrating.

This isn't used on the client yet. It's just a noop on the client
because it's just an unknown comment. This just adds the SSR parts.
2025-04-09 10:59:52 -04:00
Andrew Clark
8571249eb8 Add unstable_Activity to server entrypoint (#32833)
Activity is a client component, but you should still be able to import
it and render it from a Server Component. Same as what we do with other
types like Suspense and ViewTransition.
2025-04-09 01:49:27 -04:00
Sebastian Markbåge
8da36d0508 Enable Suspensey Images inside <ViewTransition> subtrees (#32820)
Even if the `enableSuspenseyImages` flag is off.

Started View Transitions already wait for Suspensey Fonts and this is
another Suspensey feature that is even more important for View
Transitions - even though we eventually want it all the time. So this
uses `<ViewTransition>` as an early opt-in for that tree into Suspensey
Images, which we can ship in a minor.

If you're doing an update inside a ViewTransition then we're eligible to
start a ViewTransition in any Transition that might suspend. Even if
that doesn't end up animating after all, we still consider it Suspensey.
We could try to suspend inside the startViewTransition but that's not
how it would work with `enableSuspenseyImages` on and we can't do that
for startGestureTransition.

Even so we still need some opt-in to trigger the Suspense fallback even
before we know whether we'll animate or not. So the simple solution is
just that `<ViewTransition>` opts in the whole subtree into Suspensey
Images in general.

In this PR I disable `enableSuspenseyImages` in experimental so that we
can instead test the path that only enables it inside `<ViewTransition>`
tree since that's the path that would next graduate to a minor.
2025-04-08 17:55:15 -04:00
Sebastian Markbåge
ea05b750a5 Allow Passing Blob/File/MediaSource/MediaStream to src of <img>, <video> and <audio> (#32828)
Behind the `enableSrcObject` flag. This is revisiting a variant of what
was discussed in #11163.

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

There are a number of reason to revisit this.

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

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

We may support Response objects in the future too as per
https://github.com/whatwg/fetch/issues/49
2025-04-08 12:11:41 -04:00
Hendrik Liebau
3366146796 Fix Failed to execute 'measure' on 'Performance' error (#32823)
When `startTime` still has its initial value of `-1.1` we must not call
`logComponentMount`. This can occur when rendering a `'next/dynamic'`
component with `{ssr: false}` in a client component, for example.
Unfortunately, I didn't manage to reproduce this scenario in a unit
test.
2025-04-07 10:13:34 -04:00
Sebastian Markbåge
365c031fd2 Workaround against display: inline bug in Safari (#32822)
Safari has a bug where if you put a block element inside an inline
element and the inline element has a `view-transition-name` assigned it
finds it as duplicate names.

https://bugs.webkit.org/show_bug.cgi?id=290923

This adds a warning if we detect this scenario in dev mode.

For the case where it renders into a single block, we can model this by
making the parent either `block` or `inline-block` automatically to fix
the issue. So we do that to automatically cover simple cases like
`<a><div>...</div></a>`. This unfortunately causes layout/styling thrash
so we might want to delete it once the bug has been fixed in enough
Safari versions.
2025-04-07 10:08:07 -04:00
Jason Zhang
a9d63f3f97 fix: incorrect type in getTypeSymbol (#32825)
`getTypeSymbol` also returns string
2025-04-07 10:51:28 +01:00
Andrew Clark
6a7650c75c [Bugfix] Infinite uDV loop in popstate event (#32821)
Found a bug that occurs during a specific combination of very subtle
implementation details.

It occurs sometimes (not always) when 1) a transition is scheduled
during a popstate event, and 2) as a result, a new value is passed to an
already-mounted useDeferredValue hook.

The fix is relatively straightforward, and I found it almost
immediately; it took a bit longer to figure out exactly how the scenario
occurred in production and create a test case to simulate it.

Rather than couple the test to the implementation details, I've chosen
to keep it as high-level as possible so that it doesn't break if the
details change. In the future, it might not be trigger the exact set of
internal circumstances anymore, but it could be useful for catching
similar bugs because it represents a realistic real world situation —
namely, switching tabs repeatedly in an app that uses useDeferredValue.
2025-04-05 00:49:28 -04:00
Sebastian Markbåge
efb22d8850 Add Suspensey Images behind a Flag (#32819)
We've known we've wanted this for many years and most of the
implementation was already done for Suspensey CSS. This waits to commit
until images have decoded by default or up to 500ms timeout (same as
suspensey fonts).

It only applies to Transitions, Retries (Suspense), Gesture Transitions
(flag) and Idle (doesn't exist). Sync updates just commit immediately.

`<img loading="lazy" src="..." />` opts out since you explicitly want it
to load lazily in that case.

`<img onLoad={...} src="..." />` also opts out since that implies you're
ok with managing your own reveal.

In the future, we may add an opt in e.g. `<img blocking="render"
src="..." />` that opts into longer timeouts and re-suspends even sync
updates. Perhaps also triggering error boundaries on errors.

The rollout for this would have to go in a major and we may have to
relax the default timeout to not delay too much by default. However, we
can also make this part of `enableViewTransition` so that if you opt-in
by using View Transitions then those animations will suspend on images.
That we could ship in a minor.
2025-04-04 14:54:05 -04:00
Sebastian Markbåge
540cd65252 Log Mount/Unmount/Reconnect/Disconnect in the Component Track (#32816)
Stacked on #32815.

To be able to differentiate mounted subtrees from updated subtrees. This
adds a yellow entry above the component subtree that mounted. This is
added both to the render phase, mutation effect phase, layout effect
phase and passive effect phase.

<img width="962" alt="Screenshot 2025-04-03 at 10 41 02 PM"
src="https://github.com/user-attachments/assets/13777347-07e8-458c-9127-8675ef08b54f"
/>

Ideally we could probably give an annotation to the component instead of
adding a whole other line which is also a color that's kind of
distracting. However, not all components are included and keeping track
of which one is the first one below is kind of annoying. Adding a marker
to all components is kind of noisy. So this is a compromise. It's only
one per depth so it won't make it too deep even on larger trees.

If this is an unmount, those are added to the mutation effect phase for
the layout unmounts and passive unmount effect phase. Since these never
have a render, they're not in the render phase.

<img width="1010" alt="Screenshot 2025-04-03 at 11 05 57 PM"
src="https://github.com/user-attachments/assets/ab39f27e-13be-4281-94fa-9391bb293fd2"
/>

For showing / hiding `<Activity>` the terminology "Reconnect" and
"Disconnect" is used instead.
2025-04-03 23:33:29 -04:00
Sebastian Markbåge
c0f08ae74a Fix Bugs Measuring Performance Track for Effects (#32815)
This fixes two bugs with commit phase effect tracking.

I missed, or messed up the rebase for, deletion effects when a subtree
was deleted and for passive disconnects when a subtree was hidden.

The other bug is that when I started using self time
(componentEffectDuration) for color and for determining whether to
bother logging an entry, I didn't consider that the component with
effects can have children which end up resetting this duration before we
log. Which lead to most effects not having their components logged since
they almost always have children.

We don't necessarily have to push/pop but we have to store at least one
thing on the stack unfortunately. That's because we have to do the
actual log after the children to get the right end time. So might as
well use the push/pop strategy like the rest of them.
2025-04-03 23:33:14 -04:00
Sebastian Markbåge
b10cb4c01e [DevTools] Release and aquire host instances when they're cloned in persistent mode (#32812)
In persistent mode they can change when they're closned and so we need
to release the old copy and acquire the new copy.
2025-04-03 10:06:04 -04:00
Ruslan Lesiutin
f0c767e2a2 feat[devtools]: display native tag for host components for Native (#32762)
Native only. Displays the native tag for Native Host components inside a
badge, when user inspects the component.

Only displaying will be supported for now, because in order to get
native tags indexable, they should be part of the bridge operations,
which is technically a breaking change that requires significantly more
time investment.

The text will only be shown when user hovers over the badge.
![Screenshot 2025-03-26 at 19 46
40](https://github.com/user-attachments/assets/787530cf-c5e5-4b85-8e2a-15b006a3d783)
2025-04-02 22:44:38 +01:00
Sebastian Markbåge
b2f6365745 Minor Tweak to Performance Track (#32808)
Rename "Suspended" commit to "Suspended on CSS" since that's the only
reason for this particular branch. This will not hold true because with
suspended images and with view transitions those can also be the reason.
So in the future we need to add those.

Only log "Blocked" in the components track if we yield for 3ms or
longer. It's common to have like 1-2ms yield times for various reasons
going on which is not worth the noise to consider "blocking".

Rename "Blocked" to "Update" in the Blocking/Transition tracks. This is
when a setState happens and with stack traces it's where you should look
for the stack trace of the setState. So we want to indicate that this is
the "Update".

I only added the "Blocked" part if we're blocked for more than 5ms
before we can start rendering - indicating that some other track was
working at the same time and preventing us from rendering.
2025-04-02 17:01:10 -04:00
Matt Carroll
b81c92be62 Delete CHANGELOG-canary.md (#32807)
This is no longer being used or updated
2025-04-02 16:05:43 -04:00
Sebastian Markbåge
040f8286e9 Follow through all the phases when an error happens during snapshotting (#32803)
This can happen for example if you have duplicate names in the "old"
state. This errors the transition before the updateCallback is invoked
so we haven't yet applied mutations etc.

This runs through those phases after the error to get us back to a
consistent state.
2025-04-02 10:49:44 -04:00
Sebastian Markbåge
450f8df886 Clarify that the transitionLanes used by Transition Tracing is a LaneMap (#32800)
We have a high level concept for this used elsewhere.

We should use this for `transitionTypes` too:


https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactInternalTypes.js#L285

As mentioned in #32797 we could also just use the `transitionLanes`
since the `types` are also on the `Transition` objects. If we always
stored this set.
2025-04-01 18:18:33 -04:00
Sebastian Markbåge
7a728dffd1 Don't shadow EventListenerOptionsOrUseCapture and FocusOptions types (#32801)
These are built-in to Flow.
2025-04-01 14:22:57 -04:00
Sebastian Markbåge
e5dd82a79d Warn for using a React owned node as a Container if it also has text content (#32774)
The problem with setting both `children` or `dangerouslySetInnerHTML`
and also using a ref on a DOM node to either manually append children or
using it as a Container for `createRoot` or `createPortal` is that it's
ambiguous which children should win. Ideally you use one of the four
options to control children. Meaning that ideally you always use a leaf
container for refs like this.

Unfortunately it's very common to use a React owned thing with children
as a Container of a Portal. For example `document.body` can have both
regular React children and be used as a Portal container. This isn't
really fully supported and has some undefined behavior like relative
order isn't guaranteed but still very common.

It is extra bad if the children are a `string`/`number` or if
`dangerouslySetInnerHTML` is set. Because then when ever that reactively
updates it'll clear out any manually added DOM nodes. When this happens
isn't guaranteed. It's always happening as far as the reactivity is
concerned. See https://github.com/facebook/react/issues/31600

Therefore, we should warn for this specific pattern. This still allows
non-text children as a compromise even though that behavior is also
somewhat undefined.
2025-04-01 12:11:29 -04:00
Sebastian Markbåge
731ae3e0ad Solidify addTransitionType Semantics (#32797)
Stacked on #32793.

This is meant to model the intended semantics of `addTransitionType`
better. The previous hack just consumed all transition types when any
root committed so it could steal them from other roots. Really each root
should get its own set. Really each transition lane should get its own
set.

We can't implement the full ideal semantics yet because 1) we currently
entangle transition lanes 2) we lack `AsyncContext` on the client so for
async actions we can't associate a `addTransitionType` call to a
specific `startTransition`.

This starts by modeling Transition Types to be stored on the Transition
instance. Conceptually they belong to the Transition instance of that
`startTransition` they belong to. That instance is otherwise mostly just
used for Transition Tracing but it makes sense that those would be able
to be passed the Transition Types for that specific instance.

Nested `startTransition` need to get entangled. So that this
`addTransitionType` can be associated with the `setState`:

```js
startTransition(() => {
  startTransition(() => {
    addTransitionType(...)
  });
  setState(...);
});
```

Ideally we'd probably just use the same Transition instance itself since
these are conceptually all part of one entangled one. But transition
tracing uses multiple names and start times. Unclear what we want to do
with that. So I kept separate instances but shared `types` set.

Next I collect the types added during a `startTransition` to any root
scheduled with a Transition. This should really be collected one set per
Transition lane in a `LaneMap`. In fact, the information would already
be there if Transition Tracing was always enabled because it tracks all
Transition instances per lane. For now I just keep track of one set for
all Transition lanes. Maybe we should only add it if a `setState` was
done on this root in this particular `startTransition` call rather
having already scheduled any Transition earlier.

While async transitions are entangled, we don't know if there will be a
startTransition+setState on a new root in the future. Therefore, we
collect all transition types while this is happening and if a new root
gets startTransition+setState they get added to that root.

```js
startTransition(async () => {
  addTransitionType(...)
  await ...;
  setState(...);
});
```
2025-04-01 12:11:19 -04:00
Sebastian Markbåge
deca96520f Warn if addTransitionType is called when there are no pending Actions (#32793)
Stacked on #32792.

It's tricky to associate a specific `addTransitionType` call to a
specific `startTransition` call because we don't have `AsyncContext` in
browsers yet. However, we can keep track if there are any async
transitions running at all, and if not, warn. This should cover most
cases.

This also errors when inside a React render which might be a legit way
to associate a Transition Type to a specific render (e.g. based on props
changing) but we want to be a more conservative about allowing that yet.
If we wanted to support calling it in render, we might want to set which
Transition object is currently rendering but it's still tricky if the
render has `async function` components. So it might at least be
restricted to sync components (like Hooks).
2025-04-01 12:10:10 -04:00
Sebastian Markbåge
0b1a9e90c5 Support addTransitionType in startGestureTransition (#32792)
Stacked on #32788.

Normally we track `addTransitionType` globally because of the async gap
that can happen in Actions where we lack AsyncContext to associate it
with a particular Transition. This unfortunately also means it's
possible to call outside of `startTransition` which is something we want
to warn for.

We need to be able to distinguish whether `addTransitionType` is for a
regular Transition or a Gesture Transition though.

Since `startGestureTransition` is only synchronous we can track it
within that execution scope and move it to a separate set. Since we know
for sure which call owns it we can properly associate it with that
specific provider's `ScheduledGesture`.

This does not yet handle calling `addTransitionType` inside the render
phase of a gesture. That would currently still be associated with the
next Transition instead.
2025-04-01 12:08:55 -04:00
Sebastian Markbåge
8b2046d0ce Get rid of the directional gesture options (#32788)
Stacked on #32786.

`startGestureTransition` doesn't have a concept of two directions. It's
just a start and end range now.
2025-04-01 12:07:07 -04:00
Sebastian Markbåge
d20c2802b4 Adjust range start/end based on the duration and delay of the animation (#32790)
When different animations in a View Transition have different durations,
we shouldn't stretch them out to run the full range of swipe. Because
then they wouldn't line up the same way as when played using plain time.

This adjusts the range start/end to be what it would've been when played
by time. Except since we are playing animations in reverse, the
animation-delay is actually applied from the range end and then the
duration from there to get closer to the start.

Reverse the range if the original animation was reversed.

Interestingly, the range it takes can be adjusted by what is in the
viewport since if a long duration animation is excluded then everything
else adjusts too.

I left some todos too. We really should also handle if the original
animation has multiple iterations. Currently we only play those once.
2025-04-01 11:44:44 -04:00
Sebastian Markbåge
0a7cf20b22 Remove useSwipeTransition (#32786)
Stacked on #32785.

This is now replaced by `startGestureTransition` added in #32785.

I also renamed the flag from `enableSwipeTransition` to
`enableGestureTransition` to correspond to the new name.
2025-04-01 11:43:33 -04:00
Sebastian Markbåge
b286430c8a Add startGestureTransition API (#32785)
Stacked on #32783. This will replace [the `useSwipeTransition`
API](https://github.com/facebook/react/pull/32373).

Instead, of a special Hook, you can make updates to `useOptimistic`
Hooks within the `startGestureTransition` scope.

```
import {unstable_startGestureTransition as startGestureTransition} from 'react';

const cancel = startGestureTransition(timeline, () => {
  setOptimistic(...);
}, options);
```

There are some downsides to this like you can't define two directions as
once and there's no "standard" direction protocol. It's instead up to
libraries to come up with their own conventions (although we can suggest
some).

The convention is still that a gesture recognizer has two props `action`
and `gesture`. The `gesture` prop is a Gesture concept which now behaves
more like an Action but 1) it can't be async 2) it shouldn't have
side-effects. For example you can't call `setState()` in it except on
`useOptimistic` since those can be reverted if needed. The `action` is
invoked with whatever side-effects you want after the gesture fulfills.

This is isomorphic and not associated with a specific renderer nor root
so it's a bit more complicated.

To implement this I unify with the `ReactSharedInternal.T` property to
contain a regular Transition or a Gesture Transition (the `gesture`
field). The benefit of this unification means that every time we
override this based on some scope like entering `flushSync` we also
override the `startGestureTransition` scope. We just have to be careful
when we read it to check the `gesture` field to know which one it is.
(E.g. I error for setState / requestFormReset.)

The other thing that's unique is the `cancel` return value to know when
to stop the gesture. That cancellation is no longer associated with any
particular Hook. It's more associated with the scope of the
`startGestureTransition`. Since the schedule of whether a particular
gesture has rendered or committed is associated with a root, we need to
somehow associate any scheduled gestures with a root.

We could track which roots we update inside the scope but instead, I
went with a model where I check all the roots and see if there's a
scheduled gesture matching the timeline. This means that you could
"retain" a gesture across roots. Meaning this wouldn't cancel until both
are cancelled:

```
const cancelA = startGestureTransition(timeline, () => {
  setOptimisticOnRootA(...);
}, options);

const cancelB = startGestureTransition(timeline, () => {
  setOptimisticOnRootB(...);
}, options);
```

It's more like it's a global transition than associated with the roots
that were updated.

Optimistic updates mostly just work but I now associate them with a
specific "ScheduledGesture" instance since we can only render one at a
time and so if it's not the current one, we leave it for later.

Clean up of optimistic updates is now lazy rather than when we cancel.
Allowing the cancel closure not to have to be associated with each
particular update.
2025-03-31 20:05:50 -04:00
Sebastian Markbåge
d3b8ff6e58 Unify BatchConfigTransition and Transition types (#32783)
This is some overdue refactoring. The two types never made sense. It
also should be defined by isomorphic since it defines how it should be
used by renderers rather than isomorphic depending on Fiber.

Clean up hidden classes to be consistent.

Fix missing name due to wrong types. I choose not to invoke the
transition tracing callbacks if there's no name since the name is
required there.
2025-03-31 19:59:07 -04:00
Jack Pope
a7fa8702ee Remove v19 beta specific issue template (#32795)
This was a template for the 19 beta. Since 19 has been stable for a
while now, we can clean this up. Any bug report for React 19 should use
the standard bug report template.
2025-03-31 15:53:17 -04:00
Sebastian Markbåge
95671b4eb3 Mark the root as animating if any Portal mutates or resizes (#32772)
Portals and `<ViewTransition>` are tricky because they leave the React
tree. You might think of a Portal's container conceptually as also being
part of a React tree but that's not quite how they're modeled today.
They're more like their own roots. So instead, of trying to find a
conceptual place in the React tree we treat Portals as their own root.

We have two ways of tracking whether an update to a ViewTransition
boundary has occurred. Either a DOM mutation has happened within it, or
a resize of a child has caused it to potentially relayout its parent.
Normally that just follows the tree structure of React, but not when
it's a Portal.

When it's a Portal we don't know which DOM parent it might have
affected. For all we know it's at the root (and in fact, in most cases
that's where Portals go).

With this PR we mark the root as having been affected by a mutation or
resize. This means that the whole document will animate and we can't
optimize away from it. This ensures that a mutation to the root of a
Portal doesn't go unanimated with other things are animating such as its
parent.

You can regain this optimization by adding a `<ViewTransition>` boundary
directly inside the Portal itself so it owns its own animation. If that
DOM node is also absolutely positioned it doesn't leak.

Conversely this also means that a mutation inside a Portal doesn't
affect its React parent so it won't trigger its parent's animation if
this was the only thing animating. That could be unfortunate if this
container is actually inside the same React parent. However, because
this would have been an update we would've marked it for "maybe
animating" and updates can't only get their animations cancelled if the
root is cancelled, in practice this will actually animate anyway.
2025-03-31 15:13:11 -04:00
Matt Carroll
6377903074 Update 19.1 changelog to remove confusing owner stack sentance 2025-03-28 14:55:28 -07:00
Matt Carroll
095ce8a311 Fix changelog Owner Stack spelling consistency 2025-03-28 14:30:13 -07:00
Matt Carroll
18a11339c3 Update 19.1 changelog to add owner stack context 2025-03-28 14:02:11 -07:00
Matt Carroll
d726d692ed Add changelog for 19.1.0 (#32781) 2025-03-28 13:21:29 -07:00
lauren
50c5cdb653 Bump next prerelease version numbers (#32782)
Updates the version numbers in the prerelease channels.
2025-03-28 16:20:04 -04:00
mofeiZ
deb7859bb0 [compiler][snap] Fix test filter + watch mode (#32780)
Accidentally broke this when migrating our test runner to use the
bundled build https://github.com/facebook/react/pull/32758

The fix is pretty simple. File watcher should listen for changes in
`packages/babel-plugin-react-compiler` instead of `cwd`, which is now
`packages/snap`.
2025-03-28 16:03:08 -04:00
lauren
1825990c56 [release] Don't lookup build-info.json when updating version numbers (#32778)
From what we can see, `build-info.json` is a vestigal file that we were
previously including in builds but are no longer since 2022 (see
https://github.com/facebook/react/pull/23257, which removes
`build-info.json` which would have broken
scripts/release/build-release-locally-commands/add-build-info-json.js).

Since this file is no longer built, instead of looking it up we default
to the `version` that was passed in as an argument to
scripts/release/prepare-release-from-npm.js. Since `version` is what is
pulled from npm, there should only be 1 consistent version for all the
packages that are pulled. Therefore, only 1 version (eg canary) needs to
be replaced to the new stable version.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32778).
* __->__ #32778
* #32777
2025-03-28 14:35:29 -04:00
lauren
1de32a5e75 [release] Also split the onlyPackages param (#32777)
I missed this the last time.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32777).
* #32778
* __->__ #32777
2025-03-28 14:35:18 -04:00
Rodrigo Faria
ef4bc8b4f9 feat(babel-plugin-react-compiler): support satisfies operator (#32742)
Solve https://github.com/facebook/react/pull/29818

---------

Co-authored-by: Rodrigo Faria <rodrigo.faria@cartrack.com>
2025-03-28 11:10:32 -04:00
mofeiZ
8039f1b2a0 [compiler] Fix inferEffectDependencies lint false positives (#32769)
Currently, inferred effect dependencies are considered a
"compiler-required" feature. This means that untransformed callsites
should escalate to a build error.

`ValidateNoUntransformedReferences` iterates 'special effect' callsites
and checks that the compiler was able to successfully transform them.
Prior to this PR, this relied on checking the number of arguments passed
to this special effect.

This obviously doesn't work with `noEmit: true`, which is used for our
eslint plugin (this avoids mutating the babel program as other linters
run with the same ast). This PR adds a set of `babel.SourceLocation`s to
do best effort matching in this mode.
2025-03-27 12:18:50 -04:00
Sebastian Markbåge
4280563b04 Mark shouldStartViewTransition as true when there's an enter animation (#32764)
Typically we mark the name of things that might animate in the snapshot
phase. At the same time we track that should call startViewTransition
too. However, we don't do this for "enter" since they're only marked
later. Leading to having just an "enter" not to animate unless there's
at least another update too.

This tracks if there's a ViewTransitionComponent in the tree that
enters. Luckily we know that from the static flag so we don't have to
traverse it.
2025-03-26 18:12:59 -04:00
Ricky
3e88e97c11 s/HTML/text for text hydration mismatches (#32763) 2025-03-26 17:39:52 -04:00
Sebastian Markbåge
f134b3993a Add getComputedStyle helper to ViewTransition refs (#32751)
This is also sometimes useful to read the style of the pseudo-element
itself without an animation.
2025-03-26 15:02:53 -04:00
Sebastian Markbåge
fceb0f80bc Add "auto" class to mean the built-in should run (#32761)
Stacked on https://github.com/facebook/react/pull/32734

In React a ViewTransition class of `"none"` doesn't just mean that it
has no class but also that it has no ViewTransition name. The default
(`null | undefined`) means that it has no specific class but should run
with the default built-in animation. This adds this as an explicit
string called `"auto"` as well.

That way you can do `<ViewTransition default="foo" enter="auto">` to
override the "foo" just for the "enter" trigger to be the default
built-in animation. Where as if you just specified `null` it would be
like not specifying enter at all which would trigger "foo".
2025-03-26 15:02:43 -04:00
Sebastian Markbåge
e0c99c4ea1 Rename <ViewTransition className="..."> to <ViewTransition default="..."> (#32734)
It was always confusing that this is not a CSS class but a
view-transition-class.

The `className` sticks out a bit among its siblings `enter`, `exit`,
`update` and `share`. The idea is that the most specific definition
override is the class name that gets applied and this prop is really
just the fallback, catch-all or "any" that is applied if you didn't
specify a more specific one.

It has also since evolved not just to take a string but also a map of
Transition Type to strings.

The "class" is really the type of the value. We could add a suffix to
all of them like `defaultClass`, `enterClass`, `exitClass`,
`updateClass` and `shareClass`. However, this doesn't necessarily make
sense with the mapping of Transition Type to string. It also makes it a
bit too DOM centric. In React Native this might still be called a
"class" but it might be represented by an object definition. We might
even allow some kind of inline style form for the DOM too. Really this
is about picking which "animation" that runs which can be a string or
instance. "Animation" is too broad because there's also a concept of a
CSS Animation and these are really sets of CSS animations (group,
image-pair, old, new). It could maybe be `defaultTransition`,
`enterTransition`, etc but that seems unnecessarily repetitive and still
doesn't say anything about it being a class.

We also already have the name "default" in the map of Transition Types.
In fact you can now specify a default for default:

```
<ViewTransition default={{"navigation-back": "slide-out", "default": "fade-in"}}>
```

One thing I don't like about the name `"default"` is that it might be
common to just apply a named class that does it matching to
enter/exit/update in the CSS selectors (such as the `:only-child` rule)
instead of doing that mapping to each one using React. In that can you
end up specifying only `default={...}` a lot and then what is it the
"default" for? It's more like "all". I think it's likely that you end up
with either "default" or the specific forms instead of both at once.
2025-03-26 15:02:05 -04:00
Sebastian Markbåge
a5297ece62 Don't flush synchronous work if we're in the middle of a ViewTransition async sequence (#32760)
Starting a View Transition is an async sequence. Since React can get a
sync update in the middle of sequence we sometimes interrupt that
sequence.

Currently, we don't actually cancel the View Transition so it can just
run as a partial. This ensures that we fully skip it when that happens,
as well as warn.

However, it's very easy to trigger this with just a setState in
useLayoutEffect right now. Therefore if we're inside the preparing
sequence of a startViewTransition, this delays work that would've
normally flushed in a microtask. ~Maybe we want to do the same for
Default work already scheduled through a scheduler Task.~ Edit: This was
already done.

`flushSync` currently will still lead to an interrupted View Transition
(with a warning). There's a tradeoff here whether we want to try our
best to preserve the guarantees of `flushSync` or favor the animation.
It's already possible to suspend at the root with `flushSync` which
means it's not always 100% guaranteed to commit anyway. We could treat
it as suspended. But let's see how much this is a problem in practice.
2025-03-26 14:40:23 -04:00
mofeiZ
254114616a [compiler][be] Playground now uses tsup bundled plugin (#32759)
Followup to https://github.com/facebook/react/pull/32758.

This moves playground to use the tsup bundled plugin instead of
webpack-built `babel-plugin-react-compiler`.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32759).
* __->__ #32759
* #32758
2025-03-26 14:31:20 -04:00
mofeiZ
33999c4317 [compiler][be] Test runner (snap) now uses tsup bundled plugin (#32758)
Currently, `babel-plugin-react-compiler` is bundled with (almost) all
external dependencies. This is because babel traversal and ast logic is
not forward-compatible. Since `babel-plugin-react-compiler` needs to be
compatible with babel pipelines across a wide semvar range, we (1) set
this package's babel dependency to an early version and (2) inline babel
libraries into our bundle.

A few other packages in `react/compiler` depend on the compiler. This PR
moves `snap`, our test fixture compiler and evaluator, to use the
bundled version of `babel-plugin-react-compiler`. This decouples the
babel version used by `snap` with the version used by
`babel-plugin-react-compiler`, which means that `snap` now can test
features from newer babel versions (see
https://github.com/facebook/react/pull/32742).

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32758).
* #32759
* __->__ #32758
2025-03-26 14:27:42 -04:00
lauren
5f232d72d4 [ci] Skip yarn install on cache hit (#32757)
We currently already do this in runtime_build_and_test, we can reuse the
same technique in other workflows to speed them up.
2025-03-26 13:13:39 -04:00
lauren
313332d111 [crud] Revert CRUD overload (#32741)
Cleans up this experiment. After some internal experimentation we are
deprioritizing this project for now and may revisit it at a later point.
2025-03-26 12:04:57 -04:00
Ricky
f99c9feaf7 Fix ownerStackLimit feature gating for tests (#32726)
https://github.com/facebook/react/pull/32529 added a dynamic flag for
this, but that breaks tests since the flags are not defined everywhere.

However, this is a static value and the flag is only for supporting
existing tests. So we can override it in the test config, and make it
static at built time instead.
2025-03-26 12:01:05 -04:00
Sebastian Markbåge
8ac25e5201 Warn for duplicate ViewTransition names (#32752)
This adds early logging when two ViewTransitions with the same name are
mounted at the same time. Whether they're part of a View Transition or
not.

This lets us include the owner stack of each one. I do two logs so that
you can get the stack trace of each one of the duplicates.

It currently only logs once for each name which also avoids the scenario
when you have many hits for the same name in one commit. However, we
could also possibly log a stack for each of them but seems noisy.

Currently we don't log if a SwipeTransition is the first time the pair
gets mounted which could lead to a View Transition error before we've
warned. That could be a separate improvement.
2025-03-25 22:03:05 -04:00
Sebastian Markbåge
f9e1b16098 Avoid double logging component render time (#32749)
This got moved into the functional component and class component case
statements here:
0de1233fd1.
So that we could separate the error case for class components.

However, due to a faulty rebase this got restored at the top as well.
Leading to double component renders being logged.

In the other offscreen reconnect passes we don't do this in each case
statement but still once at the top. The reason this doesn't matter is
because use the PerformedWork flag and that is only set for function and
class components. Although maybe it should be set for expensive DOM
components too and then we have to remember this.
2025-03-25 20:57:20 -04:00
lauren
4845e16c22 [ci] Fix param casing (#32748)
Casing was incorrect.

Tested by running locally with a PAT.

```
$ scripts/release/download-experimental-build.js --commit=2d40460cf768071d3a70b4cdc16075d23ca1ff25
Command failed: gh attestation verify artifacts_combined.zip --repo=facebook/react

Error: failed to fetch attestations from facebook/react: HTTP 404: Not Found (https://api.github.com/repos/facebook/react/attestations/sha256:23d05644f9e49e02cbb441e3932cc4366b261826e58ce222ea249a6b786f0b5f?per_page=30)
`gh attestation verify artifacts_combined.zip --repo=facebook/react` (exited with error code 1)

$ scripts/release/download-experimental-build.js --commit=2d40460cf768071d3a70b4cdc16075d23ca1ff25 --noVerify
⠼ Downloading artifacts from GitHub for commit 2d40460cf7)  5%                  0.1m, estimated 1.6m
✓ Downloading artifacts from GitHub for commit 2d40460cf7) 9.5 secs
An experimental build has been downloaded!

You can download this build again by running:
  scripts/download-experimental-build.js --commit=2d40460cf768071d3a70b4cdc16075d23ca1ff25
```
2025-03-25 16:05:41 -04:00
lauren
553a175c90 [ci] Fix incorrect condition (#32746)
Oops, missed this when I switched it from checking if its a fork to
checking if its from this repo.
2025-03-25 14:37:51 -04:00
lauren
740a4f7a02 [ci] Run stale cache cleanup every 6 hours (#32739)
Run this a bit more frequently so we don't thrash `main` caches as
often.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32739).
* __->__ #32739
* #32738
2025-03-25 11:16:40 -04:00
lauren
44c4693539 [ci] Dont sign builds originating from anything other than facebook/react (#32738)
We now generate attestations in `process_artifacts_combined` so we can
verify the provenance of the build later in other workflows. However,
this requires `write` permissions for `id-token` and `attestations` so
PRs from forks cannot generate this attestation.

To get around this, I added a `--no-verify` flag to
scripts/release/download-experimental-build.js. This flag is only passed
in `runtime_build_and_test.yml` for the sizebot job, since 1) the
workflow runs in the `pull_request` trigger which has read-only
permissions, and 2) the downloaded artifact is only used for sizebot
calculation, and not actually used.

The flag is explicitly not passed in `runtime_commit_artifacts.yml`
since there we actually use the artifact internally. This is fine as
once a PR lands on main, it will then run the build on that new commit
and generate an attestation.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32738).
* #32739
* __->__ #32738
2025-03-25 11:16:19 -04:00
MU AOHUA
dc9b74647e [DevTools] Add fb local build command (#32644)
<!--
  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
1. Having a development build for FB will be convenient for fb internal
feature development
2. Add a new checkbox to toggle new internal features added to React
Devtools.

## How did you test this change?
1. yarn test
2. set extra env variables in bash profile and build an internal version
with the new script.
3. toggle on/off the new checkbox, the value is stored in local storage
correctly.

---------

Co-authored-by: Aohua Mu <muaohua@fb.com>
2025-03-25 13:45:48 +00:00
Sam Zhou
b59f186011 [flow] Replace $PropertyType with indexed access type in ReactNativeTypes (#32733) 2025-03-24 23:00:47 -04:00
lauren
e5f275e72a [ci] Pass GH_TOKEN to runtime_prereleases (#32730)
Seems like this also needs to be specified.

Note: #32732 needs to land first.
2025-03-24 19:08:41 -04:00
lauren
1cdf6b9590 [ci] Add GH_TOKEN as secret input to prereleases (#32732)
Seems like this also needs to be specified
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32732).
* #32730
* __->__ #32732
2025-03-24 18:59:17 -04:00
lauren
ee0855f427 [ci] Fix missing permissions for prereleases (#32729)
Missed these earlier.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32729).
* __->__ #32729
* #32728
2025-03-24 18:24:45 -04:00
lauren
7e4c258e16 [scripts] Verify artifact integrity when downloading (#32728)
Uses https://cli.github.com/manual/gh_attestation_verify to verify that
the downloaded artifact matches the attestation generated during the
build process in runtime_commit_artifacts.

Example:

On a workflow run of runtime_build_and_test.yml with no attestations:
```
$ scripts/release/download-experimental-build.js --commit=ea5f065745b777cb41cc9e54a3b29ed8c727a574

Command failed: gh attestation verify artifacts_combined.zip --repo=facebook/react

Error: failed to fetch attestations from facebook/react: HTTP 404: Not Found (https://api.github.com/repos/facebook/react/attestations/sha256:7adba0992ba477a927aad5a07f95ee2deb7d18427c84279d33fc40a3bc28ebaa?per_page=30)
`gh attestation verify artifacts_combined.zip --repo=facebook/react` (exited with error code 1)
```

On one which does:

```
$ scripts/release/download-experimental-build.js --commit=12e85d74c1c233cdc2f3228a97473a4435d50c3b

✓ Downloading artifacts from GitHub for commit 12e85d74c1) 10.5 secs
An experimental build has been downloaded!

You can download this build again by running:
  scripts/download-experimental-build.js --commit=12e85d74c1c233cdc2f3228a97473a4435d50c3b
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32728).
* #32729
* __->__ #32728
2025-03-24 18:24:33 -04:00
lauren
07276b8682 [ci] Add artifact attestation to build (#32711)
Adds a signed build provenance attestations via
https://github.com/actions/attest-build-provenance
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32711).
* #32729
* #32728
* __->__ #32711
2025-03-24 18:13:36 -04:00
lauren
ea5f065745 [ci] Make maintainer check always remote (#32727)
To prevent local modification of the MAINTAINERS file we now always
fetch from `main` instead.
2025-03-24 16:40:14 -04:00
Ricky
2d40460cf7 [ci] fix notify/label actions for forks (#32725)
Need this to run against target for forks to get the notification.

This job does not checkout the code in the PR, so it's safe to run from
the target.

Also fixes failing checks on PRs:

<img width="870" alt="Screenshot 2025-03-24 at 3 28 30 PM"
src="https://github.com/user-attachments/assets/add78287-6449-4e48-9376-f3b360d2607c"
/>
2025-03-24 15:46:59 -04:00
mofeiZ
254dc4d9f3 [compiler][bugfix] Fix hoisting of let declarations (#32724)
(Found when compiling Meta React code)

Let variable declarations and reassignments are currently rewritten to
`StoreLocal <varName>` instructions, which each translates to a new
`const varName` declaration in codegen.

```js
// Example input
function useHook() {
  const getX = () => x;
  let x = CONSTANT1;
  if (cond) {
    x += CONSTANT2;
  }
  return <Stringify getX={getX} />
}

// Compiled output, prior to this PR
import { c as _c } from "react/compiler-runtime";
function useHook() {
  const $ = _c(1);
  let t0;
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
    const getX = () => x;
    let x = CONSTANT1;
    if (cond) {
      let x = x + CONSTANT2;
      x;
    }

    t0 = <Stringify getX={getX} />;
    $[0] = t0;
  } else {
    t0 = $[0];
  }
  return t0;
}
```

This also manifests as a babel internal error when replacing the
original function declaration with the compiler output. The below
compilation output fails with `Duplicate declaration "x" (This is an
error on an internal node. Probably an internal error.)`.
```js
// example input
let x = CONSTANT1;
if (cond) {
  x += CONSTANT2;
  x = CONSTANT3;
}

// current output
let x = CONSTANT1;
if (playheadDragState) {
  let x = x + CONSTANT2
  x;
  let x = CONSTANT3;
}
```
2025-03-24 14:30:17 -04:00
Sebastian Markbåge
42a57ea802 Merge ViewTransition layout/onLayout props into update/onUpdate (#32723)
We currently have the ability to have a separate animation for a
ViewTransition that relayouts but doesn't actually have any internal
mutations. This can be useful if you want to separate just a move from
for example flashing an update.

However, we're concerned that this might be more confusion than its
worth because subtle differences in mutations can cause it to trigger
the other case. The existence of the property name might also make you
start looking for it to solve something that it's not meant for.

We already fallback to using the "update" property if it exists but
layout doesn't. So if we ever decide to add this back it would backwards
compatible. We've also shown in implementation that it can work.
2025-03-24 14:04:27 -04:00
Jack Pope
04bf10e6a9 Add getRootNode to fragment instances (#32682)
This implements `getRootNode(options)` on fragment instances as the
equivalent of calling `getRootNode` on the fragment's parent host node.

The parent host instance will also be used to proxy dispatchEvent in an
upcoming PR.
2025-03-24 10:19:55 -04:00
mofeiZ
c61e75b76d [compiler] Avoid failing builds when import specifiers conflict or shadow vars (#32663)
Avoid failing builds when imported function specifiers conflict by using
babel's `generateUid`. Failing a build is very disruptive, as it usually
presents to developers similar to a javascript parse error.
```js
import {logRender as _logRender} from 'instrument-runtime';

const logRender = () => { /* local conflicting implementation */ }

function Component_optimized() {
  _logRender(); // inserted by compiler
}
```

Currently, we fail builds (even in `panicThreshold:none` cases) when
import specifiers are detected to conflict with existing local
variables. The reason we destructively throw (instead of bailing out) is
because (1) we first generate identifier references to the conflicting
name in compiled functions, (2) replaced original functions with
compiled functions, and then (3) finally check for conflicts.

When we finally check for conflicts, it's too late to bail out.
```js
// import {logRender} from 'instrument-runtime';

const logRender = () => { /* local conflicting implementation */ }

function Component_optimized() {
  logRender(); // inserted by compiler
}
```
2025-03-24 09:31:51 -04:00
mofeiZ
7c908bcf4e [compiler][optim] Add Effect.ConditionallyMutateIterator (#32698)
Adds Effect.ConditionallyMutateIterator, which has the following
effects:
- capture for known array, map, and sets
- mutate for all other values

An alternative to this approach could be to add polymorphic shape
definitions
2025-03-23 23:25:55 -04:00
mofeiZ
a8e503dce0 [compiler][optim] Add map and set constructors (#32697)
* Adds `isConstructor: boolean` to `FunctionType`. With this PR, each
typed function can either be a constructor (currently only known
globals) or non constructor. Alternatively, we prefer to encode
polymorphic types / effects (and match the closest subtype)

* Add Map and Set globals + built-ins
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32697).
* #32698
* __->__ #32697
2025-03-23 23:19:01 -04:00
mofeiZ
45463ab3ac [compiler][be] Refactor similar CallExpression and MethodCall effect handling (#32696)
Simplify InferReferenceEffect function signature matching logic for next
PRs in stack
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32696).
* #32698
* #32697
* __->__ #32696
* #32695
2025-03-23 23:07:49 -04:00
mofeiZ
febc09b480 [compiler][fix] mutableOnlyIfOperandsAreMutable does not apply when operands are globals (#32695)
Globals, module locals, and other locally defined functions may mutate
their arguments. See test fixtures for details
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32695).
* #32698
* #32697
* #32696
* __->__ #32695
2025-03-23 23:07:24 -04:00
Sebastian "Sebbie" Silbermann
4a9df08157 Stop creating Owner Stacks if many have been created recently (#32529)
Co-authored-by: Jack Pope <jackpope1@gmail.com>
2025-03-23 15:47:03 -07:00
mofeiZ
da996a15be [compiler][be] Move e2e tests to BabelPlugin transformer (#32706)
Clean up jest-e2e setup since
https://github.com/facebook/react/pull/32663 and other features need
program context (e.g. changing imports)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32706).
* #32663
* __->__ #32706
2025-03-21 20:05:22 -04:00
Dimitri POSTOLOV
6b1a2c1d81 fix(react-compiler): optimize components declared with arrow function and implicit return and compilationMode: 'infer' (#31792)
fixes https://github.com/facebook/react/issues/31601
https://github.com/facebook/react/issues/31639 cc @josephsavona
2025-03-21 16:46:02 -07:00
lauren
de4aad5ba6 [ci] Add missing permissions to runtime_commit_artifacts.yml (#32710)
Turns out we need permissions to write to `contents` after all.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32710).
* #32711
* __->__ #32710
2025-03-21 18:01:02 -04:00
lauren
156f0eca20 [ci] Don't use pull_request_target (#32708)
`pull_request_target` gives access to repository secrets and permissions
for use from forks, for example to add a comment.

> Due to the dangers inherent to automatic processing of PRs, GitHub’s
standard pull_request workflow trigger by default prevents write
permissions and secrets access to the target repository. However, in
some scenarios such access is needed to properly process the PR. To this
end the pull_request_target workflow trigger was introduced.

> The reason to introduce the pull_request_target trigger was to enable
workflows to label PRs (e.g. needs review) or to comment on the PR.

(via
https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/)

In this case there is no reason for us to allow this, so let's just use
the normal `pull_request` trigger which is less permissive.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32708).
* __->__ #32708
* #32709
2025-03-21 16:17:28 -04:00
lauren
4f080e498c [ci] Also give permissions on pull_requests (#32709)
Missed one
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32709).
* #32708
* __->__ #32709
2025-03-21 16:17:15 -04:00
lauren
fe8c10695c [ci] Add missing permissions (#32707)
Missed these ones earlier.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32707).
* #32708
* __->__ #32707
2025-03-21 16:07:09 -04:00
Ricky
daee08562c [activity] remove ref for now (#32645)
Followup from https://github.com/facebook/react/pull/32499

Manual mode is unused and has some bugs such as revealing hidden
boundaries when manually toggling. We also want to change how manual
mode works, and do some refactors to Activity to make it easier to
support. For now we'll remove it, then add it back after the other
changes we have planned.
2025-03-21 14:44:02 -04:00
lauren
ab693a926f [ci] Scope permissions for all workflows (#32704) 2025-03-21 14:40:55 -04:00
lauren
607615f4f6 [ci] Scope permissions for runtime_commit_artifacts.yml (#32701) 2025-03-21 14:40:34 -04:00
Sebastian Markbåge
e1e740717b Force layout before startViewTransition (#32699)
This works around this Safari bug.
https://bugs.webkit.org/show_bug.cgi?id=290146

This unfortunate because it may cause additional layouts if there's more
updates to the tree coming by manual mutation before it gets painted
naturally. However, we might end up wanting to read layout early anyway.

This affects the fixture because we clone the `<link>` from the `<head>`
which is itself another bug. However, it should be possible to have
`<link>` tags inserted into the new tree so this is still relevant.
2025-03-21 10:05:31 -04:00
lauren
ac799e569d [ci] Bump number of shards for test_build to 10 (#32693)
I noticed `test_build` can take a while so let's bump the number of
shards
2025-03-20 17:49:36 -04:00
mofeiZ
0962f684a0 [compiler][bugfix] Don't insert hook guards in retry pipeline (#32665)
Fixing bug from https://github.com/facebook/react/pull/32164 -- prior to
this PR, we inserted hook guards even for functions that bailed out of
compilation.
2025-03-20 17:25:08 -04:00
lauren
b888986054 [ci] Rename other stale branch workflow (#32692)
Makes it easier to tell what is what
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32692).
* __->__ #32692
* #32691
2025-03-20 17:19:47 -04:00
lauren
addce2f9f2 [ci] Add daily stale branch cache cleanup (#32691)
Cleans up stale non-main caches daily
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32691).
* #32692
* __->__ #32691
2025-03-20 17:17:44 -04:00
lauren
74bcf3d0d2 [ci] Don't fail on cache miss (#32690)
Partially reverts #32686.

PR caches inherit from caches generated in `main`. If it cannot find
that cache, it will create one scoped to just that PR (and PRs that
inherit from it).

There is an edge case where cache eviction can happen in the middle of a
test run. If cache eviction removes a `main` cache, child jobs that
depend on it will start failing because of the `fail-on-cache-miss`
setting.

This PR reverts the default behavior. If this happens, the workflow will
still continue in slow mode where it will `yarn install` child jobs
instead of reusing from cache. This is slower but will at least allow
workflows to continue.

Additionally I added restore keys so that we can fallback to other
caches if present so `yarn install` doesn't need to start over from
scratch.
2025-03-20 17:02:39 -04:00
Ricky
b630219b13 [refactor] move isValidElementType to react-is (#32518) 2025-03-20 16:51:33 -04:00
Jack Pope
7943da1e81 Set accurate value for alwaysThrottleRetries on www (#32684)
This flag value was updated in
https://github.com/facebook/react/pull/28965 (seemingly unrelated, maybe
as part of unit testing). But its still controlled by a dynamic flag in
www. Let's update this to VARIANT to accurately represent the state of
the rollout.

Before:
<img width="1340" alt="Screenshot 2025-03-20 at 10 45 30 AM"
src="https://github.com/user-attachments/assets/d0405a36-eb71-4108-9e23-8d462cc68fb4"
/>

After:
<img width="1351" alt="Screenshot 2025-03-20 at 10 45 11 AM"
src="https://github.com/user-attachments/assets/459d260d-7a25-430b-95a6-d6a91d958417"
/>
2025-03-20 14:28:55 -04:00
Joe Savona
e3c06424ae [compiler] Refactor validations to return Result and log where appropriate
Updates ~all of our validations to return a Result, and then updates callers to either unwrap() if they should bailout or else just log.

ghstack-source-id: 418b5f5aa2b7dd49ca76b3f98a48a35150691d7e
Pull Request resolved: https://github.com/facebook/react/pull/32688
2025-03-20 11:02:02 -07:00
Joe Savona
5f4c5c920f [compiler] Validate static components
React uses function identity to determine whether a given JSX expression represents the same type of component and should reconcile (keep state, update props) or replace (teardown state, create a new instance). This PR adds off-by-default validation to check that developers are not dynamically creating components during render.

The check is local and intentionally conservative. We specifically look for the results of call expressions, new expressions, or function expressions that are then used directly (or aliased) as a JSX tag. This allows common sketchy but fine-in-practice cases like passing a reference to a component from a parent as props, but catches very obvious mistakes such as:

```js
function Example() {
  const Component = createComponent();
  return <Component />;
}
```

We could expand this to catch more cases, but this seems like a reasonable starting point. Note that I tried enabling the validation by default and the only fixtures that error are the new ones added here. I'll also test this internally. What i'm imagining is that we enable this in the linter but not the compiler.

ghstack-source-id: e7408c0a55478b40d65489703d209e8fa7205e45
Pull Request resolved: https://github.com/facebook/react/pull/32683
2025-03-20 11:02:02 -07:00
lauren
112224d8d2 [ci] Also cache playground playwright browsers (#32687)
Following #32678, do the same for the playground e2e test since this
step can sometimes take many minutes to complete.
2025-03-20 13:53:31 -04:00
lauren
87d7e4c55b [ci] Fail on cache miss (#32686)
Since we use a centralized cache we should fail subsequent steps if the
child jobs are unable to restore the cache from the first 2 jobs.

Also fix some incorrect hashes used for the fixture tests.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32686).
* __->__ #32686
* #32685
2025-03-20 12:22:06 -04:00
lauren
3bcf8c23de [ci] Warm cache (#32685)
Try restoring from old caches as a base to speed up the case where
node_modules needs updating.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32685).
* #32686
* __->__ #32685
2025-03-20 12:21:50 -04:00
Sebastian Markbåge
a4f9bd586b Enable Fragment refs in Experimental (#32670)
That we can test it out in Next.js router conditionally when
experimental is on for other reasons.
2025-03-19 20:38:27 -04:00
lauren
ff8f6f21f7 [ci] Fix Will commit these changes www step (#32681)
Unlike the fbsource version of the step, www doesn't add any changes so
the `force` input doesn't actually work
2025-03-19 18:13:06 -04:00
lauren
19176e3c08 [ci] Use correct revision for Meta builds (#32680)
There was a bug previously in our commit artifacts step where the
emitted REVISION hash would reference the commit on the builds branch
rather than from `main`.

Given that our internal manual sync script also does this, let's align
them both to always reference the commit from `main` instead.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32680).
* __->__ #32680
* #32679
* #32678
2025-03-19 17:24:43 -04:00
lauren
d16c26da40 [ci] Specify if-no-files-found on actions/upload-artifact@v4 (#32679)
Defaults to warn, but since some steps require these artifacts to be
uploaded we specify an error if its not found. Some other steps like
playwright test-results are only uploaded on failure so it's okay to
ignore.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32679).
* #32680
* __->__ #32679
* #32678
2025-03-19 17:22:40 -04:00
lauren
a8c155cab9 [ci] Cache playwright browsers (#32678)
No reason to download them from scratch every time.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32678).
* #32680
* #32679
* __->__ #32678
2025-03-19 17:22:17 -04:00
lauren
995410463a [ci] Parameterize branch cleanup (#32677)
Allow a PR number to be passed as input
2025-03-19 16:39:01 -04:00
lauren
208905257f [ci] Add cache cleanup workflow (#32675)
> Caches have branch scope restriction in place. This means that if
caches for a specific branch are using a lot of storage quota, it may
result into more frequently used caches from default branch getting
thrashed. For example, if there are many pull requests happening on a
repo and are creating caches, these cannot be used in default branch
scope but will still occupy a lot of space till they get cleaned up by
eviction policy. But sometime we want to clean them up on a faster
cadence so as to ensure default branch is not thrashing.


https://github.com/actions/cache/blob/main/tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32675).
* __->__ #32675
* #32674
2025-03-19 15:42:04 -04:00
lauren
891a6332e3 [ci] Bump build_and_lint to 25 workers (#32674)
Increases number of workers for `build_and_lint`.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32674).
* #32675
* __->__ #32674
2025-03-19 15:41:48 -04:00
lauren
a4842c92ea [ci] Centralize cache (#32672)
To avoid race conditions where multiple jobs try to write to the same
cache, we now centralize saving the cache and then reusing it in every
subsequent job.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32672).
* #32675
* #32674
* __->__ #32672
2025-03-19 15:25:07 -04:00
Niklas Holm
ada8bbbd6a [eslint-plugin-react-compiler] Fix type error with recommended config (#32666)
## Summary

In the recommended configuration for `eslint-plugin-react-compiler`,
i.e. `reactCompiler.configs.recommended`, the rule is typed as `string`
rather than `eslint.Linter.RuleEntry` or anything assignable thereto,
which results in the following type error if you type check your eslint
configuration:
```
Property ''react-compiler/react-compiler'' is incompatible with index signature.
  Type 'string' is not assignable to type 'RuleEntry | undefined'.
```
Simply adding a const assertion fixes the error.

## How did you test this change?

I emitted declarations for the module and confirmed that the rule is now
typed as the string literal `'error'`
2025-03-19 14:58:08 -04:00
lauren
e9c3b27b4b [ci] Bump all node_modules cache keys (#32671)
I'm seeing a lot of instances of

> Failed to save: Unable to reserve cache with key
runtime-and-compiler-node_modules-v5-X64-Linux-e454609794aae66da9909c77dd6efa073eceff7f44d6527611f8465e102578b4,
another job may be creating this cache.

which is adding ~20 seconds to every step. Let's try to bust the cache
following this
[comment](https://github.com/actions/cache/issues/485#issuecomment-744145040)
and see if that helps.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32671).
* #32672
* __->__ #32671
2025-03-19 14:42:45 -04:00
lauren
b0446ff06a [ci] Properly format commit message take 2 (#32673)
We need to use the commit message from `main`, not the builds branch
2025-03-19 14:01:49 -04:00
lauren
db7dfe0550 [eprh] Don't transpile to es5 (#32669)
Now that we've moved the sync location of the plugin, we no longer need
this since those restrictions no longer apply.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32669).
* __->__ #32669
* #32668
2025-03-19 11:49:42 -04:00
lauren
646835fb59 [ci] Properly format commit message (#32668)
Using the github variable for the commit message replaces the variable
inline. If the commit message contains quotes or other characters that
need to be escaped, this breaks the workflow.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32668).
* #32669
* __->__ #32668
2025-03-19 11:49:18 -04:00
Sebastian Markbåge
c2a1961747 Minor Fixes to View Transition Fixture (#32664)
Follow up to #32656.

Remove touchAction from SwipeRecognizer. I was under the wrong
impression that this was only the touch-action applied to this
particular element, but that parents would still win but in fact this
blocks the parent from scrolling in the other direction. By specifying a
fixed direction it also blocked rage-swiping in the other direction
early on.

Disable pointer-events on view-transition so that the scroll can be hit.
This means that touches hit below the items animating above. This allows
swiping to happen again before momentum scroll has finished. Previously
they were ignored. This only works as long as the SwipeRecognizer is
itself not animating. This means you can now rage-swipe in both
directions quickly.
2025-03-18 19:20:34 -04:00
mofeiZ
6584a6eec4 [compiler] Hoist dependencies from functions more conservatively (#32616)
Alternative to facebook/react#31584 which sets
enableTreatFunctionDepsAsConditional:true` by default.

This PR changes dependency hoisting to be more conservative while trying
to preserve an optimal "happy path". We assume that a function "is
likely called" if we observe the following in the react function body.

- a direct callsite
- passed directly as a jsx attribute or child
- passed directly to a hook
- a direct return

A function is also "likely called" if it is directly called, passed to
jsx / hooks, or returned from another function that "is likely called".

Note that this approach marks the function definition site with its
hoistable properties (not its use site). I tried implementing use-site
hoisting semantics, but it felt both unpredictable (i.e. as a developer,
I can't trust that callbacks are well memoized) and not helpful (type +
null checks of a value are usually colocated with their use site)

In this fixture (copied here for easy reference), it should be safe to
use `a.value` and `b.value` as dependencies, even though these functions
are conditionally called.
```js
// inner-function/nullable-objects/assume-invoked/conditional-call-chain.tsx
function Component({a, b}) {
  const logA = () => {
    console.log(a.value);
  };
  const logB = () => {
    console.log(b.value);
  };
  const hasLogged = useRef(false);
  const log = () => {
    if (!hasLogged.current) {
      logA();
      logB();
      hasLogged.current = true;
    }
  };
  return <Stringify log={log} shouldInvokeFns={true} />;
}
```

On the other hand, this means that we produce invalid output for code
like manually implementing `Array.map`
```js
// inner-function/nullable-objects/bug-invalid-array-map-manual.js
function useFoo({arr1, arr2}) {
  const cb = e => arr2[0].value + e.value;
  const y = [];
  for (let i = 0; i < arr1.length; i++) {
    y.push(cb(arr1[i]));
  }
  return y;
}
```
2025-03-18 18:00:08 -04:00
dan
86d5ac0882 Revert "Fix:- Improve HOC support and state preservation in React Refresh" (#32214)
Reverts facebook/react#30660

I don’t feel confident in the approach. This part of code is supposed to
rely on the module bundler behaving as expected. _Maybe_ this is correct
but I need to review it closer — it was intentionally _not_ implemented
this way originally.

I’ll try to take a closer look some time this week. We don’t have to
merge this revert right now but just flagging that I don’t understand
the thinking behind the new approach and don’t have confidence in it.
2025-03-18 19:05:56 +00:00
Jack Pope
476f53879e Add getClientRects to fragment instances (#32660)
Adds `getClientRects()` to fragment instances with a fixture test case.
`Element.getClientRect` returns a collection of `DOMRect`s (see example
of multiline span returning two `DOMRect` boxes).
`fragmentInstance.getClientRects` here flattens those collections into
an array of rects.
2025-03-18 13:54:26 -04:00
Jack Pope
c69a5fc53a Add blur() and focusLast() to fragment instances (#32654)
`focus()` was added in https://github.com/facebook/react/pull/32465.
Here we add `focusLast()` and `blur()`. I also extended `focus` to take
options.

`focus` will focus the first focusable element. `focusLast` will focus
the last focusable element. We could consider a `focusFirst` naming or
even the `focusWithin` used by test selector APIs as well.

`blur` will only have an effect if the current `document.activeElement`
is one of the fragment children.
2025-03-18 11:58:12 -04:00
Sebastian Markbåge
a35aaf704c Update ViewTransition fixture to include bigger buttons/swipe (#32656)
I made the button a bit bigger and moved the swipe recognizer around the
whole screen. Typically these are used around the whole content without
any affordances and not as a standalone scrubber. Ideally the swipe
would be able to be inside the animating content but it can't yet due to
[this Safari bug](https://bugs.webkit.org/show_bug.cgi?id=288795).

Added back some paragraphs so that scrolling can be tested properly. It
appears it's possible to get the swipe to be a bit misaligned if you
scroll enough on iOS.

<img width="437" alt="Screenshot 2025-03-17 at 10 27 42 PM"
src="https://github.com/user-attachments/assets/589dc828-717e-420c-83dc-94ae6ad59791"
/>
2025-03-18 09:24:31 -04:00
Sebastian Markbåge
3c3696d554 Measure Updated ViewTransition Boundaries (#32653)
This does the same thing for `measureUpdateViewTransition` that we did
for `measureNestedViewTransitions` in
e3cbaffef0.
If a boundary hasn't mutated and didn't change in size, we mark it for
cancellation. Otherwise we add names to it. The different from the
CommitViewTransition path is that the "old" names are added to the
clones so this is the first time the "new" names.

Now we also cancel any boundaries that were unchanged. So now the root
no longer animates. We still have to clone them. There are other
optimizations that can avoid cloning but once we've done all the layouts
we can still cancel the running animation and let them just be the
regular content if they didn't change. Just like the regular
fire-and-forget path.

This also fixes the measurement so that we measure clones by adjusting
their position back into the viewport.

This actually surfaces a bug in Safari that was already in #32612. It
turns out that the old names aren't picked up for some reason and so in
Safari they looked more like a cross-fade than what #32612 was supposed
to fix. However, now that bug is even more apparent because they
actually just disappear in Safari. I'm not sure what that bug is but
it's unrelated to this PR so will fix that separately.
2025-03-17 21:38:13 -04:00
Dimitri POSTOLOV
90b511ec7a fix(react-compiler): implement NumericLiteral as ObjectPropertyKey (#31791) 2025-03-17 19:30:58 -04:00
Sebastian Markbåge
02372952e4 Don't auto-start browser in SSR fixtures (#32652)
I end up restarting these a lot and it's annoying to have it open
another tab each time.

The flight fixture already doesn't auto-start.
2025-03-17 17:26:00 -04:00
Sebastian Markbåge
9fde224a53 Materialize the tree ID when ViewTransition name=auto consumes one (#32651)
ViewTransition uses the `useId` algorithm to auto-assign names. This
ensures that we could animate between SSR content and client content by
ensuring that the names line up.

However, I missed that we need to bump the id (materialize it) when we
do that. This is what function components do if they use one or more
`useId()`. This caused duplicate names when two ViewTransitions were
nested without any siblings since they would share name.
2025-03-17 16:17:01 -04:00
lauren
ca02c4bb40 [ci][ez] use yarn --cwd (#32650)
Run yarn install via `--cwd` instead of `working-directory` to make the
labels clearer
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32650).
* __->__ #32650
* #32649
* #32648
2025-03-17 14:13:27 -04:00
lauren
c37d89827e [ci] Pin Discord webhook action to specific commit sha (#32649)
Pins the discord webhook action to
`86dc739f3f165f16dadc5666051c367efa1692f4`, which is what the v6 tag
points to.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32649).
* #32650
* __->__ #32649
* #32648
2025-03-17 14:13:17 -04:00
lauren
6c6699f3d2 [ci] Don't use third party action to push commits (#32648)
In light of recent third party actions being compromised, let's just
push the commit ourselves rather than use a third party action. We
already detect if changes are needed, so the step will only run if so.

I also added a `dry_run` option to the manual runs of this workflow for
testing.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32648).
* #32650
* #32649
* __->__ #32648
2025-03-17 14:13:02 -04:00
Andrew Clark
9320a0139d Fix COMMIT_SHA when generating PR artifacts (#32647)
Follow-up to #31850. We want to build using the original commit SHA, not
the merge commit that GitHub Actions creates behind the scenes. We were
already checking out the correct commit object, but the COMMIT_SHA
artifact was still pointing to the merge commit.

This should fix the sizebot links to point to working URLs, too.
2025-03-17 12:46:27 -04:00
Ricky
fbcda19a23 [devtools] add filters for internal builds (#32646)
We don't have an experimental-only build of devtools, but we can at
least add these filters to the internal build.

A better way would be to use feature detection, but I'm not sure how and
this isn't a very heavily used feautre.
2025-03-17 12:14:19 -04:00
Jack Pope
cd28a946d5 Add observer methods to fragment instances (#32619)
This implements `observeUsing(observer)` and `unobserverUsing(observer)`
on fragment instances. IntersectionObservers and ResizeObservers can be
passed to observe each host child of the fragment. This is the
equivalent to calling `observer.observe(child)` or
`observer.unobserve(child)` for each child target.

Just like the addEventListener, the observer is held on the fragment
instance and applied to any newly mounted child. So you can do things
like wrap a paginated list in a fragment and have each child
automatically observed as they commit in.

Unlike, the event listeners though, we don't `unobserve` when a child is
removed. If a removed child is currently intersecting, the observer
callback will be called when it is removed with an empty rect. This lets
you track all the currently intersecting elements by setting state from
the observer callback and either adding or removing them from your list
depending on the intersecting state. If you want to track the removal of
items offscreen, you'd have to maintain that state separately and append
intersecting data to it in the observer callback. This is what the
fixture example does.

There could be more convenient ways of managing the state of multiple
child intersections, but basic examples are able to be modeled with the
simple implementation. Let's see how the usage goes as we integrate this
with more advanced loggers and other features.

For now you can only attach one observer to an instance. This could
change based on usage but the fragments are composable and could be
stacked as one way to apply multiple observers to the same elements.

In practice, one pattern we expect to enable is more composable logging
such as

```javascript
function Feed({ items }) {
  return (
    <ImpressionLogger>
      {items.map((item) => (
        <FeedItem />
      ))}
    </ImpressionLogger>
  );
}
```

where `ImpressionLogger` would set up the IntersectionObserver using a
fragment ref with the required business logic and various components
could layer it wherever the logging is needed. Currently most callsites
use a hook form, which can require wiring up refs through the tree and
merging refs for multiple loggers.
2025-03-17 11:40:05 -04:00
Ricky
8243f3f063 [bug] Fix component name for Portal and add tests (#32640)
Based off: https://github.com/facebook/react/pull/32499

While looking into `React.lazy` issues for built-ins, I noticed we
already error for `lazy` with build-ins, but we don't have any tests for
`getComponentNameFromType` using all the built-ins. This may be
something we should handle, but for now we should at least have tests.

Here's why: while writing tests, I noticed we check `type` instead of
`$$typeof` for portals:


9cdf8a99ed/packages/react-reconciler/src/ReactPortal.js (L25-L32)

This PR adds tests for all the built-ins and fixes the portal bug.

[Commit to
review](e068c167d4)
2025-03-17 11:23:28 -04:00
Ricky
df31952275 Remove offscreen type (#32639)
Based off https://github.com/facebook/react/pull/32499

This is no longer used.

[Review
commit](88c297d12f)
2025-03-17 09:37:07 -04:00
Ricky
1a191701fe [refactor] Add element type for Activity (#32499)
This PR separates Activity to it's own element type separate from
Offscreen. The goal is to allow us to add Activity element boundary
semantics during hydration similar to Suspense semantics, without
impacting the Offscreen behavior in suspended children.
2025-03-17 09:17:00 -04:00
Ricky
99563e9173 Partially revert #32588 (#32621)
https://github.com/facebook/react/pull/32588 changed the babel config
impacting local tests, and I'm not able to run test:

<img width="1354" alt="Screenshot 2025-03-15 at 2 37 00 PM"
src="https://github.com/user-attachments/assets/2d4afe39-6ab6-4c83-87a9-ceb0ee5f8df5"
/>


This PR reverts those changes until we can re-land with a fix.
2025-03-15 15:21:57 -04:00
Sebastian Markbåge
17d274dc12 Remove Mutation Check Around commit/measureUpdateViewTransition (#32617)
There's two ways to find updated View Transitions.

One is the "commit/measureNestedViewTransitions" pass which is used to
find things in unchanged subtrees. This can only lead to the relayout
case since there's can't possibly be any mutations in the subtree. This
is only triggered when none of the direct siblings have any mutations at
all.

The other case is "commit/measureUpdateViewTransition" which is for a
ViewTransition that itself has mutations scheduled inside of it which
leads to the "update" case.

However, there's a case between these two cases. When a direct sibling
has a mutation but there's also a ViewTransition exactly at the same
level. In that case we can't bail out on the whole set of children so we
won't trigger the "nested" case. Previously we also didn't trigger the
"commit/measureUpdateViewTransition" case because we first checked if
that had any mutations inside of it at all. This leads to neither case
picking up this boundary.

We could check if the ViewTransition itself has any mutations inside and
if not trigger the nested path.

There's a simpler way though. Because
`commit/measureUpdateViewTransition` is actually just optimistic. The
flags are pessimistic and we don't know for sure if there will actually
be a mutation until we've traversed the tree. It can sometimes lead to
the "relayout" case. So we can just use that same path, knowing that
it'll just lead to the layout pass. Therefore it's safe to just remove
this check.
2025-03-14 17:38:53 -04:00
Sebastian Markbåge
6b5d9fd316 Move traverseFragmentInstanceChildren to internal ReactFiberTreeReflection (#32613)
This is a nit but a Config should not have to know anything about the
internals of Fibers. Ideally it shouldn't even access them but we have
some cases where we need pointers back in like for this fragment.

The way we've typically abstracted this is using the
`ReactFiberTreeReflection` helper that's in the `react-reconciler`. Such
as in the event system.
f3c956006a/packages/react-dom-bindings/src/events/ReactDOMEventListener.js (L22-L26)

We sometimes cheat but we really should clean this up such that a
`Fiber` is actually an opaque type to the Configs and it can never dot
into it without using a helper.

So this just moves `traverseFragmentInstanceChildren` to
ReactFiberTreeReflection so that the ConfigDOM doesn't ever dot into its
fields itself. It just passes the Fiber through back into the
react-reconciler. I had to add a wrapper to read the `.child` to avoid
that being assumed too. I also noticed that FragmentInstanceType is not
actually passed through so that argument is unnecessary.
2025-03-14 17:38:35 -04:00
Sebastian Markbåge
2c560374d6 Measure and apply names for the "new" phase (#32612)
Stacked on #32599 and #32611.

This is able to reuse the code from CommitViewTransitions for "enter",
"shared" and "layout". The difference is that for "enter"/"shared" in
the "new" phase we pass in the deletions.

For "layout" of nested boundaries we just need to measure the clones at
the same time we measure the original nodes since we haven't measured
them in a previous phase in the current approach.

With these updates, things move around more like expected in the fixture
because we're now applying the appropriate pairs to trigger individual
animations instead of just the full document cross-fade.

The "update" phase is a little more complicated and is coming soon.
2025-03-14 14:26:55 -04:00
Sebastian Markbåge
2e385738a4 Find Pairs and Apply View Transition Names to the Clones in the "old" Phase (#32599)
Stacked on #32578.

We need to apply view-transition-names to the clones that we create in
the "old" phase for the ViewTransition boundaries that should activate.

Finding pairs is a little trickier than in
ReactFiberCommitViewTransitions. Normally we collect all name
"insertions" in the `accumulateSuspenseyCommit` phase before we even
commit. Then in the snapshot do we visit all "deletions" and since we
already collected all the insertions we know immediately if the deletion
had a pair and should therefore get a "name" assigned to activate the
boundary. For ReactFiberApplyGesture we need to assign names to
"insertions" since it's in reverse but we don't already have a map of
deletions. Therefore we need to first visit all deletions.

Instead of doing that in a completely separate pass, we instead visit
deletions in the same pass to find pairs. Since this is in the same pass
we might visit insertions before deletions or vice versa depending on
document order. However, we can deal with this by applying the name to
the insertion when we find the deletion if we've already made the clones
at that point.

Applying names to pure exits, updates or nested (relayout) is a bit more
straight-forward.
2025-03-14 14:15:39 -04:00
Sebastian Markbåge
c4a3b92e09 Add more phases to the ReactFiberApplyGesture (#32578)
Stacked on #32585 and #32605.

This adds more loops for the phases of "Apply Gesture". It doesn't
implement the interesting bit yet like adding view-transition-names and
measurements. I'll do that in a separate PR to keep reviewing easier.

The three phases of this approach is roughly:

- Clone and apply names to the "old" state.
- Inside startViewTransition: Apply names to the "new" state. Measure
both the "old" and "new" state to know whether to cancel some of them.
Delete the clones which will include all the "old" names.
- After startViewTransition: Restore "new" names back to no
view-transition-name.

Since we don't have any other Effects in these phases we have a bit more
flexibility and we can avoid extra phases that traverse the tree. I've
tried to avoid any additional passes.

An interesting consequence of this approach is that we could measure
both the "old" and "new" state before `startViewTransition`. This would
be more efficient because we wouldn't need to take View Transition
snapshots of parts of the tree that won't actually animate. However,
that would require an extra pass and force layout earlier. It would also
have different semantics from the fire-and-forget View Transitions
because we could optimize better which can be visible. It would also not
account for any late mutations. So I decided to instead let the layout
be computed by painting as usual and then measure both "old" and "new"
inside the startViewTransition instead. Then canceling anything that
doesn't animate to keep it consistent.

Unfortunately, though there's not a lot of code sharing possible in
these phases because the strategy is so different with the cloning and
because the animation is performed in reverse. The "finishedWork" Fiber
represents the "old" state and the "current" Fiber represents the "new"
state.

The most complicated phase is the cloning. I actually ended up having to
make a very different pattern from the other phases and CommitWork in
general. Because we have to clone as we go and also do other things like
apply names and finding pairs, it has more phases. I ended up with an
approach that uses three different loops. The outer one for updated
trees, one for inserted trees that don't need cloning (doesn't include
reappearing offscreen) and one for not updated trees that still need
cloning. Inside each loop it can also be in different phases which I
track with the `visitPhase` enum - this pattern is kind of new.

Additionally, we need to measure the cloned nodes after we've applied
mutations to them and we have to wait until the whole tree is inserted.
We don't have a reference to these DOM elements in the Fiber tree since
that still refers to the original ones. We need to store the cloned
elements somewhere. So I added a temporary field on the
ViewTransitionState to keep track of any clones owned by that
ViewTransition.

When we deep clone an unchanged subtree we don't have DOM element
instances. It wouldn't be quite safe to try to find them from the tree
structure. So we need to avoid the deep clones if we might need DOM
elements. Therefore we keep traversing in the case where we need to find
nested ViewTransition boundaries that are either potentially affected by
layout or a "pair".

For the other two phases the pattern there's a lot of code duplication
since it's slightly different from the commit ones but they at least
follow the same pattern. For the restore phase I was actually able to
reuse most of the code.

I don't love how much code this is.
2025-03-14 13:20:17 -04:00
Sebastian Markbåge
6daef4e7c8 Make xViewTransitionToHostInstances helpers reusable (#32611)
This prepares from being able to reuse some this in ApplyGesture.

These all start with resetting a counter but it's tricky to have to
remember to do this and tricky to do from the outside of this module. So
we make an exported helper that does the resetting. Ideally it gets
inlined.

We also stop passing "current" to measureViewTransitionHostInstances.
Same thing for cancelViewTransitionHostInstances. This doesn't make
sense for "nested" which has not updated and so might not have an
alternate. Instead we pass in the old and new name if they might be
different.
2025-03-14 13:16:42 -04:00
Sebastian Markbåge
3e956805e8 Gate flushGestureMutations and flushGestureAnimations (#32605)
Normally these are gated by the whole commitGestureOnRoot path but in
the case of an early commit these phases may need to be invoked.
Earlier. Those paths weren't gated which I noticed when I started adding
code to them.
2025-03-14 13:16:30 -04:00
Sebastian Markbåge
1b6e3dd985 Merge restoreEnterViewTransitions and restoreExitViewTransitions (#32585)
This is the exact same code in both cases. It's just general clean up.

By unifying them it becomes less confusing to reuse these helpers in the
Apply Gesture path where the naming is reversed.
2025-03-14 13:16:20 -04:00
mofeiZ
5398b71158 [compiler] detect and throw on untransformed required features (#32512)
Traverse program after running compiler transform to find untransformed
references to compiler features (e.g. `inferEffectDeps`, `fire`).

Hard error to fail the babel pipeline when the compiler fails to
transform these features to give predictable runtime semantics.
Untransformed calls to functions like `fire` will throw at runtime
anyways, so let's fail the build to catch these earlier.

Note that with this fails the build *regardless of panicThreshold*
2025-03-14 11:44:49 -04:00
lauren
f3c956006a [ci] Update node_modules cache path (#32609)
Alternative of #32604.

Bust all old caches since I reverted some other changes to the keys.
2025-03-13 22:29:48 -04:00
lauren
4eba294c69 [ci] Cache runtime and compiler only for test runs (#32608)
We only need the compiler built for `yarn test` in the root directory.
Rather than always cache both for every step, let's just do it where
it's needed explicitly.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32608).
* #32609
* __->__ #32608
2025-03-13 22:21:35 -04:00
lauren
5200721e5c Revert "[ci] Update yarn and node_modules cache key (#32603)" (#32607)
This reverts commit 67338703aa.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32607).
* #32609
* #32608
* __->__ #32607
2025-03-13 22:14:00 -04:00
lauren
e9d80d939e Revert "[ci] Fix node_modules cache glob (#32604)" (#32606)
This reverts commit ef1103d3e9.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32606).
* #32609
* #32608
* #32607
* __->__ #32606
2025-03-13 21:52:46 -04:00
lauren
ef1103d3e9 [ci] Fix node_modules cache glob (#32604)
Seems like the stringified cache path can cause some directories not to
be cached, trying an alternative format
2025-03-13 20:59:27 -04:00
lauren
67338703aa [ci] Update yarn and node_modules cache key (#32603)
Now that the compiler lint rule is merged into
eslint-plugin-react-hooks, we also need to update our caches so compiler
dependencies are also cached. This should fix the CI walltime regression
we are now seeing.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32603).
* #32604
* __->__ #32603
2025-03-13 20:31:04 -04:00
mofeiZ
7939d92fcc [compiler] clean up retry pipeline: fireRetry flag -> compileMode (#32511)
Removes `EnvironmentConfig.enableMinimalTransformsForRetry` in favor of
`run` parameters. This is a minimal difference but lets us explicitly
opt out certain compiler passes based on mode parameters, instead of
environment configurations

Retry flags don't really make sense to have in `EnvironmentConfig`
anyways as the config is user-facing API, while retrying is a compiler
implementation detail.

(per @josephsavona's feedback
https://github.com/facebook/react/pull/32164#issuecomment-2608616479)
> Re the "hacky" framing of this in the PR title: I think this is fine.
I can see having something like a compilation or output mode that we use
when running the pipeline. Rather than changing environment settings
when we re-run, various passes could take effect based on the
combination of the mode + env flags. The modes might be:
>
> * Full: transform, validate, memoize. This is the default today.
> * Transform: Along the lines of the backup mode in this PR. Only
applies transforms that do not require following the rules of React,
like `fire()`.
> * Validate: This could be used for ESLint.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32511).
* #32512
* __->__ #32511
2025-03-13 19:54:54 -04:00
mofeiZ
d92e5713be [compiler] Avoid bailouts when inserting gating (#32598)
This change fixes a coverage hole in rolling out with `gating`. Prior to
this PR, configuring `gating` causes React Compiler to bail out of
optimizing some functions.

This means that it's not entirely safe to cutover from `gating` enabled
for all users (i.e. rolled out 100%) to removing the `gating` config
altogether, as new functions may be opted into compilation when they
stop bailing out due to gating-specific logic.

This is technically slightly slower due to the additional function
indirection. An alternative approach is to recommend running a codemod
to insert `use no memo`s on currently-bailing out functions before
removing the`gating` config.

---
Tested [internally](
https://fburl.com/diff/q982ovua) by enabling on a page that previously
had a few hundred bailouts due to gating + hoisted function declarations
and (1) clicking around locally and (2) running a bunch of e2e tests
2025-03-13 19:31:49 -04:00
mofeiZ
93b61fc4ec [compiler][ez] Stop bailing out early for hoisted gated functions (#32597)
Some code movement for the next PR
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32597).
* #32598
* __->__ #32597
2025-03-13 19:08:38 -04:00
lauren
77987e5ee3 [ci] mkdir before mv (#32602)
Missed this earlier.
2025-03-13 17:46:46 -04:00
lauren
0df46f01a9 [ci] Update eslint-plugin-react-hooks output location for Meta builds (#32601)
Updates where this file is output so we can sync it independently to
another directory.
2025-03-13 16:54:39 -04:00
mofeiZ
f457d0b4c6 [compiler][ez] Only fail gating hoisting check for referenced identifiers (#32596)
Reduce false positive bailouts by using the same
`isReferencedIdentifier` logic that the compiler also uses for
determining context variables and a function's own hoisted declarations.

Details:
Previously, we counted every babel identifier as a reference. This is
problematic because babel counts most string symbols as an identifier.

```js
print(x);  // x is an identifier as expected
obj.x      // x is.. also an identifier here
{x: 2}     // x is also an identifier here
```

This PR adds a check for `isReferencedIdentifier`. Note that only
non-lval
references pass this check. This should be fine as we don't need to
hoist function declarations before writes to the same lvalue (which
should error in strict mode anyways)
```js
print(x);  // isReferencedIdentifier(x) -> true
obj.x      // isReferencedIdentifier(x) -> false
{x: 2}     // isReferencedIdentifier(x) -> false
x = 2      // isReferencedIdentifier(x) -> false
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32596).
* __->__ #32596
* #32595
* #32594
* #32593
* #32522
* #32521
2025-03-13 12:10:22 -04:00
mofeiZ
1c79cb82ab [compiler][ez] Move compiler gating tests (#32595)
Move all gating tests to `gating/`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32595).
* #32596
* __->__ #32595
* #32594
* #32593
* #32522
* #32521
2025-03-13 12:06:48 -04:00
mofeiZ
89a46a57df [compiler][optim] more shapes for mixedreadonly (#32594)
- Add `at`, `indexOf`, and `includes`
- Optimize MixedReadOnly which is currently only used by hook return
values. Hook return values are typed as Frozen, this change propagates
that to return values of aliasing function calls (such as `at`). One
potential issue is that developers may pass
`enableAssumeHooksFollowRulesOfReact:false` and set
`transitiveMixedData`, expecting their transitive mixed data to be
mutable. This is a bit of an edge case and already doesn't have clear
semantics.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32594).
* #32596
* #32595
* __->__ #32594
* #32593
* #32522
* #32521
2025-03-13 11:59:50 -04:00
mofeiZ
eb53139ee5 [compiler][optim] infer mixedReadOnly for numeric and computed properties (#32593)
Expand type inference to infer mixedReadOnly types for numeric and
computed property accesses.
```js
function Component({idx})
  const data = useFragment(...)
  // we want to type `posts` correctly as Array
  const posts = data.viewers[idx].posts.slice(0, 5);
  // ...
}
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32593).
* #32596
* #32595
* #32594
* __->__ #32593
* #32522
* #32521
2025-03-13 11:58:40 -04:00
mofeiZ
38a7600920 [compiler][optim] Add shape for Array.from (#32522)
(see title)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32522).
* #32596
* #32595
* #32594
* #32593
* __->__ #32522
* #32521
2025-03-13 11:58:17 -04:00
mofeiZ
ed1264f077 [compiler] Patch array and argument spread mutability (#32521)
Array and argument spreads may mutate stateful iterables. Spread sites
should have `ConditionallyMutate` effects (e.g. mutate if the ValueKind
is mutable, otherwise read).

See
- [ecma spec (13.2.4.1 Runtime Semantics: ArrayAccumulation.
SpreadElement : ...
AssignmentExpression)](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-runtime-semantics-arrayaccumulation).
- [ecma spec 13.3.8.1 Runtime Semantics:
ArgumentListEvaluation](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-runtime-semantics-argumentlistevaluation)

Note that
- Object and JSX Attribute spreads do not evaluate iterables (srcs
[mozilla](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#description),
[ecma](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-runtime-semantics-propertydefinitionevaluation))
- An ideal mutability inference system could model known collections
(i.e. Arrays or Sets) as a "mutated collection of non-mutable objects"
(see `todo-granular-iterator-semantics`), but this is not what we do
today. As such, an array / argument spread will always extend the range
of built-in arrays, sets, etc
- Due to HIR limitations, call expressions with argument spreads may
cause unnecessary bailouts and/or scope merging when we know the call
itself has `freeze`, `capture`, or `read` semantics (e.g.
`useHook(...mutableValue)`)
We can deal with this by rewriting these call instructions to (1) create
an intermediate array to consume the iterator and (2) capture and spread
the array at the callsite
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32521).
* #32596
* #32595
* #32594
* #32593
* #32522
* __->__ #32521
2025-03-13 11:58:02 -04:00
Tyler Scott Williams
ef06b54f8d fix: clarify which mobx libs are not compatible with compiler (#32570)
## Summary

Right now, `react-compiler-healthcheck` flags `mobx` as a "known
incompatible library". But it's not precisely *MobX* that's
incompatible. It's the observer HOC that comes from `mobx-react` and
`mobx-react-lite`.

I've been working on
[mst-use-observable](https://github.com/coolsoftwaretyler/mst-use-observable),
which makes MobX-State-Tree compatible with the compiler. However,
projects that use `mobx-state-tree` and `mst-use-observable` will still
depend on `mobx` as a dependency.

And there [have been efforts in the past to write a hook for
observability](https://github.com/mobxjs/mobx/discussions/2566). So it's
possible that MobX could become compatible, so long as authors access it
with a hook, rather than the HOC.

I would like to propose updating the health check to be a little more
precise and flag the HOC dependencies, rather than MobX itself.

Thanks in advance for your consideration!

## How did you test this change?

`npx react-compiler-healthcheck` shouldn't flag on `mobx` in
dependencies, but will for `mobx-react-lite` and `mobx-react`.

Test suites, formatting, linting, all passed.

---------

Co-authored-by: lauren <poteto@users.noreply.github.com>
2025-03-13 11:46:26 -04:00
Mohhamad Hussain
1b77c3d7b9 Update DEVELOPMENT_GUIDE.md (#32281)
fix: update CONTRIBUTING.md link path

Updated the relative path to CONTRIBUTING.md from `../CONTRIBUTING.md`
to `./../../CONTRIBUTING.md` to ensure the correct file is referenced.

<!--
  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?
-->

## 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.
-->
2025-03-13 11:45:26 -04:00
michael faith
5ccfcd17ff feat(eslint-plugin-react-hooks): merge rule from eslint-plugin-react-compiler into react-hooks plugin (#32416)
This change merges the `react-compiler` rule from
`eslint-plugin-react-compiler` into the `eslint-plugin-react-hooks`
plugin. In order to do the move in a way that keeps commit history with
the moved files, but also no remove them from their origin until a
future cleanup change can be done, I did the `git mv` first, and then
recreated the files that were moved in their original places, as a
separate commit. Unfortunately GH shows the moved files as new instead
of the ones that are truly new. But in the IDE and `git blame`, commit
history is intact with the moved files.

Since this change adds new dependencies, and one of those dependencies
has a higher `engines` declaration for `node` than what the plugin
currently has, this is technically a breaking change and will have to go
out as part of a major release.

### Related Changes
- https://github.com/facebook/react/pull/32458

---------

Co-authored-by: Lauren Tan <poteto@users.noreply.github.com>
2025-03-12 21:43:06 -04:00
lauren
a8ab2bcb62 [rollup] Add support for running prebuild commands (#32592)
Extracting portions of #32416 for easier review.

Adds a new `prebuild` option to allow for a prebuild command to be run
prior to building the bundle.

Co-authored-by: michael faith <michaelfaith@users.noreply.github.com>
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32592).
* __->__ #32592
* #32591
* #32590
* #32589
* #32588

---------

Co-authored-by: michael faith <michaelfaith@users.noreply.github.com>
2025-03-12 19:12:45 -04:00
lauren
8646349aeb [rollup] Fix codeFrame is not a function (#32591)
Extracting portions of #32416 for easier review.

Fixes a small issue where `codeFrame` is not a function when a rollup
error was encountered.

Co-authored-by: michael faith <michaelfaith@users.noreply.github.com>
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32591).
* #32592
* __->__ #32591
* #32590
* #32589
* #32588

---------

Co-authored-by: michael faith <michaelfaith@users.noreply.github.com>
2025-03-12 19:12:33 -04:00
lauren
f31779a112 [ez] Run Prettier on eslint-plugin-react-compiler/src/types (#32590)
Extracting portions of #32416 for easier review.

This PR contains small formatting fixes.

Co-authored-by: michael faith <michaelfaith@users.noreply.github.com>
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32590).
* #32592
* #32591
* __->__ #32590
* #32589
* #32588

---------

Co-authored-by: michael faith <michaelfaith@users.noreply.github.com>
2025-03-12 19:12:22 -04:00
lauren
0e2402eb20 Update eslint fixtures (#32589)
Extracting portions of #32416 for easier review.

This PR lightly updates the build scripts for the eslint fixtures.

Co-authored-by: michael faith <michaelfaith@users.noreply.github.com>
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32589).
* #32592
* #32591
* #32590
* __->__ #32589
* #32588

---------

Co-authored-by: michael faith <michaelfaith@users.noreply.github.com>
2025-03-12 19:07:51 -04:00
lauren
f695f95290 Update babel configs used in jest (#32588)
Extracting portions of #32416 for easier review.

This PR updates our babel configs (only used in jest) to support
classes.

Co-authored-by: michael faith <michaelfaith@users.noreply.github.com>
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32588).
* #32592
* #32591
* #32590
* #32589
* __->__ #32588

Co-authored-by: michael faith <michaelfaith@users.noreply.github.com>
2025-03-12 19:07:39 -04:00
lauren
5de83dcc0f [playground] Use onMount to check if the editor is available (#32586)
Playground test flakiness seems to be fixed but adding this as an extra
precaution
2025-03-12 18:27:15 -04:00
Jack Pope
5135f98795 Add DOM fixture page for Fragment Ref (#32527)
This adds a page to the DOM fixture to test Fragment Refs. The first
test case is for `addEventListener`/`removeEventListener`.

Setting `enableFragmentRefs` to `__EXPERIMENTAL__` and building is
required to run the fixture.

<img width="872" alt="Screenshot 2025-03-05 at 12 58 57 PM"
src="https://github.com/user-attachments/assets/fee498b7-fd96-4178-9e82-c46d4cb55c9b"
/>
2025-03-12 17:49:44 -04:00
lauren
26bca0005c [playground] Wait for Monaco to fully load (#32584)
I'm not sure what exactly is causing the flakiness in the playground e2e
tests but I suspect it's some kind of timing issue.

Let's try waiting for Monaco to be fully initialized before running
tests.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32584).
* __->__ #32584
* #32583
2025-03-12 17:47:24 -04:00
lauren
e0e98d9560 [playground] Update various deps (#32583)
Updates various deps to align with the rest of the codebase.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32583).
* #32584
* __->__ #32583
2025-03-12 17:47:12 -04:00
lauren
4ab827b869 [compiler] Dedupe @babel/types (#32581)
Extracting portions of #32416 for easier review. This PR dedupes
@babel/types to resolve to 7.26.3, for compatibility in the root
workspace where eslint-plugin-react-hooks resides.

I also needed to update @babel/preset-typescript in snap.

The compiler changes in HIR and ReactiveScopes were needed due to types
changing. Notably, Babel [added support for optional chaining
assignment](https://github.com/babel/babel/pull/15751) (currently [Stage
1](https://github.com/tc39/proposal-optional-chaining-assignment)), so
in the latest versions of @babel/types, AssignmentExpression.left can
now also be of t.OptionalMemberExpression.

Given that this is in Stage 1, the compiler probably shouldn't support
this syntax, so this PR updates HIR to bailout with a TODO if there is a
non LVal on the lhs of an Assignment Expression.

There was also a small superficial SourceLocation change needed in
`InferReactiveScopeVariables` as Babel 8 changes were [accidentally
released in
7](https://github.com/babel/babel/issues/10746#issuecomment-2699146670).
It doesn't affect our analysis so it seems fine to just update with the
new properties.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32581).
* #32582
* __->__ #32581

Co-authored-by: michael faith <michaelfaith@users.noreply.github.com>

Co-authored-by: michael faith <michaelfaith@users.noreply.github.com>
2025-03-12 17:02:10 -04:00
mofeiZ
3456b6634a [compiler] Repro for object spread and Array.from with mutable iterators (#32520)
See newly added test fixtures. Repros fixed in later prs of this stack
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32520).
* #32522
* #32521
* __->__ #32520
2025-03-12 15:08:55 -04:00
Sebastian Markbåge
75c979847f Ignore AbortError for gestures (#32579)
Follow up to #32540.

We do allow gestures to be cancelled early (we call skipTransition) if
the gesture stops before it has even started.

This happens in the fixture when we auto-scroll.
2025-03-12 14:20:35 -04:00
Jack Pope
6aa8254bb7 Add ref to Fragment (#32465)
*This API is experimental and subject to change or removal.*

This PR is an alternative to
https://github.com/facebook/react/pull/32421 based on feedback:
https://github.com/facebook/react/pull/32421#pullrequestreview-2625382015
. The difference here is that we traverse from the Fragment's fiber at
operation time instead of keeping a set of children on the
`FragmentInstance`. We still need to handle newly added or removed child
nodes to apply event listeners and observers, so we treat those updates
as effects.

**Fragment Refs**

This PR extends React's Fragment component to accept a `ref` prop. The
Fragment's ref will attach to a custom host instance, which will provide
an Element-like API for working with the Fragment's host parent and host
children.

Here I've implemented `addEventListener`, `removeEventListener`, and
`focus` to get started but we'll be iterating on this by adding
additional APIs in future PRs. This sets up the mechanism to attach refs
and perform operations on children. The FragmentInstance is implemented
in `react-dom` here but is planned for Fabric as well.

The API works by targeting the first level of host children and proxying
Element-like APIs to allow developers to manage groups of elements or
elements that cannot be easily accessed such as from a third-party
library or deep in a tree of Functional Component wrappers.

```javascript
import {Fragment, useRef} from 'react';

const fragmentRef = useRef(null);

<Fragment ref={fragmentRef}>
  <div id="A" />
  <Wrapper>
    <div id="B">
      <div id="C" />
    </div>
  </Wrapper>
  <div id="D" />
</Fragment>
```

In this case, calling `fragmentRef.current.addEventListener()` would
apply an event listener to `A`, `B`, and `D`. `C` is skipped because it
is nested under the first level of Host Component. If another Host
Component was appended as a sibling to `A`, `B`, or `D`, the event
listener would be applied to that element as well and any other APIs
would also affect the newly added child.

This is an implementation of the basic feature as a starting point for
feedback and further iteration.
2025-03-12 10:32:11 -04:00
Sebastian Markbåge
ca8f91f6f6 Log errors from startViewTransition to onRecoverableError (#32540)
We customize the messages only in DEV to keep it small in prod.

We skip some messages that are not really errors but more like
information.
2025-03-11 23:02:45 -04:00
Hendrik Liebau
2398554c60 [Flight]: Client-side registerServerReference must not break .bind() (#32565) 2025-03-11 22:15:38 +01:00
lauren
0ca3deebcf [rcr] Fix incorrect output platform (#32569)
Accidentally copypasted the wrong esbuild config.
2025-03-11 10:41:48 -04:00
Sebastian Markbåge
99e1024051 Check if a child is a new child before calling moveBefore (#32567)
This fixes a critical issue with moveBefore. I was told that the
disconnected -> connected case was going to be relaxed and not be an
error but apparently that is not the case.

This means that we can't use this for initial insertions. Only moves.

Unfortunately React's internals doesn't distinguish these cases. This
adds a hack that checks each nodes but this is pretty bad for
performance. We should only call this in one or the other case.

Given that we still need feature detection. Both of which means that
these calls are no longer inlined and this extra code. I wonder if it's
even worth it given that you can't even rely on it working anyway since
not all browsers have it. Kind of don't want to ship this until all
browsers have it.

Even then we'd ideally refactor React to use separate code paths for
initial insertion vs moves. Which leads to some unfortunate code
duplication.
2025-03-10 18:12:43 -04:00
Sebastian Markbåge
696950aa69 Enable moveBefore in experimental releases (#32549)
Enabling feature detection of early DOM features in a framework is
reckless. I'm not judging other frameworks (but also a little bit).
Because if you do something like `if (moveBefore) moveBefore(a, b) else
insertBefore(a, b)` like we do and then the implementation has to change
there are still too many websites out there that it becomes impossible
to change it. It would break the web. It would instead have to change to
a different name. That's what happened with `contains` -> `includes`.
Counter to popular belief it didn't have anything to do with patching
prototypes. Therefore, ideally frameworks shouldn't start rely on it
until there's two implementations so that there's time for feedback.

That's why we didn't immediately enable this even in experimental.
However, at this point there's probably enough feature detection and it
has shipped long enough in Chrome that it's unlikely to be able to
change at this point.

We can enable it now. For now just in `@experimental` to see if we can
flush out issues with it before bringing it to stable.
2025-03-10 15:27:46 -04:00
Sebastian Markbåge
a8c2bbdabf Cancel finished view transitions Animations manually in fire-and-forget too (#32545)
Otherwise these can survive into the next View Transition and cause
havoc to that transition.

This was appearing as a flash in Safari in the fixture when going from
A->B. This triggers a View Transition and at the same time the scroll
position updates in an effect. That fires a scroll event which starts a
gesture. This shouldn't really happen and the SwipeRecognizer should
ideally ignore those but it's good to surface edge cases. That gesture
is blocked on the View Transition finishing and then immediately after
it starts a gesture View Transition. That gesture then picked up the
former Animation from the previous transition which caused issues. This
PR fixes that flash.
2025-03-10 15:27:37 -04:00
Jack Pope
50ab2dde94 Make renameElementSymbol dynamic for native fb (#32566)
Use variant to begin rolling this out internally.
2025-03-10 15:24:09 -04:00
lauren
d331ba0411 [ci] Fix incorrect condition (#32551)
Fixes an incorrect condition for running tests in the compiler
workspace.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32551).
* __->__ #32551
* #32550
2025-03-07 16:43:38 -05:00
lauren
00aa0043c7 [compiler] Migrate compiler packages to tsup (#32550)
Currently in the `compiler` workspace, we invoke esbuild directly to
build most packages (with the exception of `snap`). This has been mostly
fine, but does not allow us to do things like generate type declaration
files.

I would like #32416 to be able to consume the merged
eslint-plugin-react-compiler from source rather than via npm, and one of
the things that has come up from my exploration in that stack using the
compiler from source is that babel-plugin-react-compiler is missing type
declarations. This is primarily because React's build process uses
rollup + rollup-plugin-typescript, which runs tsc. So the merged plugin
needs to typecheck properly in order to build. An alternative might be
to migrate to something like babel with rollup instead to simply strip
types rather than typecheck before building. The minor downside of that
approach is that we would need to manually maintain a d.ts file for
eslint-plugin-react-hooks. For now I would like to see if this PR helps
us make progress rather than go for the slightly worse alternative.

[`tsup`](https://github.com/egoist/tsup) is esbuild based so build
performance is comparable. It is slower when generating d.ts files, but
it's still much faster than rollup which we used prior to esbuild. For
now, I have turned off `dts` by default, and it is only passed when
publishing on npm.

If you want to also generate d.ts files you can run `yarn build --dts`.

```
# BEFORE: build all compiler packages (esbuild)
$ time yarn build

  Done in 15.61s.
yarn build  13.82s user 1.54s system 96% cpu 15.842 total

# ---

# AFTER: build all compiler packages (tsup)
$ time yarn build

  Done in 12.39s.
yarn build  12.58s user 1.68s system 106% cpu 13.350 total

# ---

# AFTER: build all compiler packages and type declarations (tsup)
$ time yarn build --dts

  Done in 30.69s.
yarn build  43.57s user 3.20s system 150% cpu 31.061 total
```

I still need to test if this unblocks #32416 but this stack can be
landed independently though as we could probably just release type
declarations on npm. No one should be using the compiler directly, but
if they really wanted to, lack of type declarations would not stop them
(cf React secret internals).

Note that I still kept esbuild as we still use it directly for forgive.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32550).
* #32551
* __->__ #32550
2025-03-07 16:41:55 -05:00
Nick Lefever
cc680065c3 Fix asserts caused by OffscreenComponent rendering in React Native with passChildrenWhenCloningPersistedNodes (#32528)
<!--
  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?
-->

This PR fixes asserts when `passChildrenWhenCloningPersistedNodes` is
enabled for React Native and OffscreenComponent child rendering unhides
host components.

Discussions around possible fixes for the asserts seen in React Native
suggested changing the way we handle hiding/unhiding host components by
updating the fiber state with the hidden host component instead of
submitting a hidden clone Fabric and keeping the original as the current
fiber.

Implementing this fix would require holding onto the original styling of
the hidden host component. The reconciler updates the styling by adding
`display: none` to hide the contents. If the original host component was
already hidden, the renderer would lose that information and remove the
styling when showing the contents again.

To reduce the changes required to make
`passChildrenWhenCloningPersistedNodes` work, this PR falls back to the
original cloning method when OffscreenComponents are part of the
children needed to be added back. This effectively resolve the asserts
triggered by the feature in RN and improves overall performance.

## 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 fix was tested by enabling `passChildrenWhenCloningPersistedNodes`
in an app built with React Native that had a repro for triggering the
asserts. The asserts do not occur anymore when using the changes in this
PR.

---------

Co-authored-by: Nick <lefever@meta.com>
2025-03-07 17:03:59 +00:00
Ricky
f9d78089c6 [flags] make enableComponentPerformanceTrack dynamic (#32359) 2025-03-06 14:00:12 -05:00
Mathias Stang
562f17efab docs(eslint-plugin-react-hooks): add changelog for 5.1.0 & 5.2.0 (#32536)
<!--
  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 changelog entries for the last two minor releases of
`eslint-plugin-react-hooks`. Fixes #31717.

I chose to not include #31208 (8382581446)
and #32115 (fd2d279984) in the changelog
as they only changed internals that do not affect consumers of the
plugin, and it doesn't seem like the changelog previously included such
changes.

Changes are sorted by importance (rather than by commit date), with the
most important changes first.

## 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.
-->

Docs only, nothing to test.
2025-03-06 13:58:39 -05:00
lauren
9e9b54d7f6 [compiler] Make CompilerError compatible with reflection (#32539) 2025-03-06 13:06:36 -05:00
Sebastian "Sebbie" Silbermann
029e8bd618 Add Owner Stack to attribute hydration mismatches (#32538) 2025-03-06 17:12:50 +01:00
Sebastian Markbåge
e81fcfe3f2 [Flight] Expose registerServerReference from the client builds (#32534)
This is used to register Server References that exist in the current
environment but also exists in the server it might call into. Such as a
remote server.

If the value comes from the remote server in the first place then this
is called automatically to ensure that you can pass a reference back to
where it came from - even if the `serverModuleMap` option is used. This
was already the case when `serverModuleMap` wasn't passed. This is how
you can pass server references back to the server. However, when we
added `serverModuleMap` that pass was skipped because we were getting
real functions instead of proxies.

For functions that wasn't yet passed from the remote server to the
current server, we can register them eagerly just like we do for
`import('/server').registerServerReference()`. You can now also do this
with `import('/client').registerServerReference()`. We could make them
shared so you only have to do this once but it might not be possible to
pass to the remote server and the remote server might not even be the
same RSC renderer. Therefore I split them. It's up to the compiler
whether it should do that or not. It has to know that any function you
might call might be able to receive it. This is currently global to a
specific RSC renderer.
2025-03-05 22:16:56 -05:00
Keith Cirkel
aac177c484 Support beforetoggle/toggle events for dialog (#32479) 2025-03-05 19:45:16 +01:00
lauren
6b1ae49571 [ez] Remove unused netlify.toml (#32530)
I don't think this is in use anymore
2025-03-05 12:24:45 -05:00
Sebastian Markbåge
e03ac20f94 Set direction in animate call directly (#32523)
Setting the animation's currentTime causes a quirk where the transition
can end up off by a bit and the end state can be slightly off the end
time.

However, I realized that we don't have to because if we just set the
direction in the `animate()` call directly the Safari bug goes away.
2025-03-05 09:33:06 -05:00
Sebastian Markbåge
e9252bcdcc During a Swipe Gesture Render a Clone Offscreen and Animate it Onscreen (#32500)
This is really the essence mechanism of the `useSwipeTransition`
feature.

We don't want to immediately switch to the destination state when
starting a gesture. The effects remain mounted on the current state. We
want the current state to be "live". This is important to for example
allow a video to keeping playing while starting a swipe (think
TikTok/Reels) and not stop until you've committed the action. The only
thing that can be live is the "new" state. Therefore we treat the
destination as the "old" state and perform a reverse animation from
there.

Ideally we could apply the old state to the DOM tree, take a snapshot
and then revert it back in the mutation of `startViewTransition`.
Unfortunately, the way `startViewTransition` was designed it always
paints one frame of the "old" state which would lead this to cause a
flicker.

To work around this, we need to create a clone of any View Transition
boundary that might be mutated and then render that offscreen. That way
we can render the "current" state on screen and the "destination" state
offscreen for the screenshots. Being mutated can be either due to React
doing a DOM mutation or if a child boundary resizes that causes the
parent to relayout. We don't have to do this for insertions or deletions
since they only appear on one side.

The worst case scenario is that we have to clone the whole root. That's
what this first PR implements. We clone the container and if it's not
absolutely positioned, we position it on top of the current one. If the
container is `document` or `<html>` we instead clone the `<body>` tag
since it's the only one we can insert a duplicate of. If the container
is deep in the tree we clone just that even though technically we should
probably clone the whole document in that case. We just keep the impact
smaller. Ideally though we'd never hit this case. In fact, if we clone
the document we issue a warning (always for now) since you probably
should optimize this. In the future I intend to add optimizations when
affected View Transition boundaries are absolutely positioned since they
cannot possibly relayout the parent. This would be the ideal way to use
this feature most efficiently but it still works without it.

Since we render the "old" state outside the viewport, we need to then
adjust the animation to put it back into the viewport. This is the
trickiest part to get right while still preserving any customization of
the View Transitions done using CSS. This current approach reapplies all
the animations with adjusted keyframes.

In the case of an "exit" the pseudo-element itself is positioned outside
the viewport but since we can't programmatically update the style of the
pseudo-element itself we instead adjust all the keyframes to put it back
into the viewport. If there is no animation on the group we add one.

In the case of an "update" the pseudo-element is positioned on the new
state which is already inside the viewport. However, the auto-generated
animation of the group has a starting keyframe that starts outside the
viewport. In this case we need to adjust that keyframe.

In the future I might explore a technique that inserts stylesheets
instead of mutating the animations. It might be simpler. But whatever
hacks work to maximize the compatibility is best.
2025-03-04 20:10:08 -05:00
Ricky
e0fe347967 [flags] remove enableOwnerStacks (#32426)
Bassed off: https://github.com/facebook/react/pull/32425

Wait to land internally.

[Commit to
review.](66aa6a4dbb)

This has landed everywhere
2025-03-04 12:34:34 -05:00
Ricky
d48c69246c [flags] make enableScrollEndPolyfill dynamic (#32517)
Will roll this out in www
2025-03-04 11:55:34 -05:00
2728 changed files with 180680 additions and 43087 deletions

View File

@@ -1,10 +1,11 @@
{
"packages": ["packages/react", "packages/react-dom", "packages/scheduler"],
"packages": ["packages/react", "packages/react-dom", "packages/react-server-dom-webpack", "packages/scheduler"],
"buildCommand": "download-build-in-codesandbox-ci",
"node": "18",
"node": "20",
"publishDirectory": {
"react": "build/oss-experimental/react",
"react-dom": "build/oss-experimental/react-dom",
"react-server-dom-webpack": "build/oss-experimental/react-server-dom-webpack",
"scheduler": "build/oss-experimental/scheduler"
},
"sandboxes": ["new"],

View File

@@ -28,3 +28,6 @@ packages/react-devtools-shared/src/hooks/__tests__/__source__/__untransformed__/
packages/react-devtools-shell/dist
packages/react-devtools-timeline/dist
packages/react-devtools-timeline/static
# Imported third-party Flow types
flow-typed/

View File

@@ -468,13 +468,14 @@ module.exports = {
files: ['packages/react-server-dom-webpack/**/*.js'],
globals: {
__webpack_chunk_load__: 'readonly',
__webpack_get_script_filename__: 'readonly',
__webpack_require__: 'readonly',
},
},
{
files: ['packages/react-server-dom-turbopack/**/*.js'],
globals: {
__turbopack_load__: 'readonly',
__turbopack_load_by_url__: 'readonly',
__turbopack_require__: 'readonly',
},
},
@@ -496,6 +497,7 @@ module.exports = {
'packages/react-devtools-shared/src/devtools/views/**/*.js',
'packages/react-devtools-shared/src/hook.js',
'packages/react-devtools-shared/src/backend/console.js',
'packages/react-devtools-shared/src/backend/fiber/renderer.js',
'packages/react-devtools-shared/src/backend/shared/DevToolsComponentStackFrame.js',
'packages/react-devtools-shared/src/frontend/utils/withPermissionsCheck.js',
],
@@ -504,6 +506,7 @@ module.exports = {
__IS_FIREFOX__: 'readonly',
__IS_EDGE__: 'readonly',
__IS_NATIVE__: 'readonly',
__IS_INTERNAL_MCP_BUILD__: 'readonly',
__IS_INTERNAL_VERSION__: 'readonly',
chrome: 'readonly',
},
@@ -514,6 +517,14 @@ module.exports = {
__IS_INTERNAL_VERSION__: 'readonly',
},
},
{
files: ['packages/react-devtools-*/**/*.js'],
excludedFiles: '**/__tests__/**/*.js',
plugins: ['eslint-plugin-react-hooks-published'],
rules: {
'react-hooks-published/rules-of-hooks': ERROR,
},
},
{
files: ['packages/eslint-plugin-react-hooks/src/**/*'],
extends: ['plugin:@typescript-eslint/recommended'],
@@ -544,13 +555,10 @@ module.exports = {
},
globals: {
$Call: 'readonly',
$ElementType: 'readonly',
$Flow$ModuleRef: 'readonly',
$FlowFixMe: 'readonly',
$Keys: 'readonly',
$NonMaybeType: 'readonly',
$PropertyType: 'readonly',
$ReadOnly: 'readonly',
$ReadOnlyArray: 'readonly',
$ArrayBufferView: 'readonly',
@@ -559,11 +567,13 @@ module.exports = {
ConsoleTask: 'readonly', // TOOD: Figure out what the official name of this will be.
ReturnType: 'readonly',
AnimationFrameID: 'readonly',
WeakRef: 'readonly',
// For Flow type annotation. Only `BigInt` is valid at runtime.
bigint: 'readonly',
BigInt: 'readonly',
BigInt64Array: 'readonly',
BigUint64Array: 'readonly',
CacheType: 'readonly',
Class: 'readonly',
ClientRect: 'readonly',
CopyInspectedElementPath: 'readonly',
@@ -575,15 +585,19 @@ module.exports = {
$AsyncIterator: 'readonly',
Iterator: 'readonly',
AsyncIterator: 'readonly',
IntervalID: 'readonly',
IteratorResult: 'readonly',
JSONValue: 'readonly',
JSResourceReference: 'readonly',
mixin$Animatable: 'readonly',
MouseEventHandler: 'readonly',
NavigateEvent: 'readonly',
PerformanceMeasureOptions: 'readonly',
PropagationPhases: 'readonly',
PropertyDescriptor: 'readonly',
React$AbstractComponent: 'readonly',
PropertyDescriptorMap: 'readonly',
Proxy$traps: 'readonly',
React$Component: 'readonly',
React$ComponentType: 'readonly',
React$Config: 'readonly',
React$Context: 'readonly',
React$Element: 'readonly',
@@ -604,17 +618,21 @@ module.exports = {
symbol: 'readonly',
SyntheticEvent: 'readonly',
SyntheticMouseEvent: 'readonly',
SyntheticPointerEvent: 'readonly',
Thenable: 'readonly',
TimeoutID: 'readonly',
WheelEventHandler: 'readonly',
FinalizationRegistry: 'readonly',
Exclude: 'readonly',
Omit: 'readonly',
Keyframe: 'readonly',
PropertyIndexedKeyframes: 'readonly',
KeyframeAnimationOptions: 'readonly',
GetAnimationsOptions: 'readonly',
Animatable: 'readonly',
ScrollTimeline: 'readonly',
EventListenerOptionsOrUseCapture: 'readonly',
FocusOptions: 'readonly',
OptionalEffectTiming: 'readonly',
spyOnDev: 'readonly',
spyOnDevAndProd: 'readonly',
@@ -632,5 +650,6 @@ module.exports = {
AsyncLocalStorage: 'readonly',
async_hooks: 'readonly',
globalThis: 'readonly',
navigation: 'readonly',
},
};

View File

@@ -1,18 +0,0 @@
---
name: "⚛React 19 beta issue"
about: Report a issue with React 19 beta.
title: '[React 19]'
labels: 'React 19'
---
## Summary
<!--
Please provide a CodeSandbox (https://codesandbox.io/s/new), a link to a
repository on GitHub, or provide a minimal code example that reproduces the
problem. You may provide a screenshot of the application if you think it is
relevant to your bug report. Here are some tips for providing a minimal
example: https://stackoverflow.com/help/mcve.
-->

View File

@@ -11,7 +11,7 @@ body:
options:
- label: React Compiler core (the JS output is incorrect, or your app works incorrectly after optimization)
- label: babel-plugin-react-compiler (build issue installing or using the Babel plugin)
- label: eslint-plugin-react-compiler (build issue installing or using the eslint plugin)
- label: eslint-plugin-react-hooks (build issue installing or using the eslint plugin)
- label: react-compiler-healthcheck (build issue installing or using the healthcheck script)
- type: input
attributes:

View File

@@ -7,9 +7,28 @@ on:
- compiler/**
- .github/workflows/compiler_**.yml
permissions: {}
jobs:
check_access:
if: ${{ github.event.pull_request.draft == false }}
runs-on: ubuntu-latest
outputs:
is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
steps:
- run: echo ${{ github.event.pull_request.author_association }}
- name: Check is member or collaborator
id: check_is_member_or_collaborator
if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}
run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
check_maintainer:
if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }}
needs: [check_access]
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
permissions:
# Used by check_maintainer
contents: read
with:
actor: ${{ github.event.pull_request.user.login }}
@@ -19,7 +38,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v6.0.0
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
with:
webhook-url: ${{ secrets.COMPILER_DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.pull_request.user.login }}

View File

@@ -8,6 +8,8 @@ on:
- compiler/**
- .github/workflows/compiler_playground.yml
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
@@ -36,10 +38,25 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: |
**/node_modules
key: compiler-and-playground-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
working-directory: compiler
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Check Playwright version
id: playwright_version
run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
- name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
id: cache_playwright_browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
- run: npx playwright install --with-deps chromium
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
- run: CI=true yarn test
- run: ls -R test-results
if: '!cancelled()'
@@ -49,3 +66,4 @@ jobs:
with:
name: test-results
path: compiler/apps/playground/test-results
if-no-files-found: ignore

View File

@@ -16,15 +16,22 @@ on:
version_name:
required: true
type: string
tag_version:
required: false
type: string
dry_run:
required: false
type: boolean
secrets:
NPM_TOKEN:
required: true
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
GH_TOKEN: ${{ github.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
defaults:
@@ -46,10 +53,18 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Publish packages to npm
if: steps.node_modules.outputs.cache-hit != 'true'
- if: inputs.dry_run == true
name: Publish packages to npm (dry run)
run: |
cp ./scripts/release/ci-npmrc ~/.npmrc
scripts/release/publish.js --frfr --ci --versionName=${{ inputs.version_name }} --tag ${{ inputs.dist_tag }}
scripts/release/publish.js --frfr --debug --ci --versionName=${{ inputs.version_name }} --tag=${{ inputs.dist_tag }} ${{ inputs.tag_version && format('--tagVersion={0}', inputs.tag_version) || '' }}
- if: inputs.dry_run != true
name: Publish packages to npm
run: |
cp ./scripts/release/ci-npmrc ~/.npmrc
scripts/release/publish.js --frfr --ci --versionName=${{ inputs.version_name }} --tag=${{ inputs.dist_tag }} ${{ inputs.tag_version && format('--tagVersion={0}', inputs.tag_version) || '' }}

View File

@@ -14,6 +14,14 @@ on:
version_name:
required: true
type: string
tag_version:
required: false
type: string
dry_run:
required: false
type: boolean
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -27,5 +35,7 @@ jobs:
release_channel: ${{ inputs.release_channel }}
dist_tag: ${{ inputs.dist_tag }}
version_name: ${{ inputs.version_name }}
tag_version: ${{ inputs.tag_version }}
dry_run: ${{ inputs.dry_run }}
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -5,6 +5,8 @@ on:
# At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
- cron: 10 16 * * 1,2,3,4,5
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -17,5 +19,6 @@ jobs:
release_channel: experimental
dist_tag: experimental
version_name: '0.0.0'
dry_run: false
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -1,21 +0,0 @@
name: (Compiler) Publish Prereleases Weekly
on:
schedule:
# At 10 minutes past 9:00 on Mon
- cron: 10 9 * * 1
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
publish_prerelease_beta:
name: Publish to beta channel
uses: facebook/react/.github/workflows/compiler_prereleases.yml@main
with:
commit_sha: ${{ github.sha }}
release_channel: beta
dist_tag: beta
version_name: '19.0.0'
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -8,6 +8,8 @@ on:
- compiler/**
- .github/workflows/compiler_typescript.yml
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
@@ -45,10 +47,13 @@ jobs:
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn workspace babel-plugin-react-compiler lint
# Hardcoded to improve parallelism
@@ -66,9 +71,11 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn workspace babel-plugin-react-compiler jest
test:
@@ -90,10 +97,12 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: xvfb-run -a yarn workspace ${{ matrix.workspace_name }} test
if: runner.os == 'Linux' && matrix.workspace_name == 'react-forgive'
- run: yarn workspace ${{ matrix.workspace_name }} test
if: runner.os != 'Linux' && matrix.workspace_name != 'react-forgive'
if: matrix.workspace_name != 'react-forgive'

View File

@@ -0,0 +1,49 @@
name: (DevTools) Discord Notify
on:
pull_request_target:
types: [opened, ready_for_review]
paths:
- packages/react-devtools**
- .github/workflows/devtools_**.yml
permissions: {}
jobs:
check_access:
if: ${{ github.event.pull_request.draft == false }}
runs-on: ubuntu-latest
outputs:
is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
steps:
- run: echo ${{ github.event.pull_request.author_association }}
- name: Check is member or collaborator
id: check_is_member_or_collaborator
if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}
run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
check_maintainer:
if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }}
needs: [check_access]
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
permissions:
# Used by check_maintainer
contents: read
with:
actor: ${{ github.event.pull_request.user.login }}
notify:
if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }}
needs: check_maintainer
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
with:
webhook-url: ${{ secrets.DEVTOOLS_DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.pull_request.user.login }}
embed-author-url: ${{ github.event.pull_request.user.html_url }}
embed-author-icon-url: ${{ github.event.pull_request.user.avatar_url }}
embed-title: '#${{ github.event.number }} (+${{github.event.pull_request.additions}} -${{github.event.pull_request.deletions}}): ${{ github.event.pull_request.title }}'
embed-description: ${{ github.event.pull_request.body }}
embed-url: ${{ github.event.pull_request.html_url }}

View File

@@ -9,6 +9,8 @@ on:
required: false
type: string
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -18,6 +20,9 @@ jobs:
download_build:
name: Download base build
runs-on: ubuntu-latest
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -29,13 +34,15 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Download react-devtools artifacts for base revision
run: |
git fetch origin main
@@ -47,6 +54,7 @@ jobs:
with:
name: build
path: build
if-no-files-found: error
build_devtools_and_process_artifacts:
name: Build DevTools and process artifacts
@@ -63,11 +71,13 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -82,18 +92,21 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: react-devtools
path: build/devtools.tgz
path: build/devtools
if-no-files-found: error
# Simplifies getting the extension for local testing
- name: Archive chrome extension
uses: actions/upload-artifact@v4
with:
name: react-devtools-chrome-extension
path: build/devtools/chrome-extension.zip
if-no-files-found: error
- name: Archive firefox extension
uses: actions/upload-artifact@v4
with:
name: react-devtools-firefox-extension
path: build/devtools/firefox-extension.zip
if-no-files-found: error
run_devtools_tests_for_versions:
name: Run DevTools tests for versions
@@ -120,9 +133,11 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4
- name: Display structure of build
@@ -154,17 +169,28 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4
- name: Display structure of build
run: ls -R build
- name: Playwright install deps
run: |
npx playwright install
sudo npx playwright install-deps
- name: Check Playwright version
id: playwright_version
run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
- name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
id: cache_playwright_browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
- run: npx playwright install --with-deps
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
- run: npx playwright install-deps
if: steps.cache_playwright_browsers.outputs.cache-hit == 'true'
- run: ./scripts/ci/download_devtools_regression_build.js ${{ matrix.version }}
- run: ls -R build-regression
- run: ./scripts/ci/run_devtools_e2e_tests.js ${{ matrix.version }}
@@ -175,4 +201,5 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: screenshots
path: ./tmp/screenshots
path: ./tmp/playwright-artifacts
if-no-files-found: warn

View File

@@ -6,6 +6,14 @@ on:
pull_request:
paths-ignore:
- compiler/**
workflow_dispatch:
inputs:
commit_sha:
required: false
type: string
default: ''
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
@@ -17,6 +25,95 @@ env:
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
# ----- NODE_MODULES CACHE -----
# Centralize the node_modules cache so it is saved once and each subsequent job only needs to
# restore the cache. Prevents race conditions where multiple workflows try to write to the cache.
runtime_node_modules_cache:
name: Cache Runtime node_modules
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
lookup-only: true
- uses: actions/setup-node@v4
if: steps.node_modules.outputs.cache-hit != 'true'
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Warm with old cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/restore@v4
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Save cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
runtime_compiler_node_modules_cache:
name: Cache Runtime, Compiler node_modules
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
lookup-only: true
- uses: actions/setup-node@v4
if: steps.node_modules.outputs.cache-hit != 'true'
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
- name: Warm with old cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/restore@v4
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Save cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
# ----- FLOW -----
discover_flow_inline_configs:
name: Discover flow inline configs
@@ -26,7 +123,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/github-script@v7
id: set-matrix
with:
@@ -36,7 +133,7 @@ jobs:
flow:
name: Flow check ${{ matrix.flow_inline_config_shortname }}
needs: discover_flow_inline_configs
needs: [discover_flow_inline_configs, runtime_node_modules_cache]
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -45,76 +142,91 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: node ./scripts/tasks/flow-ci ${{ matrix.flow_inline_config_shortname }}
# ----- FIZZ -----
check_generated_fizz_runtime:
name: Confirm generated inline Fizz runtime is up to date
needs: [runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: |
yarn generate-inline-fizz-runtime
git diff --quiet || (echo "There was a change to the Fizz runtime. Run `yarn generate-inline-fizz-runtime` and check in the result." && false)
git diff --exit-code || (echo "There was a change to the Fizz runtime. Run \`yarn generate-inline-fizz-runtime\` and check in the result." && false)
# ----- FEATURE FLAGS -----
flags:
name: Check flags
needs: [runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn flags
# ----- TESTS -----
test:
name: yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }})
needs: [runtime_compiler_node_modules_cache]
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -148,56 +260,106 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
# Hardcoded to improve parallelism
test-linter:
name: Test eslint-plugin-react-hooks
needs: [runtime_compiler_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
- name: Install runtime dependencies
run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Install compiler dependencies
run: yarn install --frozen-lockfile
working-directory: compiler
if: steps.node_modules.outputs.cache-hit != 'true'
- run: ./scripts/react-compiler/build-compiler.sh && ./scripts/react-compiler/link-compiler.sh
- run: yarn workspace eslint-plugin-react-hooks test
# ----- BUILD -----
build_and_lint:
name: yarn build and lint
needs: [runtime_compiler_node_modules_cache]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# yml is dumb. update the --total arg to yarn build if you change the number of workers
worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]
release_channel: [stable, experimental]
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 11.0.22
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn build --index=${{ matrix.worker_id }} --total=25 --r=${{ matrix.release_channel }} --ci
env:
CI: github
RELEASE_CHANNEL: ${{ matrix.release_channel }}
@@ -211,10 +373,11 @@ jobs:
with:
name: _build_${{ matrix.worker_id }}_${{ matrix.release_channel }}
path: build
if-no-files-found: error
test_build:
name: yarn test-build
needs: build_and_lint
needs: [build_and_lint, runtime_compiler_node_modules_cache]
strategy:
fail-fast: false
matrix:
@@ -249,28 +412,44 @@ jobs:
# TODO: Test more persistent configurations?
]
shard:
- 1/3
- 2/3
- 3/3
- 1/10
- 2/10
- 3/10
- 4/10
- 5/10
- 6/10
- 7/10
- 8/10
- 9/10
- 10/10
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -283,26 +462,35 @@ jobs:
process_artifacts_combined:
name: Process artifacts combined
needs: build_and_lint
needs: [build_and_lint, runtime_node_modules_cache]
permissions:
# https://github.com/actions/attest-build-provenance
id-token: write
attestations: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -311,7 +499,7 @@ jobs:
merge-multiple: true
- name: Display structure of build
run: ls -R build
- run: echo ${{ github.sha }} >> build/COMMIT_SHA
- run: echo ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- name: Scrape warning messages
run: |
mkdir -p ./build/__test_utils__
@@ -321,35 +509,53 @@ jobs:
# TODO: Migrate scripts to use `build` directory instead of `build2`
- run: cp ./build.tgz ./build2.tgz
- name: Archive build artifacts
id: upload_artifacts_combined
uses: actions/upload-artifact@v4
with:
name: artifacts_combined
path: |
./build.tgz
./build2.tgz
if-no-files-found: error
- uses: actions/attest-build-provenance@v2
# We don't verify builds generated from pull requests not originating from facebook/react.
# However, if the PR lands, the run on `main` will generate the attestation which can then
# be used to download a build via scripts/release/download-experimental-build.js.
#
# Note that this means that scripts/release/download-experimental-build.js must be run with
# --no-verify when downloading a build from a fork.
if: github.event_name == 'push' && github.ref_name == 'main' || github.event.pull_request.head.repo.full_name == github.repository
with:
subject-name: artifacts_combined.zip
subject-digest: sha256:${{ steps.upload_artifacts_combined.outputs.artifact-digest }}
check_error_codes:
name: Search build artifacts for unminified errors
needs: build_and_lint
needs: [build_and_lint, runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -361,30 +567,35 @@ jobs:
- name: Search build artifacts for unminified errors
run: |
yarn extract-errors
git diff --quiet || (echo "Found unminified errors. Either update the error codes map or disable error minification for the affected build, if appropriate." && false)
git diff --exit-code || (echo "Found unminified errors. Either update the error codes map or disable error minification for the affected build, if appropriate." && false)
check_release_dependencies:
name: Check release dependencies
needs: build_and_lint
needs: [build_and_lint, runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -402,22 +613,23 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
id: node_modules
with:
path: "**/node_modules"
key: fixtures_dom-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: fixtures_dom-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'fixtures/dom/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
working-directory: fixtures/dom
- run: yarn --cwd fixtures/dom install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -442,7 +654,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -452,14 +664,31 @@ jobs:
# That means dependencies of the built packages are not installed.
# We need to install dependencies of the workroot to fulfill all dependency constraints
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
id: node_modules
with:
path: "**/node_modules"
key: fixtures_flight-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: fixtures_flight-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'fixtures/flight/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd fixtures/flight install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Check Playwright version
id: playwright_version
run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
- name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
id: cache_playwright_browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
- name: Playwright install deps
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
working-directory: fixtures/flight
run: npx playwright install --with-deps chromium
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -468,16 +697,6 @@ jobs:
merge-multiple: true
- name: Display structure of build
run: ls -R build
- name: Install fixture dependencies
working-directory: fixtures/flight
run: |
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
if [ $? -ne 0 ]; then
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
fi
- name: Playwright install deps
working-directory: fixtures/flight
run: npx playwright install --with-deps chromium
- name: Run tests
working-directory: fixtures/flight
run: yarn test
@@ -489,16 +708,18 @@ jobs:
with:
name: flight-playwright-report
path: fixtures/flight/playwright-report
if-no-files-found: warn
- name: Archive Flight fixture artifacts
uses: actions/upload-artifact@v4
with:
name: flight-test-results
path: fixtures/flight/test-results
if-no-files-found: ignore
# ----- DEVTOOLS -----
build_devtools_and_process_artifacts:
name: Build DevTools and process artifacts
needs: build_and_lint
needs: [build_and_lint, runtime_node_modules_cache]
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -507,21 +728,26 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -539,6 +765,12 @@ jobs:
with:
name: react-devtools-${{ matrix.browser }}-extension
path: build/devtools/${{ matrix.browser }}-extension.zip
if-no-files-found: error
- name: Archive ${{ matrix.browser }} metadata
uses: actions/upload-artifact@v4
with:
name: react-devtools-${{ matrix.browser }}-metadata
path: build/devtools/webpack-stats.*.json
merge_devtools_artifacts:
name: Merge DevTools artifacts
@@ -549,83 +781,112 @@ jobs:
uses: actions/upload-artifact/merge@v4
with:
name: react-devtools
pattern: react-devtools-*-extension
pattern: react-devtools-*
run_devtools_e2e_tests:
name: Run DevTools e2e tests
needs: build_and_lint
needs: [build_and_lint, runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
pattern: _build_*
path: build
merge-multiple: true
- run: |
npx playwright install
sudo npx playwright install-deps
- name: Check Playwright version
id: playwright_version
run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
- name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
id: cache_playwright_browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
- name: Playwright install deps
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
run: npx playwright install --with-deps chromium
- run: ./scripts/ci/run_devtools_e2e_tests.js
env:
RELEASE_CHANNEL: experimental
- name: Archive Playwright report
uses: actions/upload-artifact@v4
with:
name: devtools-playwright-artifacts
path: tmp/playwright-artifacts
if-no-files-found: warn
# ----- SIZEBOT -----
sizebot:
if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' && github.event.pull_request.base.ref == 'main' }}
name: Run sizebot
needs: [build_and_lint]
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
working-directory: scripts/release
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Download artifacts for base revision
# The build could have been generated from a fork, so we must download the build without
# any verification. This is safe since we only use this for sizebot calculation and the
# unverified artifact is not used. Additionally this workflow runs in the pull_request
# trigger so only restricted permissions are available.
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }})
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }}) ${{ (github.event.pull_request.head.repo.full_name != github.repository && '--noVerify') || ''}}
mv ./build ./base-build
# TODO: The `download-experimental-build` script copies the npm
# packages into the `node_modules` directory. This is a historical
# quirk of how the release script works. Let's pretend they
# don't exist.
- name: Delete extraneous files
# TODO: The `download-experimental-build` script copies the npm
# packages into the `node_modules` directory. This is a historical
# quirk of how the release script works. Let's pretend they
# don't exist.
run: rm -rf ./base-build/node_modules
- name: Display structure of base-build from origin/main
run: ls -R base-build
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build for PR
uses: actions/download-artifact@v4
with:
@@ -638,10 +899,11 @@ jobs:
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
- name: Display structure of build for PR
run: ls -R build
- run: echo ${{ github.sha }} >> build/COMMIT_SHA
- run: echo ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: node ./scripts/tasks/danger
- name: Archive sizebot results
uses: actions/upload-artifact@v4
with:
name: sizebot-message
path: sizebot-message.md
if-no-files-found: ignore

View File

@@ -16,6 +16,13 @@ on:
required: true
default: false
type: boolean
dry_run:
description: Perform a dry run (run everything except push)
required: true
default: false
type: boolean
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -25,6 +32,40 @@ env:
jobs:
download_artifacts:
runs-on: ubuntu-latest
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
steps:
- uses: actions/checkout@v4
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Download artifacts for base revision
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}
- name: Display structure of build
run: ls -R build
- name: Archive build
uses: actions/upload-artifact@v4
with:
name: build
path: build/
if-no-files-found: error
process_artifacts:
runs-on: ubuntu-latest
needs: [download_artifacts]
outputs:
www_branch_count: ${{ steps.check_branches.outputs.www_branch_count }}
fbsource_branch_count: ${{ steps.check_branches.outputs.fbsource_branch_count }}
@@ -64,27 +105,11 @@ jobs:
run: |
echo "www_branch_count=$(git ls-remote --heads origin "refs/heads/meta-www" | wc -l)" >> "$GITHUB_OUTPUT"
echo "fbsource_branch_count=$(git ls-remote --heads origin "refs/heads/meta-fbsource" | wc -l)" >> "$GITHUB_OUTPUT"
- uses: actions/setup-node@v4
- name: Restore downloaded build
uses: actions/download-artifact@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
name: yarn install (react)
- run: yarn install --frozen-lockfile
name: yarn install (scripts/release)
working-directory: scripts/release
- name: Download artifacts for base revision
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}
name: build
path: build
- name: Display structure of build
run: ls -R build
- name: Strip @license from eslint plugin and react-refresh
@@ -107,9 +132,10 @@ jobs:
mkdir ./compiled/facebook-www/__test_utils__
mv build/__test_utils__/ReactAllWarnings.js ./compiled/facebook-www/__test_utils__/ReactAllWarnings.js
# Move eslint-plugin-react-hooks into facebook-www
mv build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
./compiled/facebook-www/eslint-plugin-react-hooks.js
# Copy eslint-plugin-react-hooks
mkdir ./compiled/eslint-plugin-react-hooks
cp build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
./compiled/eslint-plugin-react-hooks/index.js
# Move unstable_server-external-runtime.js into facebook-www
mv build/oss-experimental/react-dom/unstable_server-external-runtime.js \
@@ -136,10 +162,19 @@ jobs:
mv build/facebook-react-native/react-is/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-is/
mv build/facebook-react-native/react-test-renderer/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-test-renderer/
# Delete OSS renderer. OSS renderer is synced through internal script.
# Delete the OSS renderers, these are sync'd to RN separately.
RENDERER_FOLDER=$BASE_FOLDER/react-native-github/Libraries/Renderer/implementations/
rm $RENDERER_FOLDER/ReactFabric-{dev,prod,profiling}.js
rm $RENDERER_FOLDER/ReactNativeRenderer-{dev,prod,profiling}.js
# Delete the legacy renderer shim, this is not sync'd and will get deleted in the future.
SHIM_FOLDER=$BASE_FOLDER/react-native-github/Libraries/Renderer/shims/
rm $SHIM_FOLDER/ReactNative.js
# Copy eslint-plugin-react-hooks
# NOTE: This is different from www, here we include the full package
# including package.json to include dependencies in fbsource.
mkdir "$BASE_FOLDER/tools"
cp -r build/oss-experimental/eslint-plugin-react-hooks "$BASE_FOLDER/tools"
# Move React Native version file
mv build/facebook-react-native/VERSION_NATIVE_FB ./compiled-rn/VERSION_NATIVE_FB
@@ -147,9 +182,9 @@ jobs:
ls -R ./compiled-rn
- name: Add REVISION files
run: |
echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION
echo ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} >> ./compiled/facebook-www/REVISION
cp ./compiled/facebook-www/REVISION ./compiled/facebook-www/REVISION_TRANSFORMS
echo ${{ github.sha}} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
echo ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
- name: "Get current version string"
id: get_current_version
run: |
@@ -166,15 +201,20 @@ jobs:
with:
name: compiled
path: compiled/
if-no-files-found: error
- uses: actions/upload-artifact@v4
with:
name: compiled-rn
path: compiled-rn/
if-no-files-found: error
commit_www_artifacts:
needs: download_artifacts
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.www_branch_count == '0')
needs: [download_artifacts, process_artifacts]
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.process_artifacts.outputs.www_branch_count == '0')
runs-on: ubuntu-latest
permissions:
# Used to push a commit to builds/facebook-www
contents: write
steps:
- uses: actions/checkout@v4
with:
@@ -186,12 +226,12 @@ jobs:
name: compiled
path: compiled/
- name: Revert version changes
if: needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != ''
if: needs.process_artifacts.outputs.last_version_classic != '' && needs.process_artifacts.outputs.last_version_modern != ''
env:
CURRENT_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.download_artifacts.outputs.current_version_modern }}
LAST_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.last_version_classic }}
LAST_VERSION_MODERN: ${{ needs.download_artifacts.outputs.last_version_modern }}
CURRENT_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.process_artifacts.outputs.current_version_modern }}
LAST_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.last_version_classic }}
LAST_VERSION_MODERN: ${{ needs.process_artifacts.outputs.last_version_modern }}
run: |
echo "Reverting $CURRENT_VERSION_CLASSIC to $LAST_VERSION_CLASSIC"
grep -rl "$CURRENT_VERSION_CLASSIC" ./compiled || echo "No files found with $CURRENT_VERSION_CLASSIC"
@@ -221,12 +261,12 @@ jobs:
echo "should_commit=false" >> "$GITHUB_OUTPUT"
fi
- name: Re-apply version changes
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != '')
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.process_artifacts.outputs.last_version_classic != '' && needs.process_artifacts.outputs.last_version_modern != '')
env:
CURRENT_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.download_artifacts.outputs.current_version_modern }}
LAST_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.last_version_classic }}
LAST_VERSION_MODERN: ${{ needs.download_artifacts.outputs.last_version_modern }}
CURRENT_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.process_artifacts.outputs.current_version_modern }}
LAST_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.last_version_classic }}
LAST_VERSION_MODERN: ${{ needs.process_artifacts.outputs.last_version_modern }}
run: |
echo "Re-applying $LAST_VERSION_CLASSIC to $CURRENT_VERSION_CLASSIC"
grep -rl "$LAST_VERSION_CLASSIC" ./compiled || echo "No files found with $LAST_VERSION_CLASSIC"
@@ -240,24 +280,31 @@ jobs:
- name: Will commit these changes
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
echo ":"
git status -u
git add .
git status
- name: Check commit message
if: inputs.dry_run
run: |
git fetch origin --quiet
git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:"%B"
- name: Commit changes to branch
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: |
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
run: |
git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
git config --global user.name "${{ github.triggering_actor }}"
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
branch: builds/facebook-www
commit_user_name: ${{ github.triggering_actor }}
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
create_branch: true
git fetch origin --quiet
git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit"
- name: Push changes to branch
if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
run: git push
commit_fbsource_artifacts:
needs: download_artifacts
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.fbsource_branch_count == '0')
needs: [download_artifacts, process_artifacts]
permissions:
# Used to push a commit to builds/facebook-fbsource
contents: write
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.process_artifacts.outputs.fbsource_branch_count == '0')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -270,10 +317,10 @@ jobs:
name: compiled-rn
path: compiled-rn/
- name: Revert version changes
if: needs.download_artifacts.outputs.last_version_rn != ''
if: needs.process_artifacts.outputs.last_version_rn != ''
env:
CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.download_artifacts.outputs.last_version_rn }}
CURRENT_VERSION: ${{ needs.process_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.process_artifacts.outputs.last_version_rn }}
run: |
echo "Reverting $CURRENT_VERSION to $LAST_VERSION"
grep -rl "$CURRENT_VERSION" ./compiled-rn || echo "No files found with $CURRENT_VERSION"
@@ -288,10 +335,10 @@ jobs:
git --no-pager diff -U0 --cached | grep '^[+-]' | head -n 100
echo "===================="
# Ignore REVISION or lines removing @generated headers.
if git diff --cached ':(exclude)*REVISION' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|\- \* @generated SignedSource)" | grep "^[+-]" > /dev/null; then
if git diff --cached ':(exclude)*REVISION' ':(exclude)*/eslint-plugin-react-hooks/package.json' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|\- \* @generated SignedSource)" | grep "^[+-]" > /dev/null; then
echo "Changes detected"
echo "===== Changes ====="
git --no-pager diff --cached ':(exclude)*REVISION' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|\- \* @generated SignedSource)" | grep "^[+-]" | head -n 50
git --no-pager diff --cached ':(exclude)*REVISION' ':(exclude)*/eslint-plugin-react-hooks/package.json' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|\- \* @generated SignedSource)" | grep "^[+-]" | head -n 50
echo "==================="
echo "should_commit=true" >> "$GITHUB_OUTPUT"
else
@@ -299,10 +346,10 @@ jobs:
echo "should_commit=false" >> "$GITHUB_OUTPUT"
fi
- name: Re-apply version changes
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_rn != '')
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.process_artifacts.outputs.last_version_rn != '')
env:
CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.download_artifacts.outputs.last_version_rn }}
CURRENT_VERSION: ${{ needs.process_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.process_artifacts.outputs.last_version_rn }}
run: |
echo "Re-applying $LAST_VERSION to $CURRENT_VERSION"
grep -rl "$LAST_VERSION" ./compiled-rn || echo "No files found with $LAST_VERSION"
@@ -409,15 +456,19 @@ jobs:
run: |
git add .
git status
- name: Check commit message
if: inputs.dry_run
run: |
git fetch origin --quiet
git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:"%B"
- name: Commit changes to branch
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: |
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
run: |
git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
git config --global user.name "${{ github.triggering_actor }}"
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
branch: builds/facebook-fbsource
commit_user_name: ${{ github.triggering_actor }}
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
create_branch: true
git fetch origin --quiet
git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit"
- name: Push changes to branch
if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
run: git push

View File

@@ -4,12 +4,33 @@ on:
pull_request_target:
types: [opened, ready_for_review]
paths-ignore:
- packages/react-devtools**
- compiler/**
- .github/workflows/compiler_**.yml
- .github/workflows/devtools**.yml
permissions: {}
jobs:
check_access:
if: ${{ github.event.pull_request.draft == false }}
runs-on: ubuntu-latest
outputs:
is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
steps:
- run: echo ${{ github.event.pull_request.author_association }}
- name: Check is member or collaborator
id: check_is_member_or_collaborator
if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}
run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
check_maintainer:
if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }}
needs: [check_access]
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
permissions:
# Used by check_maintainer
contents: read
with:
actor: ${{ github.event.pull_request.user.login }}
@@ -19,7 +40,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v6.0.0
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.pull_request.user.login }}

View File

@@ -7,6 +7,8 @@ on:
paths-ignore:
- compiler/**
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
@@ -35,22 +37,29 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "node_modules"
key: runtime-eslint_e2e-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: |
**/node_modules
key: runtime-and-compiler-eslint_e2e-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock', 'fixtures/eslint-v*/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Build plugin
working-directory: fixtures/eslint-v${{ matrix.eslint_major }}
run: node build.mjs
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Install fixture dependencies
working-directory: ./fixtures/eslint-v${{ matrix.eslint_major }}
run: yarn --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Build plugin
working-directory: fixtures/eslint-v${{ matrix.eslint_major }}
run: node build.mjs
- name: Run lint test
working-directory: ./fixtures/eslint-v${{ matrix.eslint_major }}
run: yarn lint

View File

@@ -8,6 +8,8 @@ on:
- main
workflow_dispatch:
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

View File

@@ -13,21 +13,45 @@ on:
dist_tag:
required: true
type: string
enableFailureNotification:
description: 'Whether to notify the team on Discord when the release fails. Useful if this workflow is called from an automation.'
required: false
type: boolean
only_packages:
description: Packages to publish (space separated)
type: string
skip_packages:
description: Packages to NOT publish (space separated)
type: string
dry:
required: true
description: Dry run instead of publish?
type: boolean
default: true
secrets:
DISCORD_WEBHOOK_URL:
description: 'Discord webhook URL to notify on failure. Only required if enableFailureNotification is true.'
required: false
GH_TOKEN:
required: true
NPM_TOKEN:
required: true
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
GH_TOKEN: ${{ github.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
jobs:
publish_prerelease:
name: Publish prelease (${{ inputs.release_channel }}) ${{ inputs.commit_sha }} @${{ inputs.dist_tag }}
runs-on: ubuntu-latest
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -39,14 +63,48 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: cp ./scripts/release/ci-npmrc ~/.npmrc
- run: |
scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }}
cp ./scripts/release/ci-npmrc ~/.npmrc
scripts/release/publish.js --ci --tags ${{ inputs.dist_tag }}
GH_TOKEN=${{ secrets.GH_TOKEN }} scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }}
- name: Check prepared files
run: ls -R build/node_modules
- if: '${{ inputs.only_packages }}'
name: 'Publish ${{ inputs.only_packages }}'
run: |
scripts/release/publish.js \
--ci \
--tags=${{ inputs.dist_tag }} \
--onlyPackages=${{ inputs.only_packages }} ${{ (inputs.dry && '') || '\'}}
${{ inputs.dry && '--dry' || '' }}
- if: '${{ inputs.skip_packages }}'
name: 'Publish all packages EXCEPT ${{ inputs.skip_packages }}'
run: |
scripts/release/publish.js \
--ci \
--tags=${{ inputs.dist_tag }} \
--skipPackages=${{ inputs.skip_packages }} ${{ (inputs.dry && '') || '\'}}
${{ inputs.dry && '--dry' || '' }}
- if: '${{ !inputs.skip_packages && !inputs.only_packages }}'
name: 'Publish all packages'
run: |
scripts/release/publish.js \
--ci \
--tags=${{ inputs.dist_tag }} ${{ (inputs.dry && '') || '\'}}
${{ inputs.dry && '--dry' || '' }}
- name: Notify Discord on failure
if: failure() && inputs.enableFailureNotification == true
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
embed-author-name: "GitHub Actions"
embed-title: '[Runtime] Publish of ${{ inputs.release_channel }}@${{ inputs.dist_tag}} release failed'
embed-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}

View File

@@ -5,15 +5,57 @@ on:
inputs:
prerelease_commit_sha:
required: true
only_packages:
description: Packages to publish (space separated)
type: string
skip_packages:
description: Packages to NOT publish (space separated)
type: string
dry:
required: true
description: Dry run instead of publish?
type: boolean
default: true
experimental_only:
type: boolean
description: Only publish to the experimental tag
default: false
force_notify:
description: Force a Discord notification?
type: boolean
default: false
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
notify:
if: ${{ inputs.force_notify || inputs.dry == false || inputs.dry == 'false' }}
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.sender.login }}
embed-author-url: ${{ github.event.sender.html_url }}
embed-author-icon-url: ${{ github.event.sender.avatar_url }}
embed-title: "⚠️ Publishing ${{ inputs.experimental_only && 'EXPERIMENTAL' || 'CANARY & EXPERIMENTAL' }} release ${{ (inputs.dry && ' (dry run)') || '' }}"
embed-description: |
```json
${{ toJson(inputs) }}
```
embed-url: https://github.com/facebook/react/actions/runs/${{ github.run_id }}
publish_prerelease_canary:
if: ${{ !inputs.experimental_only }}
name: Publish to Canary channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
with:
commit_sha: ${{ inputs.prerelease_commit_sha }}
release_channel: stable
@@ -28,20 +70,33 @@ jobs:
# downstream consumers might still expect that tag. We can remove this
# after some time has elapsed and the change has been communicated.
dist_tag: canary,next
only_packages: ${{ inputs.only_packages }}
skip_packages: ${{ inputs.skip_packages }}
dry: ${{ inputs.dry }}
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish_prerelease_experimental:
name: Publish to Experimental channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
# NOTE: Intentionally running these jobs sequentially because npm
# will sometimes fail if you try to concurrently publish two
# different versions of the same package, even if they use different
# dist tags.
needs: publish_prerelease_canary
# Ensures the job runs even if canary is skipped
if: always()
with:
commit_sha: ${{ inputs.prerelease_commit_sha }}
release_channel: experimental
dist_tag: experimental
only_packages: ${{ inputs.only_packages }}
skip_packages: ${{ inputs.skip_packages }}
dry: ${{ inputs.dry }}
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -5,6 +5,8 @@ on:
# At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
- cron: 10 16 * * 1,2,3,4,5
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -12,16 +14,26 @@ jobs:
publish_prerelease_canary:
name: Publish to Canary channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
with:
commit_sha: ${{ github.sha }}
release_channel: stable
dist_tag: canary,next
enableFailureNotification: true
dry: false
secrets:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish_prerelease_experimental:
name: Publish to Experimental channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
# NOTE: Intentionally running these jobs sequentially because npm
# will sometimes fail if you try to concurrently publish two
# different versions of the same package, even if they use different
@@ -31,5 +43,9 @@ jobs:
commit_sha: ${{ github.sha }}
release_channel: experimental
dist_tag: experimental
enableFailureNotification: true
dry: false
secrets:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -31,11 +31,12 @@ on:
type: boolean
default: false
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
GH_TOKEN: ${{ github.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
jobs:
@@ -44,7 +45,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v6.0.0
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.sender.login }}
@@ -71,13 +72,15 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: cp ./scripts/release/ci-npmrc ~/.npmrc
- if: '${{ inputs.only_packages }}'
name: 'Prepare ${{ inputs.only_packages }} from NPM'
@@ -107,7 +110,7 @@ jobs:
--tags=${{ inputs.tags }} \
--publishVersion=${{ inputs.version_to_publish }} \
--onlyPackages=${{ inputs.only_packages }} ${{ (inputs.dry && '') || '\'}}
${{ inputs.dry && '--dry'}}
${{ inputs.dry && '--dry' || '' }}
- if: '${{ inputs.skip_packages }}'
name: 'Publish all packages EXCEPT ${{ inputs.skip_packages }}'
run: |
@@ -116,7 +119,7 @@ jobs:
--tags=${{ inputs.tags }} \
--publishVersion=${{ inputs.version_to_publish }} \
--skipPackages=${{ inputs.skip_packages }} ${{ (inputs.dry && '') || '\'}}
${{ inputs.dry && '--dry'}}
${{ inputs.dry && '--dry' || '' }}
- name: Archive released package for debugging
uses: actions/upload-artifact@v4
with:

View File

@@ -6,14 +6,12 @@ on:
actor:
required: true
type: string
is_remote:
required: false
type: boolean
default: false
outputs:
is_core_team:
value: ${{ jobs.check_maintainer.outputs.is_core_team }}
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -22,10 +20,12 @@ env:
jobs:
check_maintainer:
runs-on: ubuntu-latest
permissions:
# We fetch the contents of the MAINTAINERS file
contents: read
outputs:
is_core_team: ${{ steps.check_if_actor_is_maintainer.outputs.result }}
steps:
- uses: actions/checkout@v4
- name: Check if actor is maintainer
id: check_if_actor_is_maintainer
uses: actions/github-script@v7
@@ -33,33 +33,20 @@ jobs:
script: |
const fs = require('fs');
const actor = '${{ inputs.actor }}';
let isRemote = ${{ inputs.is_remote }};
if (typeof isRemote === 'string') {
isRemote = isRemote === 'true';
const res = await github.rest.repos.getContent({
owner: 'facebook',
repo: 'react',
path: 'MAINTAINERS',
ref: 'main',
headers: { Accept: 'application/vnd.github+json' }
});
if (res.status !== 200) {
console.error(res);
throw new Error('Unable to fetch MAINTAINERS file');
}
if (typeof isRemote !== 'boolean') {
throw new Error(`Invalid \`isRemote\` input. Expected a boolean, got: ${isRemote}`);
}
let content = null;
if (isRemote === true) {
const res = await github.rest.repos.getContent({
owner: 'facebook',
repo: 'react',
path: 'MAINTAINERS',
ref: 'main',
headers: { Accept: 'application/vnd.github+json' }
});
if (res.status !== 200) {
console.error(res);
throw new Error('Unable to fetch MAINTAINERS file');
}
content = Buffer.from(res.data.content, 'base64').toString();
} else {
content = await fs.readFileSync('./MAINTAINERS', { encoding: 'utf8' });
}
if (content === null) {
throw new Error('Unable to retrieve local or http MAINTAINERS file');
content = Buffer.from(res.data.content, 'base64').toString();
if (content == null || typeof content !== 'string') {
throw new Error('Unable to retrieve MAINTAINERS file');
}
const maintainers = new Set(content.split('\n'));

View File

@@ -0,0 +1,41 @@
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy
name: (Shared) Cleanup Merged Branch Caches
on:
pull_request:
types:
- closed
workflow_dispatch:
inputs:
pr_number:
required: true
type: string
permissions: {}
jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
# `actions:write` permission is required to delete caches
# See also: https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28#delete-a-github-actions-cache-for-a-repository-using-a-cache-id
actions: write
contents: read
steps:
- name: Cleanup
run: |
echo "Fetching list of cache key"
cacheKeysForPR=$(gh cache list --ref $BRANCH --limit 100 --json id --jq '.[].id')
## Setting this to not fail the workflow while deleting cache keys.
set +e
for cacheKey in $cacheKeysForPR
do
gh cache delete $cacheKey
echo "Deleting $cacheKey"
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
BRANCH: refs/pull/${{ inputs.pr_number || github.event.pull_request.number }}/merge

View File

@@ -0,0 +1,36 @@
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy
name: (Shared) Cleanup Stale Branch Caches
on:
schedule:
# Every 6 hours
- cron: 0 */6 * * *
workflow_dispatch:
permissions: {}
jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
# `actions:write` permission is required to delete caches
# See also: https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28#delete-a-github-actions-cache-for-a-repository-using-a-cache-id
actions: write
contents: read
steps:
- name: Cleanup
run: |
echo "Fetching list of cache keys"
cacheKeysForPR=$(gh cache list --limit 100 --json id,ref --jq '.[] | select(.ref != "refs/heads/main") | .id')
## Setting this to not fail the workflow while deleting cache keys.
set +e
for cacheKey in $cacheKeysForPR
do
gh cache delete $cacheKey
echo "Deleting $cacheKey"
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}

View File

@@ -5,6 +5,8 @@ on:
branches:
- 'builds/facebook-**'
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -13,6 +15,10 @@ env:
jobs:
close_pr:
runs-on: ubuntu-latest
permissions:
# Used to create a review and close PRs
pull-requests: write
contents: write
steps:
- name: Close PR
uses: actions/github-script@v7

View File

@@ -2,6 +2,9 @@ name: (Shared) Label Core Team PRs
on:
pull_request_target:
types: [opened]
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -9,8 +12,24 @@ env:
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
check_access:
runs-on: ubuntu-latest
outputs:
is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
steps:
- run: echo ${{ github.event.pull_request.author_association }}
- name: Check is member or collaborator
id: check_is_member_or_collaborator
if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}
run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
check_maintainer:
if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }}
needs: [check_access]
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
permissions:
# Used by check_maintainer
contents: read
with:
actor: ${{ github.event.pull_request.user.login }}
@@ -18,6 +37,11 @@ jobs:
if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }}
runs-on: ubuntu-latest
needs: check_maintainer
permissions:
# Used to add labels on issues
issues: write
# Used to add labels on PRs
pull-requests: write
steps:
- name: Label PR as React Core Team
uses: actions/github-script@v7

View File

@@ -5,6 +5,8 @@ on:
branches: [main]
pull_request:
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
@@ -27,12 +29,15 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn prettier-check
eslint:
@@ -47,12 +52,15 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: node ./scripts/tasks/eslint
check_license:
@@ -67,12 +75,15 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: ./scripts/ci/check_license.sh
test_print_warnings:
@@ -87,10 +98,13 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: ./scripts/ci/test_print_warnings.sh

View File

@@ -6,6 +6,11 @@ on:
- cron: '0 * * * *'
workflow_dispatch:
permissions:
# https://github.com/actions/stale/tree/v9/?tab=readme-ov-file#recommended-permissions
issues: write
pull-requests: write
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

1
.gitignore vendored
View File

@@ -23,6 +23,7 @@ chrome-user-data
.vscode
*.swp
*.swo
/tmp
packages/react-devtools-core/dist
packages/react-devtools-extensions/chrome/build

2
.nvmrc
View File

@@ -1 +1 @@
v18.20.1
v20.19.0

View File

@@ -3,13 +3,12 @@
const {esNextPaths} = require('./scripts/shared/pathsByLanguageVersion');
module.exports = {
plugins: ['prettier-plugin-hermes-parser'],
bracketSpacing: false,
singleQuote: true,
bracketSameLine: true,
trailingComma: 'es5',
printWidth: 80,
parser: 'hermes',
parser: 'flow',
arrowParens: 'avoid',
overrides: [
{

View File

@@ -1,18 +0,0 @@
## March 22, 2024 (18.3.0-canary-670811593-20240322)
## React
- Added `useActionState` to replace `useFormState` and added `pending` value ([#28491](https://github.com/facebook/react/pull/28491)).
## October 5, 2023 (18.3.0-canary-546178f91-20231005)
### React
- Added support for async functions to be passed to `startTransition`.
- `useTransition` now triggers the nearest error boundary instead of a global error.
- Added `useOptimistic`, a new Hook for handling optimistic UI updates. It optimistically updates the UI before receiving confirmation from a server or external source.
### React DOM
- Added support for passing async functions to the `action` prop on `<form>`. When the function passed to `action` is marked with [`'use server'`](https://react.dev/reference/react/use-server), the form is [progressively enhanced](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement).
- Added `useFormStatus`, a new Hook for checking the submission state of a form.
- Added `useFormState`, a new Hook for updating state upon form submission. When the function passed to `useFormState` is marked with [`'use server'`](https://react.dev/reference/react/use-server), the update is [progressively enhanced](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement).

View File

@@ -1,3 +1,128 @@
## 19.2.0 (October 1st, 2025)
Below is a list of all new features, APIs, and bug fixes.
Read the [React 19.2 release post](https://react.dev/blog/2025/10/01/react-19-2) for more information.
### New React Features
- [`<Activity>`](https://react.dev/reference/react/Activity): A new API to hide and restore the UI and internal state of its children.
- [`useEffectEvent`](https://react.dev/reference/react/useEffectEvent) is a React Hook that lets you extract non-reactive logic into an [Effect Event](https://react.dev/learn/separating-events-from-effects#declaring-an-effect-event).
- [`cacheSignal`](https://react.dev/reference/react/cacheSignal) (for RSCs) lets your know when the `cache()` lifetime is over.
- [React Performance tracks](https://react.dev/reference/dev-tools/react-performance-tracks) appear on the Performance panels timeline in your browser developer tools
### New React DOM Features
- Added resume APIs for partial pre-rendering with Web Streams:
- [`resume`](https://react.dev/reference/react-dom/server/resume): to resume a prerender to a stream.
- [`resumeAndPrerender`](https://react.dev/reference/react-dom/static/resumeAndPrerender): to resume a prerender to HTML.
- Added resume APIs for partial pre-rendering with Node Streams:
- [`resumeToPipeableStream`](https://react.dev/reference/react-dom/server/resumeToPipeableStream): to resume a prerender to a stream.
- [`resumeAndPrerenderToNodeStream`](https://react.dev/reference/react-dom/static/resumeAndPrerenderToNodeStream): to resume a prerender to HTML.
- Updated [`prerender`](https://react.dev/reference/react-dom/static/prerender) APIs to return a `postponed` state that can be passed to the `resume` APIs.
### Notable changes
- React DOM now batches suspense boundary reveals, matching the behavior of client side rendering. This change is especially noticeable when animating the reveal of Suspense boundaries e.g. with the upcoming `<ViewTransition>` Component. React will batch as much reveals as possible before the first paint while trying to hit popular first-contentful paint metrics.
- Add Node Web Streams (`prerender`, `renderToReadableStream`) to server-side-rendering APIs for Node.js
- Use underscore instead of `:` IDs generated by useId
### All Changes
#### React
- `<Activity />` was developed over many years, starting before `ClassComponent.setState` (@acdlite @sebmarkbage and many others)
- Stringify context as "SomeContext" instead of "SomeContext.Provider" (@kassens [#33507](https://github.com/facebook/react/pull/33507))
- Include stack of cause of React instrumentation errors with `%o` placeholder (@eps1lon [#34198](https://github.com/facebook/react/pull/34198))
- Fix infinite `useDeferredValue` loop in popstate event (@acdlite [#32821](https://github.com/facebook/react/pull/32821))
- Fix a bug when an initial value was passed to `useDeferredValue` (@acdlite [#34376](https://github.com/facebook/react/pull/34376))
- Fix a crash when submitting forms with Client Actions (@sebmarkbage [#33055](https://github.com/facebook/react/pull/33055))
- Hide/unhide the content of dehydrated suspense boundaries if they resuspend (@sebmarkbage [#32900](https://github.com/facebook/react/pull/32900))
- Avoid stack overflow on wide trees during Hot Reload (@sophiebits [#34145](https://github.com/facebook/react/pull/34145))
- Improve Owner and Component stacks in various places (@sebmarkbage, @eps1lon: [#33629](https://github.com/facebook/react/pull/33629), [#33724](https://github.com/facebook/react/pull/33724), [#32735](https://github.com/facebook/react/pull/32735), [#33723](https://github.com/facebook/react/pull/33723))
- Add `cacheSignal` (@sebmarkbage [#33557](https://github.com/facebook/react/pull/33557))
#### React DOM
- Block on Suspensey Fonts during reveal of server-side-rendered content (@sebmarkbage [#33342](https://github.com/facebook/react/pull/33342))
- Use underscore instead of `:` for IDs generated by `useId` (@sebmarkbage, @eps1lon: [#32001](https://github.com/facebook/react/pull/32001), [https://github.com/facebook/react/pull/33342](https://github.com/facebook/react/pull/33342)[#33099](https://github.com/facebook/react/pull/33099), [#33422](https://github.com/facebook/react/pull/33422))
- Stop warning when ARIA 1.3 attributes are used (@Abdul-Omira [#34264](https://github.com/facebook/react/pull/34264))
- Allow `nonce` to be used on hoistable styles (@Andarist [#32461](https://github.com/facebook/react/pull/32461))
- Warn for using a React owned node as a Container if it also has text content (@sebmarkbage [#32774](https://github.com/facebook/react/pull/32774))
- s/HTML/text for for error messages if text hydration mismatches (@rickhanlonii [#32763](https://github.com/facebook/react/pull/32763))
- Fix a bug with `React.use` inside `React.lazy`\-ed Component (@hi-ogawa [#33941](https://github.com/facebook/react/pull/33941))
- Enable the `progressiveChunkSize` option for server-side-rendering APIs (@sebmarkbage [#33027](https://github.com/facebook/react/pull/33027))
- Fix a bug with deeply nested Suspense inside Suspense fallback when server-side-rendering (@gnoff [#33467](https://github.com/facebook/react/pull/33467))
- Avoid hanging when suspending after aborting while rendering (@gnoff [#34192](https://github.com/facebook/react/pull/34192))
- Add Node Web Streams to server-side-rendering APIs for Node.js (@sebmarkbage [#33475](https://github.com/facebook/react/pull/33475))
#### React Server Components
- Preload `<img>` and `<link>` using hints before they're rendered (@sebmarkbage [#34604](https://github.com/facebook/react/pull/34604))
- Log error if production elements are rendered during development (@eps1lon [#34189](https://github.com/facebook/react/pull/34189))
- Fix a bug when returning a Temporary reference (e.g. a Client Reference) from Server Functions (@sebmarkbage [#34084](https://github.com/facebook/react/pull/34084), @denk0403 [#33761](https://github.com/facebook/react/pull/33761))
- Pass line/column to `filterStackFrame` (@eps1lon [#33707](https://github.com/facebook/react/pull/33707))
- Support Async Modules in Turbopack Server References (@lubieowoce [#34531](https://github.com/facebook/react/pull/34531))
- Add support for .mjs file extension in Webpack (@jennyscript [#33028](https://github.com/facebook/react/pull/33028))
- Fix a wrong missing key warning (@unstubbable [#34350](https://github.com/facebook/react/pull/34350))
- Make console log resolve in predictable order (@sebmarkbage [#33665](https://github.com/facebook/react/pull/33665))
#### React Reconciler
- [createContainer](https://github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiberReconciler.js#L255-L261) and [createHydrationContainer](https://github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiberReconciler.js#L305-L312) had their parameter order adjusted after `on*` handlers to account for upcoming experimental APIs
## 19.1.1 (July 28, 2025)
### React
* Fixed Owner Stacks to work with ES2015 function.name semantics ([#33680](https://github.com/facebook/react/pull/33680) by @hoxyq)
## 19.1.0 (March 28, 2025)
### Owner Stack
An Owner Stack is a string representing the components that are directly responsible for rendering a particular component. You can log Owner Stacks when debugging or use Owner Stacks to enhance error overlays or other development tools. Owner Stacks are only available in development builds. Component Stacks in production are unchanged.
* An Owner Stack is a development-only stack trace that helps identify which components are responsible for rendering a particular component. An Owner Stack is distinct from a Component Stacks, which shows the hierarchy of components leading to an error.
* The [captureOwnerStack API](https://react.dev/reference/react/captureOwnerStack) is only available in development mode and returns a Owner Stack, if available. The API can be used to enhance error overlays or log component relationships when debugging. [#29923](https://github.com/facebook/react/pull/29923), [#32353](https://github.com/facebook/react/pull/32353), [#30306](https://github.com/facebook/react/pull/30306),
[#32538](https://github.com/facebook/react/pull/32538), [#32529](https://github.com/facebook/react/pull/32529), [#32538](https://github.com/facebook/react/pull/32538)
### React
* Enhanced support for Suspense boundaries to be used anywhere, including the client, server, and during hydration. [#32069](https://github.com/facebook/react/pull/32069), [#32163](https://github.com/facebook/react/pull/32163), [#32224](https://github.com/facebook/react/pull/32224), [#32252](https://github.com/facebook/react/pull/32252)
* Reduced unnecessary client rendering through improved hydration scheduling [#31751](https://github.com/facebook/react/pull/31751)
* Increased priority of client rendered Suspense boundaries [#31776](https://github.com/facebook/react/pull/31776)
* Fixed frozen fallback states by rendering unfinished Suspense boundaries on the client. [#31620](https://github.com/facebook/react/pull/31620)
* Reduced garbage collection pressure by improving Suspense boundary retries. [#31667](https://github.com/facebook/react/pull/31667)
* Fixed erroneous “Waiting for Paint” log when the passive effect phase was not delayed [#31526](https://github.com/facebook/react/pull/31526)
* Fixed a regression causing key warnings for flattened positional children in development mode. [#32117](https://github.com/facebook/react/pull/32117)
* Updated `useId` to use valid CSS selectors, changing format from `:r123:` to `«r123»`. [#32001](https://github.com/facebook/react/pull/32001)
* Added a dev-only warning for null/undefined created in useEffect, useInsertionEffect, and useLayoutEffect. [#32355](https://github.com/facebook/react/pull/32355)
* Fixed a bug where dev-only methods were exported in production builds. React.act is no longer available in production builds. [#32200](https://github.com/facebook/react/pull/32200)
* Improved consistency across prod and dev to improve compatibility with Google Closure Compiler and bindings [#31808](https://github.com/facebook/react/pull/31808)
* Improve passive effect scheduling for consistent task yielding. [#31785](https://github.com/facebook/react/pull/31785)
* Fixed asserts in React Native when passChildrenWhenCloningPersistedNodes is enabled for OffscreenComponent rendering. [#32528](https://github.com/facebook/react/pull/32528)
* Fixed component name resolution for Portal [#32640](https://github.com/facebook/react/pull/32640)
* Added support for beforetoggle and toggle events on the dialog element. [#32479](https://github.com/facebook/react/pull/32479)
### React DOM
* Fixed double warning when the `href` attribute is an empty string [#31783](https://github.com/facebook/react/pull/31783)
* Fixed an edge case where `getHoistableRoot()` didnt work properly when the container was a Document [#32321](https://github.com/facebook/react/pull/32321)
* Removed support for using HTML comments (e.g. `<!-- -->`) as a DOM container. [#32250](https://github.com/facebook/react/pull/32250)
* Added support for `<script>` and `<template>` tags to be nested within `<select>` tags. [#31837](https://github.com/facebook/react/pull/31837)
* Fixed responsive images to be preloaded as HTML instead of headers [#32445](https://github.com/facebook/react/pull/32445)
### use-sync-external-store
* Added `exports` field to `package.json` for `use-sync-external-store` to support various entrypoints. [#25231](https://github.com/facebook/react/pull/25231)
### React Server Components
* Added `unstable_prerender`, a new experimental API for prerendering React Server Components on the server [#31724](https://github.com/facebook/react/pull/31724)
* Fixed an issue where streams would hang when receiving new chunks after a global error [#31840](https://github.com/facebook/react/pull/31840), [#31851](https://github.com/facebook/react/pull/31851)
* Fixed an issue where pending chunks were counted twice. [#31833](https://github.com/facebook/react/pull/31833)
* Added support for streaming in edge environments [#31852](https://github.com/facebook/react/pull/31852)
* Added support for sending custom error names from a server so that they are available in the client for console replaying. [#32116](https://github.com/facebook/react/pull/32116)
* Updated the server component wire format to remove IDs for hints and console.log because they have no return value [#31671](https://github.com/facebook/react/pull/31671)
* Exposed `registerServerReference` in client builds to handle server references in different environments. [#32534](https://github.com/facebook/react/pull/32534)
* Added react-server-dom-parcel package which integrates Server Components with the [Parcel bundler](https://parceljs.org/) [#31725](https://github.com/facebook/react/pull/31725), [#32132](https://github.com/facebook/react/pull/32132), [#31799](https://github.com/facebook/react/pull/31799), [#32294](https://github.com/facebook/react/pull/32294), [#31741](https://github.com/facebook/react/pull/31741)
## 19.0.0 (December 5, 2024)
Below is a list of all new features, APIs, deprecations, and breaking changes. Read [React 19 release post](https://react.dev/blog/2024/04/25/react-19) and [React 19 upgrade guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide) for more information.

View File

@@ -1,5 +1,6 @@
acdlite
eps1lon
EugeneChoi4
gaearon
gnoff
unstubbable

View File

@@ -7,18 +7,18 @@
//
// The @latest channel uses the version as-is, e.g.:
//
// 19.1.0
// 19.3.0
//
// The @canary channel appends additional information, with the scheme
// <version>-<label>-<commit_sha>, e.g.:
//
// 19.1.0-canary-a1c2d3e4
// 19.3.0-canary-a1c2d3e4
//
// The @experimental channel doesn't include a version, only a date and a sha, e.g.:
//
// 0.0.0-experimental-241c4467e-20200129
const ReactVersion = '19.1.0';
const ReactVersion = '19.3.0';
// The label used by the @canary channel. Represents the upcoming release's
// stability. Most of the time, this will be "canary", but we may temporarily
@@ -33,8 +33,8 @@ const canaryChannelLabel = 'canary';
const rcNumber = 0;
const stablePackages = {
'eslint-plugin-react-hooks': '5.2.0',
'jest-react': '0.17.0',
'eslint-plugin-react-hooks': '7.1.0',
'jest-react': '0.18.0',
react: ReactVersion,
'react-art': ReactVersion,
'react-dom': ReactVersion,
@@ -42,12 +42,12 @@ const stablePackages = {
'react-server-dom-turbopack': ReactVersion,
'react-server-dom-parcel': ReactVersion,
'react-is': ReactVersion,
'react-reconciler': '0.32.0',
'react-refresh': '0.17.0',
'react-reconciler': '0.34.0',
'react-refresh': '0.19.0',
'react-test-renderer': ReactVersion,
'use-subscription': '1.11.0',
'use-sync-external-store': '1.5.0',
scheduler: '0.26.0',
'use-subscription': '1.13.0',
'use-sync-external-store': '1.7.0',
scheduler: '0.28.0',
};
// These packages do not exist in the @canary or @latest channel, only

View File

@@ -0,0 +1,19 @@
'use strict';
/**
* HACK: @poteto React Compiler inlines Zod in its build artifact. Zod spreads values passed to .map
* which causes issues in @babel/plugin-transform-spread in loose mode, as it will result in
* {undefined: undefined} which fails to parse.
*
* [@babel/plugin-transform-block-scoping', {throwIfClosureRequired: true}] also causes issues with
* the built version of the compiler. The minimal set of plugins needed for this file is reexported
* from babel.config-ts.
*
* I will remove this hack later when we move eslint-plugin-react-hooks into the compiler directory.
**/
const baseConfig = require('./babel.config-ts');
module.exports = {
plugins: baseConfig.plugins,
};

View File

@@ -7,6 +7,9 @@ module.exports = {
plugins: [
'@babel/plugin-syntax-jsx',
'@babel/plugin-transform-flow-strip-types',
['@babel/plugin-transform-class-properties', {loose: true}],
['@babel/plugin-transform-private-methods', {loose: true}],
'@babel/plugin-transform-classes',
],
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],

View File

@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(node scripts/enable-feature-flag.js:*)"
],
"deny": [],
"ask": []
}
}

14
compiler/.gitignore vendored
View File

@@ -1,28 +1,14 @@
.DS_Store
.spr.yml
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
node_modules
.watchmanconfig
.watchman-cookie-*
dist
.vscode
!packages/playground/.vscode
.spr.yml
testfilter.txt
bundle-oss.sh
# forgive
*.vsix
.vscode-test

65
compiler/CHANGELOG.md Normal file
View File

@@ -0,0 +1,65 @@
## 19.1.0-rc.2 (May 14, 2025)
## babel-plugin-react-compiler
* Fix for string attribute values with emoji [#33096](https://github.com/facebook/react/pull/33096) by [@josephsavona](https://github.com/josephsavona)
## 19.1.0-rc.1 (April 21, 2025)
## eslint-plugin-react-hooks
* Temporarily disable ref access in render validation [#32839](https://github.com/facebook/react/pull/32839) by [@poteto](https://github.com/poteto)
* Fix type error with recommended config [#32666](https://github.com/facebook/react/pull/32666) by [@niklasholm](https://github.com/niklasholm)
* Merge rule from eslint-plugin-react-compiler into `react-hooks` plugin [#32416](https://github.com/facebook/react/pull/32416) by [@michaelfaith](https://github.com/michaelfaith)
* Add dev dependencies for typescript migration [#32279](https://github.com/facebook/react/pull/32279) by [@michaelfaith](https://github.com/michaelfaith)
* Support v9 context api [#32045](https://github.com/facebook/react/pull/32045) by [@michaelfaith](https://github.com/michaelfaith)
* Support eslint 8+ flat plugin syntax out of the box for eslint-plugin-react-compiler [#32120](https://github.com/facebook/react/pull/32120) by [@orta](https://github.com/orta)
## babel-plugin-react-compiler
* Support satisfies operator [#32742](https://github.com/facebook/react/pull/32742) by [@rodrigofariow](https://github.com/rodrigofariow)
* Fix inferEffectDependencies lint false positives [#32769](https://github.com/facebook/react/pull/32769) by [@mofeiZ](https://github.com/mofeiZ)
* Fix hoisting of let declarations [#32724](https://github.com/facebook/react/pull/32724) by [@mofeiZ](https://github.com/mofeiZ)
* Avoid failing builds when import specifiers conflict or shadow vars [#32663](https://github.com/facebook/react/pull/32663) by [@mofeiZ](https://github.com/mofeiZ)
* Optimize components declared with arrow function and implicit return and `compilationMode: 'infer'` [#31792](https://github.com/facebook/react/pull/31792) by [@dimaMachina](https://github.com/dimaMachina)
* Validate static components [#32683](https://github.com/facebook/react/pull/32683) by [@josephsavona](https://github.com/josephsavona)
* Hoist dependencies from functions more conservatively [#32616](https://github.com/facebook/react/pull/32616) by [@mofeiZ](https://github.com/mofeiZ)
* Implement NumericLiteral as ObjectPropertyKey [#31791](https://github.com/facebook/react/pull/31791) by [@dimaMachina](https://github.com/dimaMachina)
* Avoid bailouts when inserting gating [#32598](https://github.com/facebook/react/pull/32598) by [@mofeiZ](https://github.com/mofeiZ)
* Stop bailing out early for hoisted gated functions [#32597](https://github.com/facebook/react/pull/32597) by [@mofeiZ](https://github.com/mofeiZ)
* Add shape for Array.from [#32522](https://github.com/facebook/react/pull/32522) by [@mofeiZ](https://github.com/mofeiZ)
* Patch array and argument spread mutability [#32521](https://github.com/facebook/react/pull/32521) by [@mofeiZ](https://github.com/mofeiZ)
* Make CompilerError compatible with reflection [#32539](https://github.com/facebook/react/pull/32539) by [@poteto](https://github.com/poteto)
* Add simple walltime measurement [#32331](https://github.com/facebook/react/pull/32331) by [@poteto](https://github.com/poteto)
* Improve error messages for unhandled terminal and instruction kinds [#32324](https://github.com/facebook/react/pull/32324) by [@inottn](https://github.com/inottn)
* Handle TSInstantiationExpression in lowerExpression [#32302](https://github.com/facebook/react/pull/32302) by [@inottn](https://github.com/inottn)
* Fix invalid Array.map type [#32095](https://github.com/facebook/react/pull/32095) by [@mofeiZ](https://github.com/mofeiZ)
* Patch for JSX escape sequences in @babel/generator [#32131](https://github.com/facebook/react/pull/32131) by [@mofeiZ](https://github.com/mofeiZ)
* `JSXText` emits incorrect with bracket [#32138](https://github.com/facebook/react/pull/32138) by [@himself65](https://github.com/himself65)
* Validation against calling impure functions [#31960](https://github.com/facebook/react/pull/31960) by [@josephsavona](https://github.com/josephsavona)
* Always target node [#32091](https://github.com/facebook/react/pull/32091) by [@poteto](https://github.com/poteto)
* Patch compilationMode:infer object method edge case [#32055](https://github.com/facebook/react/pull/32055) by [@mofeiZ](https://github.com/mofeiZ)
* Generate ts defs [#31994](https://github.com/facebook/react/pull/31994) by [@poteto](https://github.com/poteto)
* Relax react peer dep requirement [#31915](https://github.com/facebook/react/pull/31915) by [@poteto](https://github.com/poteto)
* Allow type cast expressions with refs [#31871](https://github.com/facebook/react/pull/31871) by [@josephsavona](https://github.com/josephsavona)
* Add shape for global Object.keys [#31583](https://github.com/facebook/react/pull/31583) by [@mofeiZ](https://github.com/mofeiZ)
* Optimize method calls w props receiver [#31775](https://github.com/facebook/react/pull/31775) by [@josephsavona](https://github.com/josephsavona)
* Fix dropped ref with spread props in InlineJsxTransform [#31726](https://github.com/facebook/react/pull/31726) by [@jackpope](https://github.com/jackpope)
* Support for non-declatation for in/of iterators [#31710](https://github.com/facebook/react/pull/31710) by [@mvitousek](https://github.com/mvitousek)
* Support for context variable loop iterators [#31709](https://github.com/facebook/react/pull/31709) by [@mvitousek](https://github.com/mvitousek)
* Replace deprecated dependency in `eslint-plugin-react-compiler` [#31629](https://github.com/facebook/react/pull/31629) by [@rakleed](https://github.com/rakleed)
* Support enableRefAsProp in jsx transform [#31558](https://github.com/facebook/react/pull/31558) by [@jackpope](https://github.com/jackpope)
* Fix: ref.current now correctly reactive [#31521](https://github.com/facebook/react/pull/31521) by [@mofeiZ](https://github.com/mofeiZ)
* Outline JSX with non-jsx children [#31442](https://github.com/facebook/react/pull/31442) by [@gsathya](https://github.com/gsathya)
* Outline jsx with duplicate attributes [#31441](https://github.com/facebook/react/pull/31441) by [@gsathya](https://github.com/gsathya)
* Store original and new prop names [#31440](https://github.com/facebook/react/pull/31440) by [@gsathya](https://github.com/gsathya)
* Stabilize compiler output: sort deps and decls by name [#31362](https://github.com/facebook/react/pull/31362) by [@mofeiZ](https://github.com/mofeiZ)
* Bugfix for hoistable deps for nested functions [#31345](https://github.com/facebook/react/pull/31345) by [@mofeiZ](https://github.com/mofeiZ)
* Remove compiler runtime-compat fixture library [#31430](https://github.com/facebook/react/pull/31430) by [@poteto](https://github.com/poteto)
* Wrap inline jsx transform codegen in conditional [#31267](https://github.com/facebook/react/pull/31267) by [@jackpope](https://github.com/jackpope)
* Check if local identifier is a hook when resolving globals [#31384](https://github.com/facebook/react/pull/31384) by [@poteto](https://github.com/poteto)
* Handle member expr as computed property [#31344](https://github.com/facebook/react/pull/31344) by [@gsathya](https://github.com/gsathya)
* Fix to ref access check to ban ref?.current [#31360](https://github.com/facebook/react/pull/31360) by [@mvitousek](https://github.com/mvitousek)
* InlineJSXTransform transforms jsx inside function expressions [#31282](https://github.com/facebook/react/pull/31282) by [@josephsavona](https://github.com/josephsavona)
## Other
* Add shebang to banner [#32225](https://github.com/facebook/react/pull/32225) by [@Jeremy-Hibiki](https://github.com/Jeremy-Hibiki)
* remove terser from react-compiler-runtime build [#31326](https://github.com/facebook/react/pull/31326) by [@henryqdineen](https://github.com/henryqdineen)

View File

@@ -1,5 +1,4 @@
import { c as _c } from "react/compiler-runtime"; // 
        @compilationMode(all)
import { c as _c } from "react/compiler-runtime"; // @compilationMode:"all"
function nonReactFn() {
  const $ = _c(1);
  let t0;

View File

@@ -1,4 +1,4 @@
// @compilationMode(infer)
// @compilationMode:"infer"
function nonReactFn() {
  return {};
}

View File

@@ -0,0 +1,5 @@
import type { PluginOptions } from 
'babel-plugin-react-compiler/dist';
({
  //compilationMode: "all"
} satisfies PluginOptions);

View File

@@ -0,0 +1,14 @@
import { c as _c } from "react/compiler-runtime";
export default function TestComponent(t0) {
const $ = _c(2);
const { x } = t0;
let t1;
if ($[0] !== x || true) {
t1 = <Button>{x}</Button>;
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}

View File

@@ -5,14 +5,32 @@
* LICENSE file in the root directory of this source tree.
*/
import {expect, test} from '@playwright/test';
import {expect, test, type Page} from '@playwright/test';
import {encodeStore, type Store} from '../../lib/stores';
import {defaultConfig} from '../../lib/defaultStore';
import {format} from 'prettier';
function isMonacoLoaded(): boolean {
return (
typeof window['MonacoEnvironment'] !== 'undefined' &&
window['__MONACO_LOADED__'] === true
);
}
function formatPrint(data: Array<string>): Promise<string> {
return format(data.join(''), {parser: 'babel'});
}
async function expandConfigs(page: Page): Promise<void> {
const expandButton = page.locator('[title="Expand config editor"]');
await expandButton.click();
await page.waitForSelector('.monaco-editor-config', {state: 'visible'});
}
const TEST_SOURCE = `export default function TestComponent({ x }) {
return <Button>{x}</Button>;
}`;
const TEST_CASE_INPUTS = [
{
name: 'module-scope-use-memo',
@@ -85,7 +103,7 @@ function useFoo(propVal: {+baz: number}) {
},
{
name: 'compilationMode-infer',
input: `// @compilationMode(infer)
input: `// @compilationMode:"infer"
function nonReactFn() {
return {};
}
@@ -94,7 +112,7 @@ function nonReactFn() {
},
{
name: 'compilationMode-all',
input: `// @compilationMode(all)
input: `// @compilationMode:"all"
function nonReactFn() {
return {};
}
@@ -105,6 +123,7 @@ function nonReactFn() {
test('editor should open successfully', async ({page}) => {
await page.goto(`/`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
await page.screenshot({
fullPage: true,
path: 'test-results/00-fresh-page.png',
@@ -113,13 +132,13 @@ test('editor should open successfully', async ({page}) => {
test('editor should compile from hash successfully', async ({page}) => {
const store: Store = {
source: `export default function TestComponent({ x }) {
return <Button>{x}</Button>;
}
`,
source: TEST_SOURCE,
config: defaultConfig,
showInternals: false,
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
// User input from hash compiles
await page.screenshot({
@@ -127,7 +146,7 @@ test('editor should compile from hash successfully', async ({page}) => {
path: 'test-results/01-compiles-from-hash.png',
});
const text =
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
(await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];
const output = await formatPrint(text);
expect(output).not.toEqual('');
@@ -136,43 +155,212 @@ test('editor should compile from hash successfully', async ({page}) => {
test('reset button works', async ({page}) => {
const store: Store = {
source: `export default function TestComponent({ x }) {
return <Button>{x}</Button>;
}
`,
source: TEST_SOURCE,
config: defaultConfig,
showInternals: false,
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
// Reset button works
page.on('dialog', dialog => dialog.accept());
await page.getByRole('button', {name: 'Reset'}).click();
await expandConfigs(page);
await page.screenshot({
fullPage: true,
path: 'test-results/02-reset-button-works.png',
});
const text =
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
(await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];
const output = await formatPrint(text);
const configText =
(await page.locator('.monaco-editor-config').allInnerTexts()) ?? [];
const configOutput = configText.join('');
expect(output).not.toEqual('');
expect(output).toMatchSnapshot('02-default-output.txt');
expect(configOutput).not.toEqual('');
expect(configOutput).toMatchSnapshot('default-config.txt');
});
test('defaults load when only source is in Store', async ({page}) => {
// Test for backwards compatibility
const partial = {
source: TEST_SOURCE,
};
const hash = encodeStore(partial as Store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
await expandConfigs(page);
await page.screenshot({
fullPage: true,
path: 'test-results/03-missing-defaults.png',
});
// Config editor has default config
const configText =
(await page.locator('.monaco-editor-config').allInnerTexts()) ?? [];
const configOutput = configText.join('');
expect(configOutput).not.toEqual('');
expect(configOutput).toMatchSnapshot('default-config.txt');
const checkbox = page.locator('label.show-internals');
await expect(checkbox).not.toBeChecked();
const ssaTab = page.locator('text=SSA');
await expect(ssaTab).not.toBeVisible();
});
test('show internals button toggles correctly', async ({page}) => {
await page.goto(`/`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
// show internals should be off
const checkbox = page.locator('label.show-internals');
await checkbox.click();
await page.screenshot({
fullPage: true,
path: 'test-results/04-show-internals-on.png',
});
await expect(checkbox).toBeChecked();
const ssaTab = page.locator('text=SSA');
await expect(ssaTab).toBeVisible();
});
test('error is displayed when config has syntax error', async ({page}) => {
const store: Store = {
source: TEST_SOURCE,
config: `compilationMode: `,
showInternals: false,
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
await expandConfigs(page);
await page.screenshot({
fullPage: true,
path: 'test-results/05-config-syntax-error.png',
});
const text =
(await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];
const output = text.join('');
// Remove hidden chars
expect(output.replace(/\s+/g, ' ')).toContain('Invalid override format');
});
test('error is displayed when config has validation error', async ({page}) => {
const store: Store = {
source: TEST_SOURCE,
config: `import type { PluginOptions } from 'babel-plugin-react-compiler/dist';
({
compilationMode: "123"
} satisfies PluginOptions);`,
showInternals: false,
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
await expandConfigs(page);
await page.screenshot({
fullPage: true,
path: 'test-results/06-config-validation-error.png',
});
const text =
(await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];
const output = text.join('');
expect(output.replace(/\s+/g, ' ')).toContain('Unexpected compilationMode');
});
test('disableMemoizationForDebugging flag works as expected', async ({
page,
}) => {
const store: Store = {
source: TEST_SOURCE,
config: `import type { PluginOptions } from 'babel-plugin-react-compiler/dist';
({
environment: {
disableMemoizationForDebugging: true
}
} satisfies PluginOptions);`,
showInternals: false,
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
await expandConfigs(page);
await page.screenshot({
fullPage: true,
path: 'test-results/07-config-disableMemoizationForDebugging-flag.png',
});
const text =
(await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];
const output = await formatPrint(text);
expect(output).not.toEqual('');
expect(output).toMatchSnapshot('disableMemoizationForDebugging-output.txt');
});
test('error is displayed when source has syntax error', async ({page}) => {
const syntaxErrorSource = `function TestComponent(props) {
const oops = props.
return (
<>{oops}</>
);
}`;
const store: Store = {
source: syntaxErrorSource,
config: defaultConfig,
showInternals: false,
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`);
await page.waitForFunction(isMonacoLoaded);
await expandConfigs(page);
await page.screenshot({
fullPage: true,
path: 'test-results/08-source-syntax-error.png',
});
const text =
(await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];
const output = text.join('');
expect(output.replace(/\s+/g, ' ')).toContain(
'Expected identifier to be defined before being used',
);
});
TEST_CASE_INPUTS.forEach((t, idx) =>
test(`playground compiles: ${t.name}`, async ({page}) => {
const store: Store = {
source: t.input,
config: defaultConfig,
showInternals: false,
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
await page.screenshot({
fullPage: true,
path: `test-results/03-0${idx}-${t.name}.png`,
path: `test-results/08-0${idx}-${t.name}.png`,
});
const text =
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
(await page.locator('.monaco-editor-output').allInnerTexts()) ?? [];
let output: string;
if (t.noFormat) {
output = text.join('');

View File

@@ -1,56 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type {NextPage} from 'next';
import Head from 'next/head';
import {SnackbarProvider} from 'notistack';
import {Editor, Header, StoreProvider} from '../components';
import MessageSnackbar from '../components/Message';
const Home: NextPage = () => {
return (
<div className="flex flex-col w-screen h-screen font-light">
<Head>
<title>
{process.env.NODE_ENV === 'development'
? '[DEV] React Compiler Playground'
: 'React Compiler Playground'}
</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"></meta>
<link rel="icon" href="/favicon.ico" />
<link rel="manifest" href="/site.webmanifest" />
<link
rel="preload"
href="/fonts/Source-Code-Pro-Regular.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
<link
rel="preload"
href="/fonts/Optimistic_Display_W_Lt.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
</Head>
<StoreProvider>
<SnackbarProvider
preventDuplicate
maxSnack={10}
Components={{message: MessageSnackbar}}>
<Header />
<Editor />
</SnackbarProvider>
</StoreProvider>
</div>
);
};
export default Home;

View File

@@ -0,0 +1,126 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {Resizable} from 're-resizable';
import React, {
useId,
unstable_ViewTransition as ViewTransition,
unstable_addTransitionType as addTransitionType,
startTransition,
} from 'react';
import {EXPAND_ACCORDION_TRANSITION} from '../lib/transitionTypes';
type TabsRecord = Map<string, React.ReactNode>;
export default function AccordionWindow(props: {
defaultTab: string | null;
tabs: TabsRecord;
tabsOpen: Set<string>;
setTabsOpen: (newTab: Set<string>) => void;
changedPasses: Set<string>;
}): React.ReactElement {
return (
<div className="flex-1 min-w-[550px] sm:min-w-0">
<div className="flex flex-row h-full">
{Array.from(props.tabs.keys()).map(name => {
return (
<AccordionWindowItem
name={name}
key={name}
tabs={props.tabs}
tabsOpen={props.tabsOpen}
setTabsOpen={props.setTabsOpen}
hasChanged={props.changedPasses.has(name)}
/>
);
})}
</div>
</div>
);
}
function AccordionWindowItem({
name,
tabs,
tabsOpen,
setTabsOpen,
hasChanged,
}: {
name: string;
tabs: TabsRecord;
tabsOpen: Set<string>;
setTabsOpen: (newTab: Set<string>) => void;
hasChanged: boolean;
isFailure: boolean;
}): React.ReactElement {
const id = useId();
const isShow = tabsOpen.has(name);
const transitionName = `accordion-window-item-${id}`;
const toggleTabs = (): void => {
startTransition(() => {
addTransitionType(EXPAND_ACCORDION_TRANSITION);
const nextState = new Set(tabsOpen);
if (nextState.has(name)) {
nextState.delete(name);
} else {
nextState.add(name);
}
setTabsOpen(nextState);
});
};
// Replace spaces with non-breaking spaces
const displayName = name.replace(/ /g, '\u00A0');
return (
<div key={name} className="flex flex-row">
{isShow ? (
<ViewTransition
name={transitionName}
update={{
[EXPAND_ACCORDION_TRANSITION]: 'expand-accordion',
default: 'none',
}}>
<Resizable className="border-r" minWidth={550} enable={{right: true}}>
<h2
title="Minimize tab"
aria-label="Minimize tab"
onClick={toggleTabs}
className={`p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 ${
hasChanged ? 'font-bold' : 'font-light'
} text-secondary hover:text-link`}>
- {displayName}
</h2>
{tabs.get(name) ?? <div>No output for {name}</div>}
</Resizable>
</ViewTransition>
) : (
<ViewTransition
name={transitionName}
update={{
[EXPAND_ACCORDION_TRANSITION]: 'expand-accordion',
default: 'none',
}}>
<div className="relative items-center h-full px-1 py-6 align-middle border-r border-grey-200">
<button
title={`Expand compiler tab: ${name}`}
aria-label={`Expand compiler tab: ${name}`}
style={{transform: 'rotate(90deg) translate(-50%)'}}
onClick={toggleTabs}
className={`flex-grow-0 w-5 transition-colors duration-150 ease-in ${
hasChanged ? 'font-bold' : 'font-light'
} text-secondary hover:text-link`}>
{displayName}
</button>
</div>
</ViewTransition>
)}
</div>
);
}

View File

@@ -0,0 +1,210 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
import type {editor} from 'monaco-editor';
import * as monaco from 'monaco-editor';
import React, {
useState,
useRef,
unstable_ViewTransition as ViewTransition,
unstable_addTransitionType as addTransitionType,
startTransition,
Activity,
} from 'react';
import {Resizable} from 're-resizable';
import {useStore, useStoreDispatch} from '../StoreContext';
import {monacoConfigOptions} from './monacoOptions';
import {IconChevron} from '../Icons/IconChevron';
import {CONFIG_PANEL_TRANSITION} from '../../lib/transitionTypes';
// @ts-expect-error - webpack asset/source loader handles .d.ts files as strings
import compilerTypeDefs from 'babel-plugin-react-compiler/dist/index.d.ts';
loader.config({monaco});
export default function ConfigEditor({
formattedAppliedConfig,
}: {
formattedAppliedConfig: string;
}): React.ReactElement {
const [isExpanded, setIsExpanded] = useState(false);
return (
<>
<Activity mode={isExpanded ? 'visible' : 'hidden'}>
<ExpandedEditor
onToggle={() => {
startTransition(() => {
addTransitionType(CONFIG_PANEL_TRANSITION);
setIsExpanded(false);
});
}}
formattedAppliedConfig={formattedAppliedConfig}
/>
</Activity>
<Activity mode={isExpanded ? 'hidden' : 'visible'}>
<CollapsedEditor
onToggle={() => {
startTransition(() => {
addTransitionType(CONFIG_PANEL_TRANSITION);
setIsExpanded(true);
});
}}
/>
</Activity>
</>
);
}
function ExpandedEditor({
onToggle,
formattedAppliedConfig,
}: {
onToggle: (expanded: boolean) => void;
formattedAppliedConfig: string;
}): React.ReactElement {
const store = useStore();
const dispatchStore = useStoreDispatch();
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
const handleChange: (value: string | undefined) => void = (
value: string | undefined,
) => {
if (value === undefined) return;
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
debounceTimerRef.current = setTimeout(() => {
dispatchStore({
type: 'updateConfig',
payload: {
config: value,
},
});
}, 500); // 500ms debounce delay
};
const handleMount: (
_: editor.IStandaloneCodeEditor,
monaco: Monaco,
) => void = (_, monaco) => {
// Add the babel-plugin-react-compiler type definitions to Monaco
monaco.languages.typescript.typescriptDefaults.addExtraLib(
//@ts-expect-error - compilerTypeDefs is a string
compilerTypeDefs,
'file:///node_modules/babel-plugin-react-compiler/dist/index.d.ts',
);
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.Latest,
allowNonTsExtensions: true,
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
module: monaco.languages.typescript.ModuleKind.ESNext,
noEmit: true,
strict: false,
esModuleInterop: true,
allowSyntheticDefaultImports: true,
jsx: monaco.languages.typescript.JsxEmit.React,
});
};
return (
<ViewTransition
enter={{[CONFIG_PANEL_TRANSITION]: 'slide-in', default: 'none'}}
exit={{[CONFIG_PANEL_TRANSITION]: 'slide-out', default: 'none'}}>
<Resizable
minWidth={300}
maxWidth={600}
defaultSize={{width: 350}}
enable={{right: true, bottom: false}}>
<div className="bg-blue-10 relative h-full flex flex-col !h-[calc(100vh_-_3.5rem)] border border-gray-300">
<div
className="absolute w-8 h-16 bg-blue-10 rounded-r-full flex items-center justify-center z-[2] cursor-pointer border border-l-0 border-gray-300"
title="Minimize config editor"
onClick={onToggle}
style={{
top: '50%',
marginTop: '-32px',
right: '-32px',
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
}}>
<IconChevron displayDirection="left" className="text-blue-50" />
</div>
<div className="flex-1 flex flex-col m-2 mb-2">
<div className="pb-2">
<h2 className="inline-block text-blue-50 py-1.5 px-1.5 xs:px-3 sm:px-4 text-sm">
Config Overrides
</h2>
</div>
<div className="flex-1 border border-gray-300">
<MonacoEditor
path={'config.ts'}
language={'typescript'}
value={store.config}
onMount={handleMount}
onChange={handleChange}
loading={''}
className="monaco-editor-config"
options={monacoConfigOptions}
/>
</div>
</div>
<div className="flex-1 flex flex-col m-2">
<div className="pb-2">
<h2 className="inline-block text-blue-50 py-1.5 px-1.5 xs:px-3 sm:px-4 text-sm">
Applied Configs
</h2>
</div>
<div className="flex-1 border border-gray-300">
<MonacoEditor
path={'applied-config.js'}
language={'javascript'}
value={formattedAppliedConfig}
loading={''}
className="monaco-editor-applied-config"
options={{
...monacoConfigOptions,
readOnly: true,
}}
/>
</div>
</div>
</div>
</Resizable>
</ViewTransition>
);
}
function CollapsedEditor({
onToggle,
}: {
onToggle: () => void;
}): React.ReactElement {
return (
<div
className="w-4 !h-[calc(100vh_-_3.5rem)]"
style={{position: 'relative'}}>
<div
className="absolute w-10 h-16 bg-blue-10 hover:translate-x-2 transition-transform rounded-r-full flex items-center justify-center z-[2] cursor-pointer border border-gray-300"
title="Expand config editor"
onClick={onToggle}
style={{
top: '50%',
marginTop: '-32px',
left: '-8px',
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
}}>
<IconChevron displayDirection="right" className="text-blue-50" />
</div>
</div>
);
}

View File

@@ -5,289 +5,63 @@
* LICENSE file in the root directory of this source tree.
*/
import {parse as babelParse, ParseResult} from '@babel/parser';
import * as HermesParser from 'hermes-parser';
import * as t from '@babel/types';
import BabelPluginReactCompiler, {
CompilerError,
import {
CompilerErrorDetail,
Effect,
ErrorSeverity,
parseConfigPragmaForTests,
ValueKind,
type Hook,
PluginOptions,
CompilerPipelineValue,
parsePluginOptions,
} from 'babel-plugin-react-compiler/src';
import clsx from 'clsx';
import invariant from 'invariant';
import {useSnackbar} from 'notistack';
import {useDeferredValue, useMemo} from 'react';
import {useMountEffect} from '../../hooks';
import {defaultStore} from '../../lib/defaultStore';
import {
createMessage,
initStoreFromUrlOrLocalStorage,
MessageLevel,
MessageSource,
type Store,
} from '../../lib/stores';
import {useStore, useStoreDispatch} from '../StoreContext';
CompilerDiagnostic,
} from 'babel-plugin-react-compiler';
import {useDeferredValue, useMemo, useState} from 'react';
import {useStore} from '../StoreContext';
import ConfigEditor from './ConfigEditor';
import Input from './Input';
import {
CompilerOutput,
CompilerTransformOutput,
default as Output,
PrintedCompilerPipelineValue,
} from './Output';
import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR';
import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction';
import {transformFromAstSync} from '@babel/core';
function parseInput(
input: string,
language: 'flow' | 'typescript',
): ParseResult<t.File> {
// Extract the first line to quickly check for custom test directives
if (language === 'flow') {
return HermesParser.parse(input, {
babel: true,
flow: 'all',
sourceType: 'module',
enableExperimentalComponentSyntax: true,
});
} else {
return babelParse(input, {
plugins: ['typescript', 'jsx'],
sourceType: 'module',
}) as ParseResult<t.File>;
}
}
function invokeCompiler(
source: string,
language: 'flow' | 'typescript',
options: PluginOptions,
): CompilerTransformOutput {
const ast = parseInput(source, language);
let result = transformFromAstSync(ast, source, {
filename: '_playgroundFile.js',
highlightCode: false,
retainLines: true,
plugins: [[BabelPluginReactCompiler, options]],
ast: true,
sourceType: 'module',
configFile: false,
sourceMaps: true,
babelrc: false,
});
if (result?.ast == null || result?.code == null || result?.map == null) {
throw new Error('Expected successful compilation');
}
return {
code: result.code,
sourceMaps: result.map,
language,
};
}
const COMMON_HOOKS: Array<[string, Hook]> = [
[
'useFragment',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
[
'usePaginationFragment',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
[
'useRefetchableFragment',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
[
'useLazyLoadQuery',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
[
'usePreloadedQuery',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
];
function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
const results = new Map<string, Array<PrintedCompilerPipelineValue>>();
const error = new CompilerError();
const upsert: (result: PrintedCompilerPipelineValue) => void = result => {
const entry = results.get(result.name);
if (Array.isArray(entry)) {
entry.push(result);
} else {
results.set(result.name, [result]);
}
};
let language: 'flow' | 'typescript';
if (source.match(/\@flow/)) {
language = 'flow';
} else {
language = 'typescript';
}
let transformOutput;
try {
// Extract the first line to quickly check for custom test directives
const pragma = source.substring(0, source.indexOf('\n'));
const logIR = (result: CompilerPipelineValue): void => {
switch (result.kind) {
case 'ast': {
break;
}
case 'hir': {
upsert({
kind: 'hir',
fnName: result.value.id,
name: result.name,
value: printFunctionWithOutlined(result.value),
});
break;
}
case 'reactive': {
upsert({
kind: 'reactive',
fnName: result.value.id,
name: result.name,
value: printReactiveFunctionWithOutlined(result.value),
});
break;
}
case 'debug': {
upsert({
kind: 'debug',
fnName: null,
name: result.name,
value: result.value,
});
break;
}
default: {
const _: never = result;
throw new Error(`Unhandled result ${result}`);
}
}
};
const parsedOptions = parseConfigPragmaForTests(pragma, {
compilationMode: 'infer',
});
const opts: PluginOptions = parsePluginOptions({
...parsedOptions,
environment: {
...parsedOptions.environment,
customHooks: new Map([...COMMON_HOOKS]),
},
logger: {
debugLogIRs: logIR,
logEvent: () => {},
},
});
transformOutput = invokeCompiler(source, language, opts);
} catch (err) {
/**
* error might be an invariant violation or other runtime error
* (i.e. object shape that is not CompilerError)
*/
if (err instanceof CompilerError && err.details.length > 0) {
error.details.push(...err.details);
} else {
/**
* Handle unexpected failures by logging (to get a stack trace)
* and reporting
*/
console.error(err);
error.details.push(
new CompilerErrorDetail({
severity: ErrorSeverity.Invariant,
reason: `Unexpected failure when transforming input! ${err}`,
loc: null,
suggestions: null,
}),
);
}
}
if (error.hasErrors()) {
return [{kind: 'err', results, error: error}, language];
}
return [{kind: 'ok', results, transformOutput}, language];
}
import {CompilerOutput, default as Output} from './Output';
import {compile} from '../../lib/compilation';
import prettyFormat from 'pretty-format';
export default function Editor(): JSX.Element {
const store = useStore();
const deferredStore = useDeferredValue(store);
const dispatchStore = useStoreDispatch();
const {enqueueSnackbar} = useSnackbar();
const [compilerOutput, language] = useMemo(
() => compile(deferredStore.source),
[deferredStore.source],
const [compilerOutput, language, appliedOptions] = useMemo(
() => compile(deferredStore.source, 'compiler', deferredStore.config),
[deferredStore.source, deferredStore.config],
);
const [linterOutput] = useMemo(
() => compile(deferredStore.source, 'linter', deferredStore.config),
[deferredStore.source, deferredStore.config],
);
const [formattedAppliedConfig, setFormattedAppliedConfig] = useState('');
useMountEffect(() => {
let mountStore: Store;
try {
mountStore = initStoreFromUrlOrLocalStorage();
} catch (e) {
invariant(e instanceof Error, 'Only Error may be caught.');
enqueueSnackbar(e.message, {
variant: 'warning',
...createMessage(
'Bad URL - fell back to the default Playground.',
MessageLevel.Info,
MessageSource.Playground,
),
});
mountStore = defaultStore;
}
dispatchStore({
type: 'setStore',
payload: {store: mountStore},
let mergedOutput: CompilerOutput;
let errors: Array<CompilerErrorDetail | CompilerDiagnostic>;
if (compilerOutput.kind === 'ok') {
errors = linterOutput.kind === 'ok' ? [] : linterOutput.error.details;
mergedOutput = {
...compilerOutput,
errors,
};
} else {
mergedOutput = compilerOutput;
errors = compilerOutput.error.details;
}
if (appliedOptions) {
const formatted = prettyFormat(appliedOptions, {
printFunctionName: false,
printBasicPrototype: false,
});
});
if (formatted !== formattedAppliedConfig) {
setFormattedAppliedConfig(formatted);
}
}
return (
<>
<div className="relative flex basis top-14">
<div className={clsx('relative sm:basis-1/4')}>
<Input
language={language}
errors={
compilerOutput.kind === 'err' ? compilerOutput.error.details : []
}
/>
<div className="relative flex top-14">
<div className="flex-shrink-0">
<ConfigEditor formattedAppliedConfig={formattedAppliedConfig} />
</div>
<div className={clsx('flex sm:flex flex-wrap')}>
<Output store={deferredStore} compilerOutput={compilerOutput} />
<div className="flex flex-1 min-w-0">
<Input language={language} errors={errors} />
<Output store={deferredStore} compilerOutput={mergedOutput} />
</div>
</div>
</>

View File

@@ -6,22 +6,31 @@
*/
import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
import {CompilerErrorDetail} from 'babel-plugin-react-compiler/src';
import {
CompilerErrorDetail,
CompilerDiagnostic,
} from 'babel-plugin-react-compiler';
import invariant from 'invariant';
import type {editor} from 'monaco-editor';
import * as monaco from 'monaco-editor';
import {Resizable} from 're-resizable';
import {useEffect, useState} from 'react';
import {
useEffect,
useState,
unstable_ViewTransition as ViewTransition,
} from 'react';
import {renderReactCompilerMarkers} from '../../lib/reactCompilerMonacoDiagnostics';
import {useStore, useStoreDispatch} from '../StoreContext';
import TabbedWindow from '../TabbedWindow';
import {monacoOptions} from './monacoOptions';
import {CONFIG_PANEL_TRANSITION} from '../../lib/transitionTypes';
// @ts-expect-error TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
import React$Types from '../../node_modules/@types/react/index.d.ts';
loader.config({monaco});
type Props = {
errors: Array<CompilerErrorDetail>;
errors: Array<CompilerErrorDetail | CompilerDiagnostic>;
language: 'flow' | 'typescript';
};
@@ -36,13 +45,13 @@ export default function Input({errors, language}: Props): JSX.Element {
const uri = monaco.Uri.parse(`file:///index.js`);
const model = monaco.editor.getModel(uri);
invariant(model, 'Model must exist for the selected input file.');
renderReactCompilerMarkers({monaco, model, details: errors});
/**
* N.B. that `tabSize` is a model property, not an editor property.
* So, the tab size has to be set per model.
*/
model.updateOptions({tabSize: 2});
}, [monaco, errors]);
renderReactCompilerMarkers({
monaco,
model,
details: errors,
source: store.source,
});
}, [monaco, errors, store.source]);
useEffect(() => {
/**
@@ -74,11 +83,11 @@ export default function Input({errors, language}: Props): JSX.Element {
});
}, [monaco, language]);
const handleChange: (value: string | undefined) => void = value => {
const handleChange: (value: string | undefined) => void = async value => {
if (!value) return;
dispatchStore({
type: 'updateFile',
type: 'updateSource',
payload: {
source: value,
},
@@ -89,6 +98,9 @@ export default function Input({errors, language}: Props): JSX.Element {
_: editor.IStandaloneCodeEditor,
monaco: Monaco,
) => void = (_, monaco) => {
if (typeof window !== 'undefined') {
window['__MONACO_LOADED__'] = true;
}
setMonaco(monaco);
const tscOptions = {
@@ -127,30 +139,42 @@ export default function Input({errors, language}: Props): JSX.Element {
});
};
const editorContent = (
<MonacoEditor
path={'index.js'}
/**
* .js and .jsx files are specified to be TS so that Monaco can actually
* check their syntax using its TS language service. They are still JS files
* due to their extensions, so TS language features don't work.
*/
language={'javascript'}
value={store.source}
onMount={handleMount}
onChange={handleChange}
className="monaco-editor-input"
options={monacoOptions}
loading={''}
/>
);
const tabs = new Map([['Input', editorContent]]);
const [activeTab, setActiveTab] = useState('Input');
return (
<div className="relative flex flex-col flex-none border-r border-gray-200">
<Resizable
minWidth={650}
enable={{right: true}}
/**
* Restrict MonacoEditor's height, since the config autoLayout:true
* will grow the editor to fit within parent element
*/
className="!h-[calc(100vh_-_3.5rem)]">
<MonacoEditor
path={'index.js'}
/**
* .js and .jsx files are specified to be TS so that Monaco can actually
* check their syntax using its TS language service. They are still JS files
* due to their extensions, so TS language features don't work.
*/
language={'javascript'}
value={store.source}
onMount={handleMount}
onChange={handleChange}
options={monacoOptions}
/>
</Resizable>
</div>
<ViewTransition
update={{
[CONFIG_PANEL_TRANSITION]: 'container',
default: 'none',
}}>
<div className="flex-1 min-w-[550px] sm:min-w-0">
<div className="flex flex-col h-full !h-[calc(100vh_-_3.5rem)] border-r border-gray-200">
<TabbedWindow
tabs={tabs}
activeTab={activeTab}
onTabChange={setActiveTab}
/>
</div>
</div>
</ViewTransition>
);
}

View File

@@ -11,19 +11,46 @@ import {
InformationCircleIcon,
} from '@heroicons/react/outline';
import MonacoEditor, {DiffEditor} from '@monaco-editor/react';
import {type CompilerError} from 'babel-plugin-react-compiler/src';
import {
CompilerErrorDetail,
CompilerDiagnostic,
type CompilerError,
} from 'babel-plugin-react-compiler';
import parserBabel from 'prettier/plugins/babel';
import * as prettierPluginEstree from 'prettier/plugins/estree';
import * as prettier from 'prettier/standalone';
import {memo, ReactNode, useEffect, useState} from 'react';
import {type Store} from '../../lib/stores';
import {
memo,
ReactNode,
use,
useState,
Suspense,
unstable_ViewTransition as ViewTransition,
unstable_addTransitionType as addTransitionType,
startTransition,
} from 'react';
import AccordionWindow from '../AccordionWindow';
import TabbedWindow from '../TabbedWindow';
import {monacoOptions} from './monacoOptions';
import {BabelFileResult} from '@babel/core';
import {
CONFIG_PANEL_TRANSITION,
TOGGLE_INTERNALS_TRANSITION,
EXPAND_ACCORDION_TRANSITION,
} from '../../lib/transitionTypes';
import {LRUCache} from 'lru-cache';
const MemoizedOutput = memo(Output);
export default MemoizedOutput;
export const BASIC_OUTPUT_TAB_NAMES = ['Output', 'SourceMap'];
const tabifyCache = new LRUCache<Store, Promise<Map<string, ReactNode>>>({
max: 5,
});
export type PrintedCompilerPipelineValue =
| {
kind: 'hir';
@@ -44,6 +71,7 @@ export type CompilerOutput =
kind: 'ok';
transformOutput: CompilerTransformOutput;
results: Map<string, Array<PrintedCompilerPipelineValue>>;
errors: Array<CompilerErrorDetail | CompilerDiagnostic>;
}
| {
kind: 'err';
@@ -59,12 +87,16 @@ type Props = {
async function tabify(
source: string,
compilerOutput: CompilerOutput,
showInternals: boolean,
): Promise<Map<string, ReactNode>> {
const tabs = new Map<string, React.ReactNode>();
const reorderedTabs = new Map<string, React.ReactNode>();
const concattedResults = new Map<string, string>();
// Concat all top level function declaration results into a single tab for each pass
for (const [passName, results] of compilerOutput.results) {
if (!showInternals && !BASIC_OUTPUT_TAB_NAMES.includes(passName)) {
continue;
}
for (const result of results) {
switch (result.kind) {
case 'hir': {
@@ -123,10 +155,36 @@ async function tabify(
parser: transformOutput.language === 'flow' ? 'babel-flow' : 'babel-ts',
plugins: [parserBabel, prettierPluginEstree],
});
let output: string;
let language: string;
if (compilerOutput.errors.length === 0) {
output = code;
language = 'javascript';
} else {
language = 'markdown';
output = `
# Summary
React Compiler compiled this function successfully, but there are lint errors that indicate potential issues with the original code.
## ${compilerOutput.errors.length} Lint Errors
${compilerOutput.errors.map(e => e.printErrorMessage(source, {eslint: false})).join('\n\n')}
## Output
\`\`\`js
${code}
\`\`\`
`.trim();
}
reorderedTabs.set(
'JS',
'Output',
<TextTabContent
output={code}
output={output}
language={language}
diff={null}
showInfoPanel={false}></TextTabContent>,
);
@@ -142,6 +200,18 @@ async function tabify(
</>,
);
}
} else if (compilerOutput.kind === 'err') {
const errors = compilerOutput.error.printErrorMessage(source, {
eslint: false,
});
reorderedTabs.set(
'Output',
<TextTabContent
output={errors}
language="markdown"
diff={null}
showInfoPanel={false}></TextTabContent>,
);
}
tabs.forEach((tab, name) => {
reorderedTabs.set(name, tab);
@@ -149,6 +219,25 @@ async function tabify(
return reorderedTabs;
}
function tabifyCached(
store: Store,
compilerOutput: CompilerOutput,
): Promise<Map<string, ReactNode>> {
const cached = tabifyCache.get(store);
if (cached) return cached;
const result = tabify(store.source, compilerOutput, store.showInternals);
tabifyCache.set(store, result);
return result;
}
function Fallback(): JSX.Element {
return (
<div className="w-full h-monaco_small sm:h-monaco flex items-center justify-center">
Loading...
</div>
);
}
function utf16ToUTF8(s: string): string {
return unescape(encodeURIComponent(s));
}
@@ -162,17 +251,40 @@ function getSourceMapUrl(code: string, map: string): string | null {
}
function Output({store, compilerOutput}: Props): JSX.Element {
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set(['JS']));
const [tabs, setTabs] = useState<Map<string, React.ReactNode>>(
() => new Map(),
return (
<Suspense fallback={<Fallback />}>
<OutputContent store={store} compilerOutput={compilerOutput} />
</Suspense>
);
useEffect(() => {
tabify(store.source, compilerOutput).then(tabs => {
setTabs(tabs);
});
}, [store.source, compilerOutput]);
}
const changedPasses: Set<string> = new Set(['JS', 'HIR']); // Initial and final passes should always be bold
function OutputContent({store, compilerOutput}: Props): JSX.Element {
const [tabsOpen, setTabsOpen] = useState<Set<string>>(
() => new Set(['Output']),
);
const [activeTab, setActiveTab] = useState<string>('Output');
/*
* Update the active tab back to the output or errors tab when the compilation state
* changes between success/failure.
*/
const [previousOutputKind, setPreviousOutputKind] = useState(
compilerOutput.kind,
);
const isFailure = compilerOutput.kind !== 'ok';
if (compilerOutput.kind !== previousOutputKind) {
setPreviousOutputKind(compilerOutput.kind);
if (isFailure) {
startTransition(() => {
addTransitionType(EXPAND_ACCORDION_TRANSITION);
setTabsOpen(prev => new Set(prev).add('Output'));
setActiveTab('Output');
});
}
}
const changedPasses: Set<string> = new Set(['Output', 'HIR']); // Initial and final passes should always be bold
let lastResult: string = '';
for (const [passName, results] of compilerOutput.results) {
for (const result of results) {
@@ -186,31 +298,40 @@ function Output({store, compilerOutput}: Props): JSX.Element {
lastResult = currResult;
}
}
const tabs = use(tabifyCached(store, compilerOutput));
if (!store.showInternals) {
return (
<ViewTransition
update={{
[CONFIG_PANEL_TRANSITION]: 'container',
[TOGGLE_INTERNALS_TRANSITION]: '',
default: 'none',
}}>
<TabbedWindow
tabs={tabs}
activeTab={activeTab}
onTabChange={setActiveTab}
/>
</ViewTransition>
);
}
return (
<>
<TabbedWindow
defaultTab="HIR"
<ViewTransition
update={{
[CONFIG_PANEL_TRANSITION]: 'accordion-container',
[TOGGLE_INTERNALS_TRANSITION]: '',
default: 'none',
}}>
<AccordionWindow
defaultTab={store.showInternals ? 'HIR' : 'Output'}
setTabsOpen={setTabsOpen}
tabsOpen={tabsOpen}
tabs={tabs}
changedPasses={changedPasses}
/>
{compilerOutput.kind === 'err' ? (
<div
className="flex flex-wrap absolute bottom-0 bg-white grow border-y border-grey-200 transition-all ease-in"
style={{width: 'calc(100vw - 650px)'}}>
<div className="w-full p-4 basis-full border-b">
<h2>COMPILER ERRORS</h2>
</div>
<pre
className="p-4 basis-full text-red-600 overflow-y-scroll whitespace-pre-wrap"
style={{width: 'calc(100vw - 650px)', height: '150px'}}>
<code>{compilerOutput.error.toString()}</code>
</pre>
</div>
) : null}
</>
</ViewTransition>
);
}
@@ -218,10 +339,12 @@ function TextTabContent({
output,
diff,
showInfoPanel,
language,
}: {
output: string;
diff: string | null;
showInfoPanel: boolean;
language: string;
}): JSX.Element {
const [diffMode, setDiffMode] = useState(false);
return (
@@ -260,20 +383,29 @@ function TextTabContent({
<DiffEditor
original={diff}
modified={output}
loading={''}
options={{
...monacoOptions,
scrollbar: {
vertical: 'hidden',
},
dimension: {
width: 0,
height: 0,
},
readOnly: true,
lineNumbers: 'off',
glyphMargin: false,
// Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882
lineDecorationsWidth: 0,
lineNumbersMinChars: 0,
overviewRulerLanes: 0,
}}
/>
) : (
<MonacoEditor
defaultLanguage="javascript"
language={language ?? 'javascript'}
value={output}
loading={''}
className="monaco-editor-output"
options={{
...monacoOptions,
readOnly: true,

View File

@@ -28,5 +28,18 @@ export const monacoOptions: Partial<EditorProps['options']> = {
automaticLayout: true,
wordWrap: 'on',
wrappingIndent: 'deepIndent',
wrappingIndent: 'same',
tabSize: 2,
};
export const monacoConfigOptions: Partial<EditorProps['options']> = {
...monacoOptions,
lineNumbers: 'off',
renderLineHighlight: 'none',
overviewRulerBorder: false,
overviewRulerLanes: 0,
fontSize: 12,
scrollBeyondLastLine: false,
glyphMargin: false,
};

View File

@@ -10,14 +10,20 @@ import {CheckIcon} from '@heroicons/react/solid';
import clsx from 'clsx';
import Link from 'next/link';
import {useSnackbar} from 'notistack';
import {useState} from 'react';
import {
useState,
startTransition,
unstable_addTransitionType as addTransitionType,
} from 'react';
import {defaultStore} from '../lib/defaultStore';
import {IconGitHub} from './Icons/IconGitHub';
import Logo from './Logo';
import {useStoreDispatch} from './StoreContext';
import {useStore, useStoreDispatch} from './StoreContext';
import {TOGGLE_INTERNALS_TRANSITION} from '../lib/transitionTypes';
export default function Header(): JSX.Element {
const [showCheck, setShowCheck] = useState(false);
const store = useStore();
const dispatchStore = useStoreDispatch();
const {enqueueSnackbar, closeSnackbar} = useSnackbar();
@@ -56,6 +62,32 @@ export default function Header(): JSX.Element {
<p className="hidden select-none sm:block">React Compiler Playground</p>
</div>
<div className="flex items-center text-[15px] gap-4">
<div className="flex items-center gap-2">
<label className="show-internals relative inline-block w-[34px] h-5">
<input
type="checkbox"
checked={store.showInternals}
onChange={() =>
startTransition(() => {
addTransitionType(TOGGLE_INTERNALS_TRANSITION);
dispatchStore({type: 'toggleInternals'});
})
}
className="absolute opacity-0 cursor-pointer h-full w-full m-0"
/>
<span
className={clsx(
'absolute inset-0 rounded-full cursor-pointer transition-all duration-250',
"before:content-[''] before:absolute before:w-4 before:h-4 before:left-0.5 before:bottom-0.5",
'before:bg-white before:rounded-full before:transition-transform before:duration-250',
'focus-within:shadow-[0_0_1px_#2196F3]',
store.showInternals
? 'bg-link before:translate-x-3.5'
: 'bg-gray-300',
)}></span>
</label>
<span className="text-secondary">Show Internals</span>
</div>
<button
title="Reset Playground"
aria-label="Reset Playground"

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {memo} from 'react';
export const IconChevron = memo<
JSX.IntrinsicElements['svg'] & {
/**
* The direction the arrow should point.
*/
displayDirection: 'right' | 'left';
}
>(function IconChevron({className, displayDirection, ...props}) {
const rotationClass =
displayDirection === 'left' ? 'rotate-90' : '-rotate-90';
const classes = className ? `${rotationClass} ${className}` : rotationClass;
return (
<svg
className={classes}
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
{...props}>
<g fill="none" fillRule="evenodd" transform="translate(-446 -398)">
<path
fill="currentColor"
fillRule="nonzero"
d="M95.8838835,240.366117 C95.3957281,239.877961 94.6042719,239.877961 94.1161165,240.366117 C93.6279612,240.854272 93.6279612,241.645728 94.1161165,242.133883 L98.6161165,246.633883 C99.1042719,247.122039 99.8957281,247.122039 100.383883,246.633883 L104.883883,242.133883 C105.372039,241.645728 105.372039,240.854272 104.883883,240.366117 C104.395728,239.877961 103.604272,239.877961 103.116117,240.366117 L99.5,243.982233 L95.8838835,240.366117 Z"
transform="translate(356.5 164.5)"
/>
<polygon points="446 418 466 418 466 398 446 398" />
</g>
</svg>
);
});

View File

@@ -6,10 +6,14 @@
*/
import type {Dispatch, ReactNode} from 'react';
import {useEffect, useReducer} from 'react';
import {useState, useEffect, useReducer} from 'react';
import createContext from '../lib/createContext';
import {emptyStore} from '../lib/defaultStore';
import {saveStore, type Store} from '../lib/stores';
import {emptyStore, defaultStore} from '../lib/defaultStore';
import {
saveStore,
initStoreFromUrlOrLocalStorage,
type Store,
} from '../lib/stores';
const StoreContext = createContext<Store>();
@@ -30,6 +34,20 @@ export const useStoreDispatch = StoreDispatchContext.useContext;
*/
export function StoreProvider({children}: {children: ReactNode}): JSX.Element {
const [store, dispatch] = useReducer(storeReducer, emptyStore);
const [isPageReady, setIsPageReady] = useState<boolean>(false);
useEffect(() => {
let mountStore: Store;
try {
mountStore = initStoreFromUrlOrLocalStorage();
} catch (e) {
console.error('Failed to initialize store from URL or local storage', e);
mountStore = defaultStore;
}
dispatch({type: 'setStore', payload: {store: mountStore}});
setIsPageReady(true);
}, []);
useEffect(() => {
if (store !== emptyStore) {
saveStore(store);
@@ -39,7 +57,7 @@ export function StoreProvider({children}: {children: ReactNode}): JSX.Element {
return (
<StoreContext.Provider value={store}>
<StoreDispatchContext.Provider value={dispatch}>
{children}
{isPageReady ? children : null}
</StoreDispatchContext.Provider>
</StoreContext.Provider>
);
@@ -53,10 +71,19 @@ type ReducerAction =
};
}
| {
type: 'updateFile';
type: 'updateSource';
payload: {
source: string;
};
}
| {
type: 'updateConfig';
payload: {
config: string;
};
}
| {
type: 'toggleInternals';
};
function storeReducer(store: Store, action: ReducerAction): Store {
@@ -65,13 +92,28 @@ function storeReducer(store: Store, action: ReducerAction): Store {
const newStore = action.payload.store;
return newStore;
}
case 'updateFile': {
const {source} = action.payload;
case 'updateSource': {
const source = action.payload.source;
const newStore = {
...store,
source,
};
return newStore;
}
case 'updateConfig': {
const config = action.payload.config;
const newStore = {
...store,
config,
};
return newStore;
}
case 'toggleInternals': {
const newStore = {
...store,
showInternals: !store.showInternals,
};
return newStore;
}
}
}

View File

@@ -4,103 +4,78 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, {
startTransition,
useId,
unstable_ViewTransition as ViewTransition,
unstable_addTransitionType as addTransitionType,
} from 'react';
import clsx from 'clsx';
import {TOGGLE_TAB_TRANSITION} from '../lib/transitionTypes';
import {Resizable} from 're-resizable';
import React, {useCallback} from 'react';
type TabsRecord = Map<string, React.ReactNode>;
export default function TabbedWindow(props: {
defaultTab: string | null;
tabs: TabsRecord;
tabsOpen: Set<string>;
setTabsOpen: (newTab: Set<string>) => void;
changedPasses: Set<string>;
}): React.ReactElement {
if (props.tabs.size === 0) {
return (
<div
className="flex items-center justify-center"
style={{width: 'calc(100vw - 650px)'}}>
No compiler output detected, see errors below
</div>
);
}
return (
<div className="flex flex-row">
{Array.from(props.tabs.keys()).map(name => {
return (
<TabbedWindowItem
name={name}
key={name}
tabs={props.tabs}
tabsOpen={props.tabsOpen}
setTabsOpen={props.setTabsOpen}
hasChanged={props.changedPasses.has(name)}
/>
);
})}
</div>
);
}
function TabbedWindowItem({
name,
export default function TabbedWindow({
tabs,
tabsOpen,
setTabsOpen,
hasChanged,
activeTab,
onTabChange,
}: {
name: string;
tabs: TabsRecord;
tabsOpen: Set<string>;
setTabsOpen: (newTab: Set<string>) => void;
hasChanged: boolean;
tabs: Map<string, React.ReactNode>;
activeTab: string;
onTabChange: (tab: string) => void;
}): React.ReactElement {
const isShow = tabsOpen.has(name);
const id = useId();
const transitionName = `tab-highlight-${id}`;
const toggleTabs = useCallback(() => {
const nextState = new Set(tabsOpen);
if (nextState.has(name)) {
nextState.delete(name);
} else {
nextState.add(name);
}
setTabsOpen(nextState);
}, [tabsOpen, name, setTabsOpen]);
// Replace spaces with non-breaking spaces
const displayName = name.replace(/ /g, '\u00A0');
const handleTabChange = (tab: string): void => {
startTransition(() => {
addTransitionType(TOGGLE_TAB_TRANSITION);
onTabChange(tab);
});
};
return (
<div key={name} className="flex flex-row">
{isShow ? (
<Resizable className="border-r" minWidth={550} enable={{right: true}}>
<h2
title="Minimize tab"
aria-label="Minimize tab"
onClick={toggleTabs}
className={`p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 ${
hasChanged ? 'font-bold' : 'font-light'
} text-secondary hover:text-link`}>
- {displayName}
</h2>
{tabs.get(name) ?? <div>No output for {name}</div>}
</Resizable>
) : (
<div className="relative items-center h-full px-1 py-6 align-middle border-r border-grey-200">
<button
title={`Expand compiler tab: ${name}`}
aria-label={`Expand compiler tab: ${name}`}
style={{transform: 'rotate(90deg) translate(-50%)'}}
onClick={toggleTabs}
className={`flex-grow-0 w-5 transition-colors duration-150 ease-in ${
hasChanged ? 'font-bold' : 'font-light'
} text-secondary hover:text-link`}>
{displayName}
</button>
<div className="flex-1 min-w-[550px] sm:min-w-0">
<div className="flex flex-col h-full max-w-full">
<div className="flex p-2 flex-shrink-0">
{Array.from(tabs.keys()).map(tab => {
const isActive = activeTab === tab;
return (
<button
key={tab}
onClick={() => handleTabChange(tab)}
className={clsx(
'transition-transform py-1.5 px-1.5 xs:px-3 sm:px-4 rounded-full text-sm relative',
isActive ? 'text-link' : 'hover:bg-primary/5',
)}>
{isActive && (
<ViewTransition
name={transitionName}
enter={{default: 'none'}}
exit={{default: 'none'}}
share={{
[TOGGLE_TAB_TRANSITION]: 'tab-highlight',
default: 'none',
}}
update={{default: 'none'}}>
<div className="absolute inset-0 bg-highlight rounded-full" />
</ViewTransition>
)}
<ViewTransition
enter={{default: 'none'}}
exit={{default: 'none'}}
update={{
[TOGGLE_TAB_TRANSITION]: 'tab-text',
default: 'none',
}}>
<span className="relative z-1">{tab}</span>
</ViewTransition>
</button>
);
})}
</div>
)}
<div className="flex-1 overflow-hidden w-full h-full">
{tabs.get(activeTab)}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,308 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {parse as babelParse, ParseResult} from '@babel/parser';
import * as HermesParser from 'hermes-parser';
import * as t from '@babel/types';
import BabelPluginReactCompiler, {
CompilerError,
CompilerErrorDetail,
CompilerDiagnostic,
Effect,
ErrorCategory,
parseConfigPragmaForTests,
ValueKind,
type Hook,
PluginOptions,
CompilerPipelineValue,
parsePluginOptions,
printReactiveFunctionWithOutlined,
printFunctionWithOutlined,
type LoggerEvent,
} from 'babel-plugin-react-compiler';
import {transformFromAstSync} from '@babel/core';
import type {
CompilerOutput,
CompilerTransformOutput,
PrintedCompilerPipelineValue,
} from '../components/Editor/Output';
function parseInput(
input: string,
language: 'flow' | 'typescript',
): ParseResult<t.File> {
// Extract the first line to quickly check for custom test directives
if (language === 'flow') {
return HermesParser.parse(input, {
babel: true,
flow: 'all',
sourceType: 'module',
enableExperimentalComponentSyntax: true,
});
} else {
return babelParse(input, {
plugins: ['typescript', 'jsx'],
sourceType: 'module',
}) as ParseResult<t.File>;
}
}
function invokeCompiler(
source: string,
language: 'flow' | 'typescript',
options: PluginOptions,
): CompilerTransformOutput {
const ast = parseInput(source, language);
let result = transformFromAstSync(ast, source, {
filename: '_playgroundFile.js',
highlightCode: false,
retainLines: true,
plugins: [[BabelPluginReactCompiler, options]],
ast: true,
sourceType: 'module',
configFile: false,
sourceMaps: true,
babelrc: false,
});
if (result?.ast == null || result?.code == null || result?.map == null) {
throw new Error('Expected successful compilation');
}
return {
code: result.code,
sourceMaps: result.map,
language,
};
}
const COMMON_HOOKS: Array<[string, Hook]> = [
[
'useFragment',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
[
'usePaginationFragment',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
[
'useRefetchableFragment',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
[
'useLazyLoadQuery',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
[
'usePreloadedQuery',
{
valueKind: ValueKind.Frozen,
effectKind: Effect.Freeze,
noAlias: true,
transitiveMixedData: true,
},
],
];
function parseOptions(
source: string,
mode: 'compiler' | 'linter',
configOverrides: string,
): PluginOptions {
// Extract the first line to quickly check for custom test directives
const pragma = source.substring(0, source.indexOf('\n'));
const parsedPragmaOptions = parseConfigPragmaForTests(pragma, {
compilationMode: 'infer',
environment:
mode === 'linter'
? {
// enabled in compiler
validateRefAccessDuringRender: false,
// enabled in linter
validateNoSetStateInRender: true,
validateNoSetStateInEffects: true,
validateNoJSXInTryStatements: true,
validateNoImpureFunctionsInRender: true,
validateStaticComponents: true,
validateNoFreezingKnownMutableFunctions: true,
validateNoVoidUseMemo: true,
}
: {
/* use defaults for compiler mode */
},
});
// Parse config overrides from config editor
let configOverrideOptions: any = {};
const configMatch = configOverrides.match(/^\s*import.*?\n\n\((.*)\)/s);
if (configOverrides.trim()) {
if (configMatch && configMatch[1]) {
const configString = configMatch[1].replace(/satisfies.*$/, '').trim();
configOverrideOptions = new Function(`return (${configString})`)();
} else {
throw new Error('Invalid override format');
}
}
const opts: PluginOptions = parsePluginOptions({
...parsedPragmaOptions,
...configOverrideOptions,
environment: {
...parsedPragmaOptions.environment,
...configOverrideOptions.environment,
customHooks: new Map([...COMMON_HOOKS]),
},
});
return opts;
}
export function compile(
source: string,
mode: 'compiler' | 'linter',
configOverrides: string,
): [CompilerOutput, 'flow' | 'typescript', PluginOptions | null] {
const results = new Map<string, Array<PrintedCompilerPipelineValue>>();
const error = new CompilerError();
const otherErrors: Array<CompilerErrorDetail | CompilerDiagnostic> = [];
const upsert: (result: PrintedCompilerPipelineValue) => void = result => {
const entry = results.get(result.name);
if (Array.isArray(entry)) {
entry.push(result);
} else {
results.set(result.name, [result]);
}
};
let language: 'flow' | 'typescript';
if (source.match(/\@flow/)) {
language = 'flow';
} else {
language = 'typescript';
}
let transformOutput;
let baseOpts: PluginOptions | null = null;
try {
baseOpts = parseOptions(source, mode, configOverrides);
} catch (err) {
error.details.push(
new CompilerErrorDetail({
category: ErrorCategory.Config,
reason: `Unexpected failure when transforming configs! \n${err}`,
loc: null,
suggestions: null,
}),
);
}
if (baseOpts) {
try {
const logIR = (result: CompilerPipelineValue): void => {
switch (result.kind) {
case 'ast': {
break;
}
case 'hir': {
upsert({
kind: 'hir',
fnName: result.value.id,
name: result.name,
value: printFunctionWithOutlined(result.value),
});
break;
}
case 'reactive': {
upsert({
kind: 'reactive',
fnName: result.value.id,
name: result.name,
value: printReactiveFunctionWithOutlined(result.value),
});
break;
}
case 'debug': {
upsert({
kind: 'debug',
fnName: null,
name: result.name,
value: result.value,
});
break;
}
default: {
const _: never = result;
throw new Error(`Unhandled result ${result}`);
}
}
};
// Add logger options to the parsed options
const opts = {
...baseOpts,
logger: {
debugLogIRs: logIR,
logEvent: (_filename: string | null, event: LoggerEvent): void => {
if (event.kind === 'CompileError') {
otherErrors.push(event.detail);
}
},
},
};
transformOutput = invokeCompiler(source, language, opts);
} catch (err) {
/**
* error might be an invariant violation or other runtime error
* (i.e. object shape that is not CompilerError)
*/
if (err instanceof CompilerError && err.details.length > 0) {
error.merge(err);
} else {
/**
* Handle unexpected failures by logging (to get a stack trace)
* and reporting
*/
error.details.push(
new CompilerErrorDetail({
category: ErrorCategory.Invariant,
reason: `Unexpected failure when transforming input! \n${err}`,
loc: null,
suggestions: null,
}),
);
}
}
}
// Only include logger errors if there weren't other errors
if (!error.hasErrors() && otherErrors.length !== 0) {
otherErrors.forEach(e => error.details.push(e));
}
if (error.hasErrors() || !transformOutput) {
return [{kind: 'err', results, error}, language, baseOpts];
}
return [
{kind: 'ok', results, transformOutput, errors: error.details},
language,
baseOpts,
];
}

View File

@@ -13,10 +13,21 @@ export default function MyApp() {
}
`;
export const defaultConfig = `\
import type { PluginOptions } from 'babel-plugin-react-compiler/dist';
({
//compilationMode: "all"
} satisfies PluginOptions);`;
export const defaultStore: Store = {
source: index,
config: defaultConfig,
showInternals: false,
};
export const emptyStore: Store = {
source: '',
config: '',
showInternals: false,
};

View File

@@ -7,9 +7,10 @@
import {Monaco} from '@monaco-editor/react';
import {
CompilerDiagnostic,
CompilerErrorDetail,
ErrorSeverity,
} from 'babel-plugin-react-compiler/src';
} from 'babel-plugin-react-compiler';
import {MarkerSeverity, type editor} from 'monaco-editor';
function mapReactCompilerSeverityToMonaco(
@@ -25,38 +26,46 @@ function mapReactCompilerSeverityToMonaco(
}
function mapReactCompilerDiagnosticToMonacoMarker(
detail: CompilerErrorDetail,
detail: CompilerErrorDetail | CompilerDiagnostic,
monaco: Monaco,
source: string,
): editor.IMarkerData | null {
if (detail.loc == null || typeof detail.loc === 'symbol') {
const loc = detail.primaryLocation();
if (loc == null || typeof loc === 'symbol') {
return null;
}
const severity = mapReactCompilerSeverityToMonaco(detail.severity, monaco);
let message = detail.printErrorMessage();
let message = detail.printErrorMessage(source, {eslint: true});
return {
severity,
message,
startLineNumber: detail.loc.start.line,
startColumn: detail.loc.start.column + 1,
endLineNumber: detail.loc.end.line,
endColumn: detail.loc.end.column + 1,
startLineNumber: loc.start.line,
startColumn: loc.start.column + 1,
endLineNumber: loc.end.line,
endColumn: loc.end.column + 1,
};
}
type ReactCompilerMarkerConfig = {
monaco: Monaco;
model: editor.ITextModel;
details: Array<CompilerErrorDetail>;
details: Array<CompilerErrorDetail | CompilerDiagnostic>;
source: string;
};
let decorations: Array<string> = [];
export function renderReactCompilerMarkers({
monaco,
model,
details,
source,
}: ReactCompilerMarkerConfig): void {
let markers = [];
const markers: Array<editor.IMarkerData> = [];
for (const detail of details) {
const marker = mapReactCompilerDiagnosticToMonacoMarker(detail, monaco);
const marker = mapReactCompilerDiagnosticToMonacoMarker(
detail,
monaco,
source,
);
if (marker == null) {
continue;
}

View File

@@ -10,18 +10,20 @@ import {
compressToEncodedURIComponent,
decompressFromEncodedURIComponent,
} from 'lz-string';
import {defaultStore} from '../defaultStore';
import {defaultStore, defaultConfig} from '../defaultStore';
/**
* Global Store for Playground
*/
export interface Store {
source: string;
config: string;
showInternals: boolean;
}
export function encodeStore(store: Store): string {
return compressToEncodedURIComponent(JSON.stringify(store));
}
export function decodeStore(hash: string): Store {
export function decodeStore(hash: string): any {
return JSON.parse(decompressFromEncodedURIComponent(hash));
}
@@ -62,8 +64,14 @@ export function initStoreFromUrlOrLocalStorage(): Store {
*/
if (!encodedSource) return defaultStore;
const raw = decodeStore(encodedSource);
const raw: any = decodeStore(encodedSource);
invariant(isValidStore(raw), 'Invalid Store');
return raw;
// Make sure all properties are populated
return {
source: raw.source,
config: 'config' in raw && raw['config'] ? raw.config : defaultConfig,
showInternals: 'showInternals' in raw ? raw.showInternals : false,
};
}

View File

@@ -0,0 +1,11 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export const CONFIG_PANEL_TRANSITION = 'config-panel';
export const TOGGLE_TAB_TRANSITION = 'toggle-tab';
export const TOGGLE_INTERNALS_TRANSITION = 'toggle-internals';
export const EXPAND_ACCORDION_TRANSITION = 'open-accordion';

View File

@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@@ -11,6 +11,7 @@ const path = require('path');
const nextConfig = {
experimental: {
reactCompiler: true,
viewTransition: true,
},
reactStrictMode: true,
webpack: (config, options) => {

View File

@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "cd ../.. && concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run watch\" \"yarn workspace react-compiler-runtime run watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && cd apps/playground && NODE_ENV=development next dev\"",
"build:compiler": "cd ../.. && concurrently -n compiler,runtime \"yarn workspace babel-plugin-react-compiler run build\" \"yarn workspace react-compiler-runtime run build\"",
"build:compiler": "cd ../.. && concurrently -n compiler,runtime \"yarn workspace babel-plugin-react-compiler run build --dts\" \"yarn workspace react-compiler-runtime run build\"",
"build": "yarn build:compiler && next build",
"postbuild": "node ./scripts/downloadFonts.js",
"preinstall": "cd ../.. && yarn install --frozen-lockfile",
@@ -12,7 +12,7 @@
"vercel-build": "yarn build",
"start": "next start",
"lint": "next lint",
"test": "playwright test"
"test": "playwright test --workers=4"
},
"dependencies": {
"@babel/core": "^7.18.9",
@@ -22,42 +22,43 @@
"@babel/plugin-transform-block-scoping": "^7.18.9",
"@babel/plugin-transform-modules-commonjs": "^7.18.9",
"@babel/preset-react": "^7.18.9",
"@babel/preset-typescript": "^7.18.9",
"@babel/preset-typescript": "^7.26.0",
"@babel/traverse": "^7.18.9",
"@babel/types": "7.18.9",
"@babel/types": "7.26.3",
"@heroicons/react": "^1.0.6",
"@monaco-editor/react": "^4.4.6",
"@playwright/test": "^1.42.1",
"@monaco-editor/react": "^4.8.0-rc.2",
"@playwright/test": "^1.56.1",
"@use-gesture/react": "^10.2.22",
"hermes-eslint": "^0.25.0",
"hermes-parser": "^0.25.0",
"invariant": "^2.2.4",
"lru-cache": "^11.2.2",
"lz-string": "^1.5.0",
"monaco-editor": "^0.52.0",
"next": "^15.2.0-canary.64",
"next": "15.6.0-canary.7",
"notistack": "^3.0.0-alpha.7",
"prettier": "^3.3.3",
"pretty-format": "^29.3.1",
"re-resizable": "^6.9.16",
"react": "19.0.0-rc-77b637d6-20241016",
"react-dom": "19.0.0-rc-77b637d6-20241016"
"react": "19.2",
"react-dom": "19.2"
},
"devDependencies": {
"@types/node": "18.11.9",
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
"@types/react": "19.2",
"@types/react-dom": "19.2",
"autoprefixer": "^10.4.13",
"clsx": "^1.2.1",
"concurrently": "^7.4.0",
"eslint": "^8.28.0",
"eslint-config-next": "^15.0.1",
"eslint-config-next": "15.5.2",
"monaco-editor-webpack-plugin": "^7.1.0",
"postcss": "^8.4.31",
"tailwindcss": "^3.2.4",
"wait-on": "^7.2.0"
},
"resolutions": {
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
"@types/react": "19.2",
"@types/react-dom": "19.2"
}
}

View File

@@ -23,7 +23,7 @@ export default defineConfig({
// Test directory
testDir: path.join(__dirname, '__tests__/e2e'),
// If a test fails, retry it additional 2 times
retries: 2,
retries: 3,
// Artifacts folder where screenshots, videos, and traces are stored.
outputDir: 'test-results/',
// Note: we only use text snapshots, so its safe to omit the host environment name
@@ -55,12 +55,16 @@ export default defineConfig({
// contextOptions: {
// ignoreHTTPSErrors: true,
// },
viewport: {width: 1920, height: 1080},
},
projects: [
{
name: 'chromium',
use: {...devices['Desktop Chrome']},
use: {
...devices['Desktop Chrome'],
viewport: {width: 1920, height: 1080},
},
},
// {
// name: 'Desktop Firefox',

View File

@@ -8,8 +8,8 @@ set -eo pipefail
HERE=$(pwd)
cd ../../packages/react-compiler-runtime && yarn --silent link && cd $HERE
cd ../../packages/babel-plugin-react-compiler && yarn --silent link && cd $HERE
cd ../../packages/react-compiler-runtime && yarn --silent link && cd "$HERE"
cd ../../packages/babel-plugin-react-compiler && yarn --silent link && cd "$HERE"
yarn --silent link babel-plugin-react-compiler
yarn --silent link react-compiler-runtime

View File

@@ -69,3 +69,75 @@
scrollbar-width: none; /* Firefox */
}
}
::view-transition-old(.slide-in) {
animation-name: slideOutLeft;
}
::view-transition-new(.slide-in) {
animation-name: slideInLeft;
}
::view-transition-group(.slide-in) {
z-index: 1;
}
::view-transition-old(.slide-out) {
animation-name: slideOutLeft;
}
::view-transition-new(.slide-out) {
animation-name: slideInLeft;
}
::view-transition-group(.slide-out) {
z-index: 1;
}
@keyframes slideOutLeft {
from {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}
@keyframes slideInLeft {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
::view-transition-old(.container),
::view-transition-new(.container) {
height: 100%;
}
::view-transition-old(.accordion-container),
::view-transition-new(.accordion-container) {
height: 100%;
object-fit: none;
object-position: left;
}
::view-transition-old(.tab-highlight),
::view-transition-new(.tab-highlight) {
height: 100%;
}
::view-transition-group(.tab-text) {
z-index: 1;
}
::view-transition-old(.expand-accordion),
::view-transition-new(.expand-accordion) {
width: auto;
}
::view-transition-group(.expand-accordion) {
overflow: clip;
}
/**
* For some reason, the original Monaco editor is still visible to the
* left of the DiffEditor. This is a workaround for better visual clarity.
*/
.monaco-diff-editor .editor.original{
visibility: hidden !important;
}

View File

@@ -6,6 +6,9 @@
"dom.iterable",
"esnext"
],
"types": [
"react/experimental"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -16,7 +19,7 @@
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# React Compiler Development Guide
Note: for general notes about contributing, see the [CONTRIBUTING.md](../CONTRIBUTING.md).
Note: for general notes about contributing, see the [CONTRIBUTING.md](../../CONTRIBUTING.md).
## Compiler Development

View File

@@ -19,13 +19,15 @@
"test": "yarn workspaces run test",
"snap": "yarn workspace babel-plugin-react-compiler run snap",
"snap:build": "yarn workspace snap run build",
"npm:publish": "node scripts/release/publish"
"npm:publish": "node scripts/release/publish",
"eslint-docs": "yarn workspace babel-plugin-react-compiler build && node scripts/build-eslint-docs.js"
},
"dependencies": {
"fs-extra": "^4.0.2",
"react-is": "0.0.0-experimental-4beb1fd8-20241118"
},
"devDependencies": {
"@babel/types": "^7.26.0",
"@tsconfig/strictest": "^2.0.5",
"concurrently": "^7.4.0",
"esbuild": "^0.25.0",
@@ -36,13 +38,15 @@
"prettier": "^3.3.3",
"prettier-plugin-hermes-parser": "^0.26.0",
"prompt-promise": "^1.0.3",
"rimraf": "^5.0.10",
"rimraf": "^6.0.1",
"to-fast-properties": "^2.0.0",
"tsup": "^8.4.0",
"typescript": "^5.4.3",
"wait-on": "^7.2.0",
"yargs": "^17.7.2"
},
"resolutions": {
"rimraf": "5.0.10"
"@babel/types": "7.26.3"
},
"packageManager": "yarn@1.22.22"
}

View File

@@ -9,18 +9,18 @@
"!*.tsbuildinfo"
],
"scripts": {
"build": "rimraf dist && scripts/build.js",
"build": "rimraf dist && tsup",
"test": "./scripts/link-react-compiler-runtime.sh && yarn snap:ci",
"jest": "yarn build && ts-node node_modules/.bin/jest",
"snap": "node ../snap/dist/main.js",
"snap": "yarn workspace snap run snap",
"snap:build": "yarn workspace snap run build",
"snap:ci": "yarn snap:build && yarn snap",
"ts:analyze-trace": "scripts/ts-analyze-trace.sh",
"lint": "yarn eslint src",
"watch": "scripts/build.js --watch"
"watch": "yarn build --dts --watch"
},
"dependencies": {
"@babel/types": "^7.19.0"
"@babel/types": "^7.26.0"
},
"devDependencies": {
"@babel/core": "^7.2.0",
@@ -52,8 +52,8 @@
"react-dom": "0.0.0-experimental-4beb1fd8-20241118",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
"zod": "^3.22.4",
"zod-validation-error": "^2.1.0"
"zod": "^3.25.0 || ^4.0.0",
"zod-validation-error": "^3.5.0 || ^4.0.0"
},
"resolutions": {
"./**/@babel/parser": "7.7.4",

View File

@@ -1,61 +0,0 @@
#!/usr/bin/env node
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const esbuild = require('esbuild');
const yargs = require('yargs');
const path = require('path');
const argv = yargs(process.argv.slice(2))
.options('w', {
alias: 'watch',
default: false,
type: 'boolean',
})
.parse();
const config = {
entryPoints: [path.join(__dirname, '../src/index.ts')],
outfile: path.join(__dirname, '../dist/index.js'),
bundle: true,
external: ['@babel/types'],
format: 'cjs',
platform: 'node',
banner: {
js: `/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @lightSyntaxTransform
* @noflow
* @nolint
* @preventMunge
* @preserve-invariant-messages
*/
"use no memo";`,
},
};
async function main() {
if (argv.w) {
const ctx = await esbuild.context(config);
await ctx.watch();
console.log('watching for changes...');
} else {
await esbuild.build({
sourcemap: true,
minify: false,
...config,
});
}
}
main();

View File

@@ -7,7 +7,4 @@
const makeE2EConfig = require('../jest/makeE2EConfig');
const config = makeE2EConfig('e2e with forget', true);
config.setupFilesAfterEnv = ['<rootDir>/../scripts/jest/setupEnvE2E.js'];
module.exports = config;
module.exports = makeE2EConfig('e2e with forget', true);

View File

@@ -5,19 +5,16 @@
* LICENSE file in the root directory of this source tree.
*/
import {jsx} from '@babel/plugin-syntax-jsx';
import babelJest from 'babel-jest';
import {compile} from 'babel-plugin-react-compiler';
import {
validateEnvironmentConfig,
EnvironmentConfig,
} from 'babel-plugin-react-compiler';
import {execSync} from 'child_process';
import type {NodePath, Visitor} from '@babel/traverse';
import type {CallExpression, FunctionDeclaration} from '@babel/types';
import * as t from '@babel/types';
import {
EnvironmentConfig,
validateEnvironmentConfig,
} from 'babel-plugin-react-compiler';
import {basename} from 'path';
import type {CallExpression} from '@babel/types';
import BabelPluginReactCompiler from 'babel-plugin-react-compiler';
/**
* -- IMPORTANT --
@@ -28,10 +25,19 @@ import {basename} from 'path';
const e2eTransformerCacheKey = 1;
const forgetOptions: EnvironmentConfig = validateEnvironmentConfig({
enableAssumeHooksFollowRulesOfReact: true,
enableFunctionOutlining: false,
});
const debugMode = process.env['DEBUG_FORGET_COMPILER'] != null;
const compilerCacheKey = execSync(
'yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist',
)
.toString()
.trim();
if (debugMode) {
console.log('cachebreaker', compilerCacheKey);
}
module.exports = (useForget: boolean) => {
function createTransformer() {
return babelJest.createTransformer({
@@ -42,15 +48,14 @@ module.exports = (useForget: boolean) => {
plugins: [
useForget
? [
ReactForgetFunctionTransform,
BabelPluginReactCompiler,
{
environment: forgetOptions,
/*
* Jest hashes the babel config as a cache breaker.
* (see https://github.com/jestjs/jest/blob/v29.6.2/packages/babel-jest/src/index.ts#L84)
*/
compilerCacheKey: execSync(
'yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist',
).toString(),
compilerCacheKey,
transformOptionsCacheKey: forgetOptions,
e2eTransformerCacheKey,
},
@@ -105,103 +110,3 @@ module.exports = (useForget: boolean) => {
createTransformer,
};
};
// Mostly copied from react/scripts/babel/transform-forget.js
function isReactComponentLike(fn: NodePath<FunctionDeclaration>): boolean {
let isReactComponent = false;
let hasNoUseForgetDirective = false;
/*
* React components start with an upper case letter,
* React hooks start with `use`
*/
if (
fn.node.id == null ||
(fn.node.id.name[0].toUpperCase() !== fn.node.id.name[0] &&
!/^use[A-Z0-9]/.test(fn.node.id.name))
) {
return false;
}
fn.traverse({
DirectiveLiteral(path) {
if (path.node.value === 'use no forget') {
hasNoUseForgetDirective = true;
}
},
JSX(path) {
// Is there is a JSX node created in the current function context?
if (path.scope.getFunctionParent()?.path.node === fn.node) {
isReactComponent = true;
}
},
CallExpression(path) {
// Is there hook usage?
if (
path.node.callee.type === 'Identifier' &&
!/^use[A-Z0-9]/.test(path.node.callee.name)
) {
isReactComponent = true;
}
},
});
if (hasNoUseForgetDirective) {
return false;
}
return isReactComponent;
}
function ReactForgetFunctionTransform() {
const compiledFns = new Set();
const visitor = {
FunctionDeclaration(fn: NodePath<FunctionDeclaration>, state: any): void {
if (compiledFns.has(fn.node)) {
return;
}
if (!isReactComponentLike(fn)) {
return;
}
if (debugMode) {
const filename = basename(state.file.opts.filename);
if (fn.node.loc && fn.node.id) {
console.log(
` Compiling ${filename}:${fn.node.loc.start.line}:${fn.node.loc.start.column} ${fn.node.id.name}`,
);
} else {
console.log(` Compiling ${filename} ${fn.node.id?.name}`);
}
}
const compiled = compile(
fn,
forgetOptions,
'Other',
'_c',
null,
null,
null,
);
compiledFns.add(compiled);
const fun = t.functionDeclaration(
compiled.id,
compiled.params,
compiled.body,
compiled.generator,
compiled.async,
);
fn.replaceWith(fun);
fn.skip();
},
};
return {
name: 'react-forget-e2e',
inherits: jsx,
visitor,
};
}

View File

@@ -1,16 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const ReactCompilerRuntime = require('react/compiler-runtime');
/*
* Our e2e babel transform currently only compiles functions, not programs.
* As a result, our e2e transpiled code does not contain an import for the
* memo cache function. As a temporary hack, we add a `_c` global, which is
* the name that is used for the import by default.
*/
globalThis._c = ReactCompilerRuntime.c;

View File

@@ -11,6 +11,8 @@ import {
injectReanimatedFlag,
pipelineUsesReanimatedPlugin,
} from '../Entrypoint/Reanimated';
import validateNoUntransformedReferences from '../Entrypoint/ValidateNoUntransformedReferences';
import {CompilerError} from '..';
const ENABLE_REACT_COMPILER_TIMINGS =
process.env['ENABLE_REACT_COMPILER_TIMINGS'] === '1';
@@ -33,44 +35,58 @@ export default function BabelPluginReactCompiler(
*/
Program: {
enter(prog, pass): void {
const filename = pass.filename ?? 'unknown';
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
performance.mark(`${filename}:start`, {
detail: 'BabelPlugin:Program:start',
});
}
let opts = parsePluginOptions(pass.opts);
const isDev =
(typeof __DEV__ !== 'undefined' && __DEV__ === true) ||
process.env['NODE_ENV'] === 'development';
if (
opts.enableReanimatedCheck === true &&
pipelineUsesReanimatedPlugin(pass.file.opts.plugins)
) {
opts = injectReanimatedFlag(opts);
}
if (
opts.environment.enableResetCacheOnSourceFileChanges !== false &&
isDev
) {
opts = {
...opts,
environment: {
...opts.environment,
enableResetCacheOnSourceFileChanges: true,
},
};
}
compileProgram(prog, {
opts,
filename: pass.filename ?? null,
comments: pass.file.ast.comments ?? [],
code: pass.file.code,
});
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
performance.mark(`${filename}:end`, {
detail: 'BabelPlugin:Program:end',
try {
const filename = pass.filename ?? 'unknown';
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
performance.mark(`${filename}:start`, {
detail: 'BabelPlugin:Program:start',
});
}
let opts = parsePluginOptions(pass.opts);
const isDev =
(typeof __DEV__ !== 'undefined' && __DEV__ === true) ||
process.env['NODE_ENV'] === 'development';
if (
opts.enableReanimatedCheck === true &&
pipelineUsesReanimatedPlugin(pass.file.opts.plugins)
) {
opts = injectReanimatedFlag(opts);
}
if (
opts.environment.enableResetCacheOnSourceFileChanges !== false &&
isDev
) {
opts = {
...opts,
environment: {
...opts.environment,
enableResetCacheOnSourceFileChanges: true,
},
};
}
const result = compileProgram(prog, {
opts,
filename: pass.filename ?? null,
comments: pass.file.ast.comments ?? [],
code: pass.file.code,
});
validateNoUntransformedReferences(
prog,
pass.filename ?? null,
opts.logger,
opts.environment,
result,
);
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
performance.mark(`${filename}:end`, {
detail: 'BabelPlugin:Program:end',
});
}
} catch (e) {
if (e instanceof CompilerError) {
throw e.withPrintedMessage(pass.file.code, {eslint: false});
}
throw e;
}
},
exit(_, pass): void {

View File

@@ -17,7 +17,7 @@ export function runBabelPluginReactCompiler(
text: string,
file: string,
language: 'flow' | 'typescript',
options: Partial<PluginOptions> | null,
options: PluginOptions | null,
includeAst: boolean = false,
): BabelCore.BabelFileResult {
const ast = BabelParser.parse(text, {

View File

@@ -7,8 +7,136 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {PluginOptions} from './Options';
import {CompilerError} from '../CompilerError';
import {ProgramContext} from './Imports';
import {ExternalFunction} from '..';
/**
* Gating rewrite for function declarations which are referenced before their
* declaration site.
*
* ```js
* // original
* export default React.memo(Foo);
* function Foo() { ... }
*
* // React compiler optimized + gated
* import {gating} from 'myGating';
* export default React.memo(Foo);
* const gating_result = gating(); <- inserted
* function Foo_optimized() {} <- inserted
* function Foo_unoptimized() {} <- renamed from Foo
* function Foo() { <- inserted function, which can be hoisted by JS engines
* if (gating_result) return Foo_optimized();
* else return Foo_unoptimized();
* }
* ```
*/
function insertAdditionalFunctionDeclaration(
fnPath: NodePath<t.FunctionDeclaration>,
compiled: t.FunctionDeclaration,
programContext: ProgramContext,
gatingFunctionIdentifierName: string,
): void {
const originalFnName = fnPath.node.id;
const originalFnParams = fnPath.node.params;
const compiledParams = fnPath.node.params;
/**
* Note that other than `export default function() {}`, all other function
* declarations must have a binding identifier. Since default exports cannot
* be referenced, it's safe to assume that all function declarations passed
* here will have an identifier.
* https://tc39.es/ecma262/multipage/ecmascript-language-functions-and-classes.html#sec-function-definitions
*/
CompilerError.invariant(originalFnName != null && compiled.id != null, {
reason:
'Expected function declarations that are referenced elsewhere to have a named identifier',
description: null,
details: [
{
kind: 'error',
loc: fnPath.node.loc ?? null,
message: null,
},
],
});
CompilerError.invariant(originalFnParams.length === compiledParams.length, {
reason:
'Expected React Compiler optimized function declarations to have the same number of parameters as source',
description: null,
details: [
{
kind: 'error',
loc: fnPath.node.loc ?? null,
message: null,
},
],
});
const gatingCondition = t.identifier(
programContext.newUid(`${gatingFunctionIdentifierName}_result`),
);
const unoptimizedFnName = t.identifier(
programContext.newUid(`${originalFnName.name}_unoptimized`),
);
const optimizedFnName = t.identifier(
programContext.newUid(`${originalFnName.name}_optimized`),
);
/**
* Step 1: rename existing functions
*/
compiled.id.name = optimizedFnName.name;
fnPath.get('id').replaceInline(unoptimizedFnName);
/**
* Step 2: insert new function declaration
*/
const newParams: Array<t.Identifier | t.RestElement> = [];
const genNewArgs: Array<() => t.Identifier | t.SpreadElement> = [];
for (let i = 0; i < originalFnParams.length; i++) {
const argName = `arg${i}`;
if (originalFnParams[i].type === 'RestElement') {
newParams.push(t.restElement(t.identifier(argName)));
genNewArgs.push(() => t.spreadElement(t.identifier(argName)));
} else {
newParams.push(t.identifier(argName));
genNewArgs.push(() => t.identifier(argName));
}
}
// insertAfter called in reverse order of how nodes should appear in program
fnPath.insertAfter(
t.functionDeclaration(
originalFnName,
newParams,
t.blockStatement([
t.ifStatement(
gatingCondition,
t.returnStatement(
t.callExpression(
compiled.id,
genNewArgs.map(fn => fn()),
),
),
t.returnStatement(
t.callExpression(
unoptimizedFnName,
genNewArgs.map(fn => fn()),
),
),
),
]),
),
);
fnPath.insertBefore(
t.variableDeclaration('const', [
t.variableDeclarator(
gatingCondition,
t.callExpression(t.identifier(gatingFunctionIdentifierName), []),
),
]),
);
fnPath.insertBefore(compiled);
}
export function insertGatedFunctionDeclaration(
fnPath: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
@@ -17,48 +145,71 @@ export function insertGatedFunctionDeclaration(
| t.FunctionDeclaration
| t.ArrowFunctionExpression
| t.FunctionExpression,
gating: NonNullable<PluginOptions['gating']>,
programContext: ProgramContext,
gating: ExternalFunction,
referencedBeforeDeclaration: boolean,
): void {
const gatingExpression = t.conditionalExpression(
t.callExpression(t.identifier(gating.importSpecifierName), []),
buildFunctionExpression(compiled),
buildFunctionExpression(fnPath.node),
);
/*
* Convert function declarations to named variables *unless* this is an
* `export default function ...` since `export default const ...` is
* not supported. For that case we fall through to replacing w the raw
* conditional expression
*/
if (
fnPath.parentPath.node.type !== 'ExportDefaultDeclaration' &&
fnPath.node.type === 'FunctionDeclaration' &&
fnPath.node.id != null
) {
fnPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(fnPath.node.id, gatingExpression),
]),
);
} else if (
fnPath.parentPath.node.type === 'ExportDefaultDeclaration' &&
fnPath.node.type !== 'ArrowFunctionExpression' &&
fnPath.node.id != null
) {
fnPath.insertAfter(
t.exportDefaultDeclaration(t.identifier(fnPath.node.id.name)),
);
fnPath.parentPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(fnPath.node.id.name),
gatingExpression,
),
]),
const gatingImportedName = programContext.addImportSpecifier(gating).name;
if (referencedBeforeDeclaration && fnPath.isFunctionDeclaration()) {
CompilerError.invariant(compiled.type === 'FunctionDeclaration', {
reason: 'Expected compiled node type to match input type',
description: `Got ${compiled.type} but expected FunctionDeclaration`,
details: [
{
kind: 'error',
loc: fnPath.node.loc ?? null,
message: null,
},
],
});
insertAdditionalFunctionDeclaration(
fnPath,
compiled,
programContext,
gatingImportedName,
);
} else {
fnPath.replaceWith(gatingExpression);
const gatingExpression = t.conditionalExpression(
t.callExpression(t.identifier(gatingImportedName), []),
buildFunctionExpression(compiled),
buildFunctionExpression(fnPath.node),
);
/*
* Convert function declarations to named variables *unless* this is an
* `export default function ...` since `export default const ...` is
* not supported. For that case we fall through to replacing w the raw
* conditional expression
*/
if (
fnPath.parentPath.node.type !== 'ExportDefaultDeclaration' &&
fnPath.node.type === 'FunctionDeclaration' &&
fnPath.node.id != null
) {
fnPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(fnPath.node.id, gatingExpression),
]),
);
} else if (
fnPath.parentPath.node.type === 'ExportDefaultDeclaration' &&
fnPath.node.type !== 'ArrowFunctionExpression' &&
fnPath.node.id != null
) {
fnPath.insertAfter(
t.exportDefaultDeclaration(t.identifier(fnPath.node.id.name)),
);
fnPath.parentPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(fnPath.node.id.name),
gatingExpression,
),
]),
);
} else {
fnPath.replaceWith(gatingExpression);
}
}
}

View File

@@ -7,9 +7,20 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {EnvironmentConfig, ExternalFunction, GeneratedSource} from '../HIR';
import {getOrInsertDefault} from '../Utils/utils';
import {Scope as BabelScope} from '@babel/traverse';
import {CompilerError, ErrorCategory} from '../CompilerError';
import {
EnvironmentConfig,
GeneratedSource,
NonLocalImportSpecifier,
} from '../HIR';
import {getOrInsertWith} from '../Utils/utils';
import {ExternalFunction, isHookName} from '../HIR/Environment';
import {Err, Ok, Result} from '../Utils/Result';
import {LoggerEvent, ParsedPluginOptions} from './Options';
import {BabelFn, getReactCompilerRuntimeModule} from './Program';
import {SuppressionRange} from './Suppression';
export function validateRestrictedImports(
path: NodePath<t.Program>,
@@ -27,7 +38,7 @@ export function validateRestrictedImports(
ImportDeclaration(importDeclPath) {
if (restrictedImports.has(importDeclPath.node.source.value)) {
error.push({
severity: ErrorSeverity.Todo,
category: ErrorCategory.Todo,
reason: 'Bailing out due to blocklisted import',
description: `Import from module ${importDeclPath.node.source.value}`,
loc: importDeclPath.node.loc ?? null,
@@ -35,57 +46,287 @@ export function validateRestrictedImports(
}
},
});
if (error.hasErrors()) {
if (error.hasAnyErrors()) {
return error;
} else {
return null;
}
}
export function addImportsToProgram(
path: NodePath<t.Program>,
importList: Array<ExternalFunction>,
): void {
const identifiers: Set<string> = new Set();
const sortedImports: Map<string, Array<string>> = new Map();
for (const {importSpecifierName, source} of importList) {
/*
* Codegen currently does not rename import specifiers, so we do additional
* validation here
*/
CompilerError.invariant(identifiers.has(importSpecifierName) === false, {
reason: `Encountered conflicting import specifier for ${importSpecifierName} in Forget config.`,
description: null,
loc: GeneratedSource,
suggestions: null,
});
CompilerError.invariant(
path.scope.hasBinding(importSpecifierName) === false,
{
reason: `Encountered conflicting import specifiers for ${importSpecifierName} in generated program.`,
description: null,
loc: GeneratedSource,
suggestions: null,
},
);
identifiers.add(importSpecifierName);
type ProgramContextOptions = {
program: NodePath<t.Program>;
suppressions: Array<SuppressionRange>;
opts: ParsedPluginOptions;
filename: string | null;
code: string | null;
hasModuleScopeOptOut: boolean;
};
export class ProgramContext {
/**
* Program and environment context
*/
scope: BabelScope;
opts: ParsedPluginOptions;
filename: string | null;
code: string | null;
reactRuntimeModule: string;
suppressions: Array<SuppressionRange>;
hasModuleScopeOptOut: boolean;
const importSpecifierNameList = getOrInsertDefault(
sortedImports,
source,
[],
);
importSpecifierNameList.push(importSpecifierName);
/*
* This is a hack to work around what seems to be a Babel bug. Babel doesn't
* consistently respect the `skip()` function to avoid revisiting a node within
* a pass, so we use this set to track nodes that we have compiled.
*/
alreadyCompiled: WeakSet<object> | Set<object> = new (WeakSet ?? Set)();
// known generated or referenced identifiers in the program
knownReferencedNames: Set<string> = new Set();
// generated imports
imports: Map<string, Map<string, NonLocalImportSpecifier>> = new Map();
/**
* Metadata from compilation
*/
retryErrors: Array<{fn: BabelFn; error: CompilerError}> = [];
inferredEffectLocations: Set<t.SourceLocation> = new Set();
constructor({
program,
suppressions,
opts,
filename,
code,
hasModuleScopeOptOut,
}: ProgramContextOptions) {
this.scope = program.scope;
this.opts = opts;
this.filename = filename;
this.code = code;
this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target);
this.suppressions = suppressions;
this.hasModuleScopeOptOut = hasModuleScopeOptOut;
}
const stmts: Array<t.ImportDeclaration> = [];
for (const [source, importSpecifierNameList] of sortedImports) {
const importSpecifiers = importSpecifierNameList.map(name => {
const id = t.identifier(name);
return t.importSpecifier(id, id);
isHookName(name: string): boolean {
if (this.opts.environment.hookPattern == null) {
return isHookName(name);
} else {
const match = new RegExp(this.opts.environment.hookPattern).exec(name);
return (
match != null && typeof match[1] === 'string' && isHookName(match[1])
);
}
}
hasReference(name: string): boolean {
return (
this.knownReferencedNames.has(name) ||
this.scope.hasBinding(name) ||
this.scope.hasGlobal(name) ||
this.scope.hasReference(name)
);
}
newUid(name: string): string {
/**
* Don't call babel's generateUid for known hook imports, as
* InferTypes might eventually type `HookKind` based on callee naming
* convention and `_useFoo` is not named as a hook.
*
* Local uid generation is susceptible to check-before-use bugs since we're
* checking for naming conflicts / references long before we actually insert
* the import. (see similar logic in HIRBuilder:resolveBinding)
*/
let uid;
if (this.isHookName(name)) {
uid = name;
let i = 0;
while (this.hasReference(uid)) {
this.knownReferencedNames.add(uid);
uid = `${name}_${i++}`;
}
} else if (!this.hasReference(name)) {
uid = name;
} else {
uid = this.scope.generateUid(name);
}
this.knownReferencedNames.add(uid);
return uid;
}
addMemoCacheImport(): NonLocalImportSpecifier {
return this.addImportSpecifier(
{
source: this.reactRuntimeModule,
importSpecifierName: 'c',
},
'_c',
);
}
/**
*
* @param externalFunction
* @param nameHint if defined, will be used as the name of the import specifier
* @returns
*/
addImportSpecifier(
{source: module, importSpecifierName: specifier}: ExternalFunction,
nameHint?: string,
): NonLocalImportSpecifier {
const maybeBinding = this.imports.get(module)?.get(specifier);
if (maybeBinding != null) {
return {...maybeBinding};
}
const binding: NonLocalImportSpecifier = {
kind: 'ImportSpecifier',
name: this.newUid(nameHint ?? specifier),
module,
imported: specifier,
};
getOrInsertWith(this.imports, module, () => new Map()).set(specifier, {
...binding,
});
return binding;
}
addNewReference(name: string): void {
this.knownReferencedNames.add(name);
}
assertGlobalBinding(
name: string,
localScope?: BabelScope,
): Result<void, CompilerError> {
const scope = localScope ?? this.scope;
if (!scope.hasReference(name) && !scope.hasBinding(name)) {
return Ok(undefined);
}
const error = new CompilerError();
error.push({
category: ErrorCategory.Todo,
reason: 'Encountered conflicting global in generated program',
description: `Conflict from local binding ${name}`,
loc: scope.getBinding(name)?.path.node.loc ?? null,
suggestions: null,
});
return Err(error);
}
logEvent(event: LoggerEvent): void {
if (this.opts.logger != null) {
this.opts.logger.logEvent(this.filename, event);
}
}
}
function getExistingImports(
program: NodePath<t.Program>,
): Map<string, NodePath<t.ImportDeclaration>> {
const existingImports = new Map<string, NodePath<t.ImportDeclaration>>();
program.traverse({
ImportDeclaration(path) {
if (isNonNamespacedImport(path)) {
existingImports.set(path.node.source.value, path);
}
},
});
return existingImports;
}
export function addImportsToProgram(
path: NodePath<t.Program>,
programContext: ProgramContext,
): void {
const existingImports = getExistingImports(path);
const stmts: Array<t.ImportDeclaration | t.VariableDeclaration> = [];
const sortedModules = [...programContext.imports.entries()].sort(([a], [b]) =>
a.localeCompare(b),
);
for (const [moduleName, importsMap] of sortedModules) {
for (const [specifierName, loweredImport] of importsMap) {
/**
* Assert that the import identifier hasn't already be declared in the program.
* Note: we use getBinding here since `Scope.hasBinding` pessimistically returns true
* for all allocated uids (from `Scope.getUid`)
*/
CompilerError.invariant(
path.scope.getBinding(loweredImport.name) == null,
{
reason:
'Encountered conflicting import specifiers in generated program',
description: `Conflict from import ${loweredImport.module}:(${loweredImport.imported} as ${loweredImport.name})`,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
suggestions: null,
},
);
CompilerError.invariant(
loweredImport.module === moduleName &&
loweredImport.imported === specifierName,
{
reason:
'Found inconsistent import specifier. This is an internal bug.',
description: `Expected import ${moduleName}:${specifierName} but found ${loweredImport.module}:${loweredImport.imported}`,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
},
);
}
const sortedImport: Array<NonLocalImportSpecifier> = [
...importsMap.values(),
].sort(({imported: a}, {imported: b}) => a.localeCompare(b));
const importSpecifiers = sortedImport.map(specifier => {
return t.importSpecifier(
t.identifier(specifier.name),
t.identifier(specifier.imported),
);
});
stmts.push(t.importDeclaration(importSpecifiers, t.stringLiteral(source)));
/**
* If an existing import of this module exists (ie `import { ... } from
* '<moduleName>'`), inject new imported specifiers into the list of
* destructured variables.
*/
const maybeExistingImports = existingImports.get(moduleName);
if (maybeExistingImports != null) {
maybeExistingImports.pushContainer('specifiers', importSpecifiers);
} else {
if (path.node.sourceType === 'module') {
stmts.push(
t.importDeclaration(importSpecifiers, t.stringLiteral(moduleName)),
);
} else {
stmts.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.objectPattern(
sortedImport.map(specifier => {
return t.objectProperty(
t.identifier(specifier.imported),
t.identifier(specifier.name),
);
}),
),
t.callExpression(t.identifier('require'), [
t.stringLiteral(moduleName),
]),
),
]),
);
}
}
}
path.unshiftContainer('body', stmts);
}
@@ -93,13 +334,12 @@ export function addImportsToProgram(
/*
* Matches `import { ... } from <moduleName>;`
* but not `import * as React from <moduleName>;`
* `import type { Foo } from <moduleName>;`
*/
function isNonNamespacedImport(
importDeclPath: NodePath<t.ImportDeclaration>,
moduleName: string,
): boolean {
return (
importDeclPath.get('source').node.value === moduleName &&
importDeclPath
.get('specifiers')
.every(specifier => specifier.isImportSpecifier()) &&
@@ -107,94 +347,3 @@ function isNonNamespacedImport(
importDeclPath.node.importKind !== 'typeof'
);
}
function hasExistingNonNamespacedImportOfModule(
program: NodePath<t.Program>,
moduleName: string,
): boolean {
let hasExistingImport = false;
program.traverse({
ImportDeclaration(importDeclPath) {
if (isNonNamespacedImport(importDeclPath, moduleName)) {
hasExistingImport = true;
}
},
});
return hasExistingImport;
}
/*
* If an existing import of React exists (ie `import { ... } from '<moduleName>'`), inject useMemoCache
* into the list of destructured variables.
*/
function addMemoCacheFunctionSpecifierToExistingImport(
program: NodePath<t.Program>,
moduleName: string,
identifierName: string,
): boolean {
let didInsertUseMemoCache = false;
program.traverse({
ImportDeclaration(importDeclPath) {
if (
!didInsertUseMemoCache &&
isNonNamespacedImport(importDeclPath, moduleName)
) {
importDeclPath.pushContainer(
'specifiers',
t.importSpecifier(t.identifier(identifierName), t.identifier('c')),
);
didInsertUseMemoCache = true;
}
},
});
return didInsertUseMemoCache;
}
export function updateMemoCacheFunctionImport(
program: NodePath<t.Program>,
moduleName: string,
useMemoCacheIdentifier: string,
): void {
/*
* If there isn't already an import of * as React, insert it so useMemoCache doesn't
* throw
*/
const hasExistingImport = hasExistingNonNamespacedImportOfModule(
program,
moduleName,
);
if (hasExistingImport) {
const didUpdateImport = addMemoCacheFunctionSpecifierToExistingImport(
program,
moduleName,
useMemoCacheIdentifier,
);
if (!didUpdateImport) {
throw new Error(
`Expected an ImportDeclaration of \`${moduleName}\` in order to update ImportSpecifiers with useMemoCache`,
);
}
} else {
addMemoCacheFunctionImportDeclaration(
program,
moduleName,
useMemoCacheIdentifier,
);
}
}
function addMemoCacheFunctionImportDeclaration(
program: NodePath<t.Program>,
moduleName: string,
localName: string,
): void {
program.unshiftContainer(
'body',
t.importDeclaration(
[t.importSpecifier(t.identifier(localName), t.identifier('c'))],
t.stringLiteral(moduleName),
),
);
}

View File

@@ -6,15 +6,21 @@
*/
import * as t from '@babel/types';
import {z} from 'zod';
import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
import {z} from 'zod/v4';
import {
CompilerDiagnostic,
CompilerError,
CompilerErrorDetail,
CompilerErrorDetailOptions,
} from '../CompilerError';
import {
EnvironmentConfig,
ExternalFunction,
parseEnvironmentConfig,
tryParseExternalFunction,
} from '../HIR/Environment';
import {hasOwnProperty} from '../Utils/utils';
import {fromZodError} from 'zod-validation-error';
import {fromZodError} from 'zod-validation-error/v4';
import {CompilerPipelineValue} from './Pipeline';
const PanicThresholdOptionsSchema = z.enum([
@@ -36,9 +42,17 @@ const PanicThresholdOptionsSchema = z.enum([
]);
export type PanicThresholdOptions = z.infer<typeof PanicThresholdOptionsSchema>;
const DynamicGatingOptionsSchema = z.object({
source: z.string(),
});
export type DynamicGatingOptions = z.infer<typeof DynamicGatingOptionsSchema>;
const CustomOptOutDirectiveSchema = z
.nullable(z.array(z.string()))
.default(null);
type CustomOptOutDirective = z.infer<typeof CustomOptOutDirectiveSchema>;
export type PluginOptions = {
environment: EnvironmentConfig;
export type PluginOptions = Partial<{
environment: Partial<EnvironmentConfig>;
logger: Logger | null;
@@ -64,16 +78,49 @@ export type PluginOptions = {
*/
gating: ExternalFunction | null;
/**
* If specified, this enables dynamic gating which matches `use memo if(...)`
* directives.
*
* Example usage:
* ```js
* // @dynamicGating:{"source":"myModule"}
* export function MyComponent() {
* 'use memo if(isEnabled)';
* return <div>...</div>;
* }
* ```
* This will emit:
* ```js
* import {isEnabled} from 'myModule';
* export const MyComponent = isEnabled()
* ? <optimized version>
* : <original version>;
* ```
*/
dynamicGating: DynamicGatingOptions | null;
panicThreshold: PanicThresholdOptions;
/*
/**
* @deprecated
*
* When enabled, Forget will continue statically analyzing and linting code, but skip over codegen
* passes.
*
* NOTE: ignored if `outputMode` is specified
*
* Defaults to false
*/
noEmit: boolean;
/**
* If specified, overrides `noEmit` and controls the output mode of the compiler.
*
* Defaults to null
*/
outputMode: CompilerOutputMode | null;
/*
* Determines the strategy for determining which functions to compile. Note that regardless of
* which mode is enabled, a component can be opted out by adding the string literal
@@ -97,15 +144,25 @@ export type PluginOptions = {
* provided rules will skip compilation. To disable this feature (never bailout of compilation
* even if the default ESLint is suppressed), pass an empty array.
*/
eslintSuppressionRules?: Array<string> | null | undefined;
eslintSuppressionRules: Array<string> | null | undefined;
/**
* Whether to report "suppression" errors for Flow suppressions. If false, suppression errors
* are only emitted for ESLint suppressions
*/
flowSuppressions: boolean;
/*
* Ignore 'use no forget' annotations. Helpful during testing but should not be used in production.
*/
ignoreUseNoForget: boolean;
sources?: Array<string> | ((filename: string) => boolean) | null;
/**
* Unstable / do not use
*/
customOptOutDirectives: CustomOptOutDirective;
sources: Array<string> | ((filename: string) => boolean) | null;
/**
* The compiler has customized support for react-native-reanimated, intended as a temporary workaround.
@@ -120,7 +177,11 @@ export type PluginOptions = {
* a userspace approximation of runtime APIs.
*/
target: CompilerReactTarget;
};
}>;
export type ParsedPluginOptions = Required<
Omit<PluginOptions, 'environment'>
> & {environment: EnvironmentConfig};
const CompilerReactTargetSchema = z.union([
z.literal('17'),
@@ -162,6 +223,19 @@ const CompilationModeSchema = z.enum([
export type CompilationMode = z.infer<typeof CompilationModeSchema>;
const CompilerOutputModeSchema = z.enum([
// Build optimized for SSR, with client features removed
'ssr',
// Build optimized for the client, with auto memoization
'client',
// Build optimized for the client without auto memo
'client-no-memo',
// Lint mode, the output is unused but validations should run
'lint',
]);
export type CompilerOutputMode = z.infer<typeof CompilerOutputModeSchema>;
/**
* Represents 'events' that may occur during compilation. Events are only
* recorded when a logger is set (through the config).
@@ -181,12 +255,14 @@ export type LoggerEvent =
| CompileDiagnosticEvent
| CompileSkipEvent
| PipelineErrorEvent
| TimingEvent;
| TimingEvent
| AutoDepsDecorationsEvent
| AutoDepsEligibleEvent;
export type CompileErrorEvent = {
kind: 'CompileError';
fnLoc: t.SourceLocation | null;
detail: CompilerErrorDetailOptions;
detail: CompilerErrorDetail | CompilerDiagnostic;
};
export type CompileDiagnosticEvent = {
kind: 'CompileDiagnostic';
@@ -218,19 +294,31 @@ export type TimingEvent = {
kind: 'Timing';
measurement: PerformanceMeasure;
};
export type AutoDepsDecorationsEvent = {
kind: 'AutoDepsDecorations';
fnLoc: t.SourceLocation;
decorations: Array<t.SourceLocation>;
};
export type AutoDepsEligibleEvent = {
kind: 'AutoDepsEligible';
fnLoc: t.SourceLocation;
depArrayLoc: t.SourceLocation;
};
export type Logger = {
logEvent: (filename: string | null, event: LoggerEvent) => void;
debugLogIRs?: (value: CompilerPipelineValue) => void;
};
export const defaultOptions: PluginOptions = {
export const defaultOptions: ParsedPluginOptions = {
compilationMode: 'infer',
panicThreshold: 'none',
environment: parseEnvironmentConfig({}).unwrap(),
logger: null,
gating: null,
noEmit: false,
outputMode: null,
dynamicGating: null,
eslintSuppressionRules: null,
flowSuppressions: true,
ignoreUseNoForget: false,
@@ -238,10 +326,11 @@ export const defaultOptions: PluginOptions = {
return filename.indexOf('node_modules') === -1;
},
enableReanimatedCheck: true,
customOptOutDirectives: null,
target: '19',
} as const;
};
export function parsePluginOptions(obj: unknown): PluginOptions {
export function parsePluginOptions(obj: unknown): ParsedPluginOptions {
if (obj == null || typeof obj !== 'object') {
return defaultOptions;
}
@@ -271,6 +360,48 @@ export function parsePluginOptions(obj: unknown): PluginOptions {
parsedOptions[key] = parseTargetConfig(value);
break;
}
case 'gating': {
if (value == null) {
parsedOptions[key] = null;
} else {
parsedOptions[key] = tryParseExternalFunction(value);
}
break;
}
case 'dynamicGating': {
if (value == null) {
parsedOptions[key] = null;
} else {
const result = DynamicGatingOptionsSchema.safeParse(value);
if (result.success) {
parsedOptions[key] = result.data;
} else {
CompilerError.throwInvalidConfig({
reason:
'Could not parse dynamic gating. Update React Compiler config to fix the error',
description: `${fromZodError(result.error)}`,
loc: null,
suggestions: null,
});
}
}
break;
}
case 'customOptOutDirectives': {
const result = CustomOptOutDirectiveSchema.safeParse(value);
if (result.success) {
parsedOptions[key] = result.data;
} else {
CompilerError.throwInvalidConfig({
reason:
'Could not parse custom opt out directives. Update React Compiler config to fix the error',
description: `${fromZodError(result.error)}`,
loc: null,
suggestions: null,
});
}
break;
}
default: {
parsedOptions[key] = value;
}

View File

@@ -8,7 +8,7 @@
import {NodePath} from '@babel/traverse';
import * as t from '@babel/types';
import prettyFormat from 'pretty-format';
import {Logger} from '.';
import {CompilerOutputMode, Logger, ProgramContext} from '.';
import {
HIRFunction,
ReactiveFunction,
@@ -32,9 +32,7 @@ import {findContextIdentifiers} from '../HIR/FindContextIdentifiers';
import {
analyseFunctions,
dropManualMemoization,
inferMutableRanges,
inferReactivePlaces,
inferReferenceEffects,
inlineImmediatelyInvokedFunctionExpressions,
inferEffectDependencies,
} from '../Inference';
@@ -91,15 +89,24 @@ import {
} from '../Validation';
import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLocalsNotReassignedAfterRender';
import {outlineFunctions} from '../Optimization/OutlineFunctions';
import {propagatePhiTypes} from '../TypeInference/PropagatePhiTypes';
import {lowerContextAccess} from '../Optimization/LowerContextAccess';
import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetStateInPassiveEffects';
import {validateNoSetStateInEffects} from '../Validation/ValidateNoSetStateInEffects';
import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement';
import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
import {outlineJSX} from '../Optimization/OutlineJsx';
import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
import {transformFire} from '../Transform';
import {validateNoImpureFunctionsInRender} from '../Validation/ValiateNoImpureFunctionsInRender';
import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender';
import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions';
import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects';
import {inferMutationAliasingRanges} from '../Inference/InferMutationAliasingRanges';
import {validateNoDerivedComputationsInEffects} from '../Validation/ValidateNoDerivedComputationsInEffects';
import {validateNoDerivedComputationsInEffects_exp} from '../Validation/ValidateNoDerivedComputationsInEffects_exp';
import {nameAnonymousFunctions} from '../Transform/NameAnonymousFunctions';
import {optimizeForSSR} from '../Optimization/OptimizeForSSR';
import {validateExhaustiveDependencies} from '../Validation/ValidateExhaustiveDependencies';
import {validateSourceLocations} from '../Validation/ValidateSourceLocations';
export type CompilerPipelineValue =
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -113,7 +120,8 @@ function run(
>,
config: EnvironmentConfig,
fnType: ReactFunctionType,
useMemoCacheIdentifier: string,
mode: CompilerOutputMode,
programContext: ProgramContext,
logger: Logger | null,
filename: string | null,
code: string | null,
@@ -122,12 +130,14 @@ function run(
const env = new Environment(
func.scope,
fnType,
mode,
config,
contextIdentifiers,
func,
logger,
filename,
code,
useMemoCacheIdentifier,
programContext,
);
env.logger?.debugLogIRs?.({
kind: 'debug',
@@ -157,15 +167,15 @@ function runWithEnvironment(
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
validateContextVariableLValues(hir);
validateUseMemo(hir);
validateUseMemo(hir).unwrap();
if (
env.enableDropManualMemoization &&
!env.config.enablePreserveExistingManualUseMemo &&
!env.config.disableMemoizationForDebugging &&
!env.config.enableChangeDetectionForDebugging &&
!env.config.enableMinimalTransformsForRetry
!env.config.enableChangeDetectionForDebugging
) {
dropManualMemoization(hir);
dropManualMemoization(hir).unwrap();
log({kind: 'hir', name: 'DropManualMemoization', value: hir});
}
@@ -196,8 +206,13 @@ function runWithEnvironment(
inferTypes(hir);
log({kind: 'hir', name: 'InferTypes', value: hir});
if (env.config.validateHooksUsage) {
validateHooksUsage(hir);
if (env.enableValidations) {
if (env.config.validateHooksUsage) {
validateHooksUsage(hir).unwrap();
}
if (env.config.validateNoCapitalizedCalls) {
validateNoCapitalizedCalls(hir).unwrap();
}
}
if (env.config.enableFire) {
@@ -205,10 +220,6 @@ function runWithEnvironment(
log({kind: 'hir', name: 'TransformFire', value: hir});
}
if (env.config.validateNoCapitalizedCalls) {
validateNoCapitalizedCalls(hir);
}
if (env.config.lowerContextAccess) {
lowerContextAccess(hir, env.config.lowerContextAccess);
}
@@ -219,10 +230,18 @@ function runWithEnvironment(
analyseFunctions(hir);
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
inferReferenceEffects(hir);
log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
const mutabilityAliasingErrors = inferMutationAliasingEffects(hir);
log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir});
if (env.enableValidations) {
if (mutabilityAliasingErrors.isErr()) {
throw mutabilityAliasingErrors.unwrapErr();
}
}
validateLocalsNotReassignedAfterRender(hir);
if (env.outputMode === 'ssr') {
optimizeForSSR(hir);
log({kind: 'hir', name: 'OptimizeForSSR', value: hir});
}
// Note: Has to come after infer reference effects because "dead" code may still affect inference
deadCodeElimination(hir);
@@ -236,36 +255,61 @@ function runWithEnvironment(
pruneMaybeThrows(hir);
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
inferMutableRanges(hir);
log({kind: 'hir', name: 'InferMutableRanges', value: hir});
if (env.config.assertValidMutableRanges) {
assertValidMutableRanges(hir);
const mutabilityAliasingRangeErrors = inferMutationAliasingRanges(hir, {
isFunctionExpression: false,
});
log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir});
if (env.enableValidations) {
if (mutabilityAliasingRangeErrors.isErr()) {
throw mutabilityAliasingRangeErrors.unwrapErr();
}
validateLocalsNotReassignedAfterRender(hir);
}
if (env.config.validateRefAccessDuringRender) {
validateNoRefAccessInRender(hir);
}
if (env.enableValidations) {
if (env.config.assertValidMutableRanges) {
assertValidMutableRanges(hir);
}
if (env.config.validateNoSetStateInRender) {
validateNoSetStateInRender(hir);
}
if (env.config.validateRefAccessDuringRender) {
validateNoRefAccessInRender(hir).unwrap();
}
if (env.config.validateNoSetStateInPassiveEffects) {
validateNoSetStateInPassiveEffects(hir);
}
if (env.config.validateNoSetStateInRender) {
validateNoSetStateInRender(hir).unwrap();
}
if (env.config.validateNoJSXInTryStatements) {
validateNoJSXInTryStatement(hir);
}
if (env.config.validateNoDerivedComputationsInEffects_exp) {
env.logErrors(validateNoDerivedComputationsInEffects_exp(hir));
} else if (env.config.validateNoDerivedComputationsInEffects) {
validateNoDerivedComputationsInEffects(hir);
}
if (env.config.validateNoImpureFunctionsInRender) {
validateNoImpureFunctionsInRender(hir);
if (env.config.validateNoSetStateInEffects) {
env.logErrors(validateNoSetStateInEffects(hir, env));
}
if (env.config.validateNoJSXInTryStatements) {
env.logErrors(validateNoJSXInTryStatement(hir));
}
if (env.config.validateNoImpureFunctionsInRender) {
validateNoImpureFunctionsInRender(hir).unwrap();
}
validateNoFreezingKnownMutableFunctions(hir).unwrap();
}
inferReactivePlaces(hir);
log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
if (env.enableValidations) {
if (env.config.validateExhaustiveMemoizationDependencies) {
// NOTE: this relies on reactivity inference running first
validateExhaustiveDependencies(hir).unwrap();
}
}
rewriteInstructionKindsBasedOnReassignment(hir);
log({
kind: 'hir',
@@ -273,14 +317,16 @@ function runWithEnvironment(
value: hir,
});
propagatePhiTypes(hir);
log({
kind: 'hir',
name: 'PropagatePhiTypes',
value: hir,
});
if (env.enableValidations && env.config.validateStaticComponents) {
env.logErrors(validateStaticComponents(hir));
}
if (!env.config.enableMinimalTransformsForRetry) {
if (env.enableMemoization) {
/**
* Only create reactive scopes (which directly map to generated memo blocks)
* if inferred memoization is enabled. This makes all later passes which
* transform reactive-scope labeled instructions no-ops.
*/
inferReactiveScopeVariables(hir);
log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
}
@@ -296,6 +342,15 @@ function runWithEnvironment(
outlineJSX(hir);
}
if (env.config.enableNameAnonymousFunctions) {
nameAnonymousFunctions(hir);
log({
kind: 'hir',
name: 'NameAnonymousFunctions',
value: hir,
});
}
if (env.config.enableFunctionOutlining) {
outlineFunctions(hir, fbtOperands);
log({kind: 'hir', name: 'OutlineFunctions', value: hir});
@@ -370,6 +425,11 @@ function runWithEnvironment(
if (env.config.inferEffectDependencies) {
inferEffectDependencies(hir);
log({
kind: 'hir',
name: 'InferEffectDependencies',
value: hir,
});
}
if (env.config.inlineJsxTransform) {
@@ -492,14 +552,14 @@ function runWithEnvironment(
});
if (env.config.validateMemoizedEffectDependencies) {
validateMemoizedEffectDependencies(reactiveFunction);
validateMemoizedEffectDependencies(reactiveFunction).unwrap();
}
if (
env.config.enablePreserveExistingMemoizationGuarantees ||
env.config.validatePreserveExistingMemoizationGuarantees
) {
validatePreservedManualMemoization(reactiveFunction);
validatePreservedManualMemoization(reactiveFunction).unwrap();
}
const ast = codegenFunction(reactiveFunction, {
@@ -511,6 +571,10 @@ function runWithEnvironment(
log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
}
if (env.config.validateSourceLocations) {
validateSourceLocations(func, ast).unwrap();
}
/**
* This flag should be only set for unit / fixture tests to check
* that Forget correctly handles unexpected errors (e.g. exceptions
@@ -529,7 +593,8 @@ export function compileFn(
>,
config: EnvironmentConfig,
fnType: ReactFunctionType,
useMemoCacheIdentifier: string,
mode: CompilerOutputMode,
programContext: ProgramContext,
logger: Logger | null,
filename: string | null,
code: string | null,
@@ -538,7 +603,8 @@ export function compileFn(
func,
config,
fnType,
useMemoCacheIdentifier,
mode,
programContext,
logger,
filename,
code,

View File

@@ -1,8 +1,18 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type * as BabelCore from '@babel/core';
import {hasOwnProperty} from '../Utils/utils';
import {PluginOptions} from './Options';
import {ParsedPluginOptions} from './Options';
function hasModule(name: string): boolean {
if (typeof require === 'undefined') {
return false;
}
try {
return !!require.resolve(name);
} catch (error: any) {
@@ -42,7 +52,9 @@ export function pipelineUsesReanimatedPlugin(
return hasModule('react-native-reanimated');
}
export function injectReanimatedFlag(options: PluginOptions): PluginOptions {
export function injectReanimatedFlag(
options: ParsedPluginOptions,
): ParsedPluginOptions {
return {
...options,
environment: {

View File

@@ -8,10 +8,10 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {
CompilerDiagnostic,
CompilerError,
CompilerErrorDetail,
CompilerSuggestionOperation,
ErrorSeverity,
ErrorCategory,
} from '../CompilerError';
import {assertExhaustive} from '../Utils/utils';
import {GeneratedSource} from '../HIR';
@@ -78,7 +78,7 @@ export function filterSuppressionsThatAffectFunction(
export function findProgramSuppressions(
programComments: Array<t.Comment>,
ruleNames: Array<string>,
ruleNames: Array<string> | null,
flowSuppressions: boolean,
): Array<SuppressionRange> {
const suppressionRanges: Array<SuppressionRange> = [];
@@ -86,12 +86,18 @@ export function findProgramSuppressions(
let enableComment: t.Comment | null = null;
let source: SuppressionSource | null = null;
const rulePattern = `(${ruleNames.join('|')})`;
const disableNextLinePattern = new RegExp(
`eslint-disable-next-line ${rulePattern}`,
);
const disablePattern = new RegExp(`eslint-disable ${rulePattern}`);
const enablePattern = new RegExp(`eslint-enable ${rulePattern}`);
let disableNextLinePattern: RegExp | null = null;
let disablePattern: RegExp | null = null;
let enablePattern: RegExp | null = null;
if (ruleNames != null && ruleNames.length !== 0) {
const rulePattern = `(${ruleNames.join('|')})`;
disableNextLinePattern = new RegExp(
`eslint-disable-next-line ${rulePattern}`,
);
disablePattern = new RegExp(`eslint-disable ${rulePattern}`);
enablePattern = new RegExp(`eslint-enable ${rulePattern}`);
}
const flowSuppressionPattern = new RegExp(
'\\$(FlowFixMe\\w*|FlowExpectedError|FlowIssue)\\[react\\-rule',
);
@@ -107,6 +113,7 @@ export function findProgramSuppressions(
* CommentLine within the block.
*/
disableComment == null &&
disableNextLinePattern != null &&
disableNextLinePattern.test(comment.value)
) {
disableComment = comment;
@@ -124,12 +131,16 @@ export function findProgramSuppressions(
source = 'Flow';
}
if (disablePattern.test(comment.value)) {
if (disablePattern != null && disablePattern.test(comment.value)) {
disableComment = comment;
source = 'Eslint';
}
if (enablePattern.test(comment.value) && source === 'Eslint') {
if (
enablePattern != null &&
enablePattern.test(comment.value) &&
source === 'Eslint'
) {
enableComment = comment;
}
@@ -152,7 +163,14 @@ export function suppressionsToCompilerError(
): CompilerError {
CompilerError.invariant(suppressionRanges.length !== 0, {
reason: `Expected at least suppression comment source range`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
const error = new CompilerError();
for (const suppressionRange of suppressionRanges) {
@@ -181,12 +199,11 @@ export function suppressionsToCompilerError(
'Unhandled suppression source',
);
}
error.pushErrorDetail(
new CompilerErrorDetail({
reason: `${reason}. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior`,
description: suppressionRange.disableComment.value.trim(),
severity: ErrorSeverity.InvalidReact,
loc: suppressionRange.disableComment.loc ?? null,
error.pushDiagnostic(
CompilerDiagnostic.create({
reason: reason,
description: `React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression \`${suppressionRange.disableComment.value.trim()}\``,
category: ErrorCategory.Suppression,
suggestions: [
{
description: suggestion,
@@ -197,6 +214,10 @@ export function suppressionsToCompilerError(
op: CompilerSuggestionOperation.Remove,
},
],
}).withDetails({
kind: 'error',
loc: suppressionRange.disableComment.loc ?? null,
message: 'Found React rule suppression',
}),
);
}

View File

@@ -0,0 +1,341 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {CompilerError, EnvironmentConfig, Logger} from '..';
import {getOrInsertWith} from '../Utils/utils';
import {Environment, GeneratedSource} from '../HIR';
import {DEFAULT_EXPORT} from '../HIR/Environment';
import {CompileProgramMetadata} from './Program';
import {
CompilerDiagnostic,
CompilerDiagnosticOptions,
ErrorCategory,
} from '../CompilerError';
function throwInvalidReact(
options: CompilerDiagnosticOptions,
{logger, filename}: TraversalState,
): never {
logger?.logEvent(filename, {
kind: 'CompileError',
fnLoc: null,
detail: new CompilerDiagnostic(options),
});
CompilerError.throwDiagnostic(options);
}
function isAutodepsSigil(
arg: NodePath<t.ArgumentPlaceholder | t.SpreadElement | t.Expression>,
): boolean {
// Check for AUTODEPS identifier imported from React
if (arg.isIdentifier() && arg.node.name === 'AUTODEPS') {
const binding = arg.scope.getBinding(arg.node.name);
if (binding && binding.path.isImportSpecifier()) {
const importSpecifier = binding.path.node as t.ImportSpecifier;
if (importSpecifier.imported.type === 'Identifier') {
return (importSpecifier.imported as t.Identifier).name === 'AUTODEPS';
}
}
return false;
}
// Check for React.AUTODEPS member expression
if (arg.isMemberExpression() && !arg.node.computed) {
const object = arg.get('object');
const property = arg.get('property');
if (
object.isIdentifier() &&
object.node.name === 'React' &&
property.isIdentifier() &&
property.node.name === 'AUTODEPS'
) {
return true;
}
}
return false;
}
function assertValidEffectImportReference(
autodepsIndex: number,
paths: Array<NodePath<t.Node>>,
context: TraversalState,
): void {
for (const path of paths) {
const parent = path.parentPath;
if (parent != null && parent.isCallExpression()) {
const args = parent.get('arguments');
const maybeCalleeLoc = path.node.loc;
const hasInferredEffect =
maybeCalleeLoc != null &&
context.inferredEffectLocations.has(maybeCalleeLoc);
/**
* Error on effect calls that still have AUTODEPS in their args
*/
const hasAutodepsArg = args.some(isAutodepsSigil);
if (hasAutodepsArg && !hasInferredEffect) {
const maybeErrorDiagnostic = matchCompilerDiagnostic(
path,
context.transformErrors,
);
/**
* Note that we cannot easily check the type of the first argument here,
* as it may have already been transformed by the compiler (and not
* memoized).
*/
throwInvalidReact(
{
category: ErrorCategory.AutomaticEffectDependencies,
reason:
'Cannot infer dependencies of this effect. This will break your build!',
description:
'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics' +
(maybeErrorDiagnostic ? ` ${maybeErrorDiagnostic}` : ''),
details: [
{
kind: 'error',
message: 'Cannot infer dependencies',
loc: parent.node.loc ?? GeneratedSource,
},
],
},
context,
);
}
}
}
}
function assertValidFireImportReference(
paths: Array<NodePath<t.Node>>,
context: TraversalState,
): void {
if (paths.length > 0) {
const maybeErrorDiagnostic = matchCompilerDiagnostic(
paths[0],
context.transformErrors,
);
throwInvalidReact(
{
category: ErrorCategory.Fire,
reason: '[Fire] Untransformed reference to compiler-required feature.',
description:
'Either remove this `fire` call or ensure it is successfully transformed by the compiler' +
(maybeErrorDiagnostic != null ? ` ${maybeErrorDiagnostic}` : ''),
details: [
{
kind: 'error',
message: 'Untransformed `fire` call',
loc: paths[0].node.loc ?? GeneratedSource,
},
],
},
context,
);
}
}
export default function validateNoUntransformedReferences(
path: NodePath<t.Program>,
filename: string | null,
logger: Logger | null,
env: EnvironmentConfig,
compileResult: CompileProgramMetadata | null,
): void {
const moduleLoadChecks = new Map<
string,
Map<string, CheckInvalidReferenceFn>
>();
if (env.enableFire) {
/**
* Error on any untransformed references to `fire` (e.g. including non-call
* expressions)
*/
for (const module of Environment.knownReactModules) {
const react = getOrInsertWith(moduleLoadChecks, module, () => new Map());
react.set('fire', assertValidFireImportReference);
}
}
if (env.inferEffectDependencies) {
for (const {
function: {source, importSpecifierName},
autodepsIndex,
} of env.inferEffectDependencies) {
const module = getOrInsertWith(moduleLoadChecks, source, () => new Map());
module.set(
importSpecifierName,
assertValidEffectImportReference.bind(null, autodepsIndex),
);
}
}
if (moduleLoadChecks.size > 0) {
transformProgram(path, moduleLoadChecks, filename, logger, compileResult);
}
}
type TraversalState = {
shouldInvalidateScopes: boolean;
program: NodePath<t.Program>;
logger: Logger | null;
filename: string | null;
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>;
inferredEffectLocations: Set<t.SourceLocation>;
};
type CheckInvalidReferenceFn = (
paths: Array<NodePath<t.Node>>,
context: TraversalState,
) => void;
function validateImportSpecifier(
specifier: NodePath<t.ImportSpecifier>,
importSpecifierChecks: Map<string, CheckInvalidReferenceFn>,
state: TraversalState,
): void {
const imported = specifier.get('imported');
const specifierName: string =
imported.node.type === 'Identifier'
? imported.node.name
: imported.node.value;
const checkFn = importSpecifierChecks.get(specifierName);
if (checkFn == null) {
return;
}
if (state.shouldInvalidateScopes) {
state.shouldInvalidateScopes = false;
state.program.scope.crawl();
}
const local = specifier.get('local');
const binding = local.scope.getBinding(local.node.name);
CompilerError.invariant(binding != null, {
reason: 'Expected binding to be found for import specifier',
description: null,
details: [
{
kind: 'error',
loc: local.node.loc ?? null,
message: null,
},
],
});
checkFn(binding.referencePaths, state);
}
function validateNamespacedImport(
specifier: NodePath<t.ImportNamespaceSpecifier | t.ImportDefaultSpecifier>,
importSpecifierChecks: Map<string, CheckInvalidReferenceFn>,
state: TraversalState,
): void {
if (state.shouldInvalidateScopes) {
state.shouldInvalidateScopes = false;
state.program.scope.crawl();
}
const local = specifier.get('local');
const binding = local.scope.getBinding(local.node.name);
const defaultCheckFn = importSpecifierChecks.get(DEFAULT_EXPORT);
CompilerError.invariant(binding != null, {
reason: 'Expected binding to be found for import specifier',
description: null,
details: [
{
kind: 'error',
loc: local.node.loc ?? null,
message: null,
},
],
});
const filteredReferences = new Map<
CheckInvalidReferenceFn,
Array<NodePath<t.Node>>
>();
for (const reference of binding.referencePaths) {
if (defaultCheckFn != null) {
getOrInsertWith(filteredReferences, defaultCheckFn, () => []).push(
reference,
);
}
const parent = reference.parentPath;
if (
parent != null &&
parent.isMemberExpression() &&
parent.get('object') === reference
) {
if (parent.node.computed || parent.node.property.type !== 'Identifier') {
continue;
}
const checkFn = importSpecifierChecks.get(parent.node.property.name);
if (checkFn != null) {
getOrInsertWith(filteredReferences, checkFn, () => []).push(parent);
}
}
}
for (const [checkFn, references] of filteredReferences) {
checkFn(references, state);
}
}
function transformProgram(
path: NodePath<t.Program>,
moduleLoadChecks: Map<string, Map<string, CheckInvalidReferenceFn>>,
filename: string | null,
logger: Logger | null,
compileResult: CompileProgramMetadata | null,
): void {
const traversalState: TraversalState = {
shouldInvalidateScopes: true,
program: path,
filename,
logger,
transformErrors: compileResult?.retryErrors ?? [],
inferredEffectLocations:
compileResult?.inferredEffectLocations ?? new Set(),
};
path.traverse({
ImportDeclaration(path: NodePath<t.ImportDeclaration>) {
const importSpecifierChecks = moduleLoadChecks.get(
path.node.source.value,
);
if (importSpecifierChecks == null) {
return;
}
const specifiers = path.get('specifiers');
for (const specifier of specifiers) {
if (specifier.isImportSpecifier()) {
validateImportSpecifier(
specifier,
importSpecifierChecks,
traversalState,
);
} else {
validateNamespacedImport(
specifier as NodePath<
t.ImportNamespaceSpecifier | t.ImportDefaultSpecifier
>,
importSpecifierChecks,
traversalState,
);
}
}
},
});
}
function matchCompilerDiagnostic(
badReference: NodePath<t.Node>,
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
): string | null {
for (const {fn, error} of transformErrors) {
if (fn.isAncestor(badReference)) {
return error.toString();
}
}
return null;
}

View File

@@ -0,0 +1,752 @@
/**
* TypeScript definitions for Flow type JSON representations
* Based on the output of /data/sandcastle/boxes/fbsource/fbcode/flow/src/typing/convertTypes.ml
*/
// Base type for all Flow types with a kind field
export interface BaseFlowType {
kind: string;
}
// Type for representing polarity
export type Polarity = 'positive' | 'negative' | 'neutral';
// Type for representing a name that might be null
export type OptionalName = string | null;
// Open type
export interface OpenType extends BaseFlowType {
kind: 'Open';
}
// Def type
export interface DefType extends BaseFlowType {
kind: 'Def';
def: DefT;
}
// Eval type
export interface EvalType extends BaseFlowType {
kind: 'Eval';
type: FlowType;
destructor: Destructor;
}
// Generic type
export interface GenericType extends BaseFlowType {
kind: 'Generic';
name: string;
bound: FlowType;
no_infer: boolean;
}
// ThisInstance type
export interface ThisInstanceType extends BaseFlowType {
kind: 'ThisInstance';
instance: InstanceT;
is_this: boolean;
name: string;
}
// ThisTypeApp type
export interface ThisTypeAppType extends BaseFlowType {
kind: 'ThisTypeApp';
t1: FlowType;
t2: FlowType;
t_list?: Array<FlowType>;
}
// TypeApp type
export interface TypeAppType extends BaseFlowType {
kind: 'TypeApp';
type: FlowType;
targs: Array<FlowType>;
from_value: boolean;
use_desc: boolean;
}
// FunProto type
export interface FunProtoType extends BaseFlowType {
kind: 'FunProto';
}
// ObjProto type
export interface ObjProtoType extends BaseFlowType {
kind: 'ObjProto';
}
// NullProto type
export interface NullProtoType extends BaseFlowType {
kind: 'NullProto';
}
// FunProtoBind type
export interface FunProtoBindType extends BaseFlowType {
kind: 'FunProtoBind';
}
// Intersection type
export interface IntersectionType extends BaseFlowType {
kind: 'Intersection';
members: Array<FlowType>;
}
// Union type
export interface UnionType extends BaseFlowType {
kind: 'Union';
members: Array<FlowType>;
}
// Maybe type
export interface MaybeType extends BaseFlowType {
kind: 'Maybe';
type: FlowType;
}
// Optional type
export interface OptionalType extends BaseFlowType {
kind: 'Optional';
type: FlowType;
use_desc: boolean;
}
// Keys type
export interface KeysType extends BaseFlowType {
kind: 'Keys';
type: FlowType;
}
// Annot type
export interface AnnotType extends BaseFlowType {
kind: 'Annot';
type: FlowType;
use_desc: boolean;
}
// Opaque type
export interface OpaqueType extends BaseFlowType {
kind: 'Opaque';
opaquetype: {
opaque_id: string;
underlying_t: FlowType | null;
super_t: FlowType | null;
opaque_type_args: Array<{
name: string;
type: FlowType;
polarity: Polarity;
}>;
opaque_name: string;
};
}
// Namespace type
export interface NamespaceType extends BaseFlowType {
kind: 'Namespace';
namespace_symbol: {
symbol: string;
};
values_type: FlowType;
types_tmap: PropertyMap;
}
// Any type
export interface AnyType extends BaseFlowType {
kind: 'Any';
}
// StrUtil type
export interface StrUtilType extends BaseFlowType {
kind: 'StrUtil';
op: 'StrPrefix' | 'StrSuffix';
prefix?: string;
suffix?: string;
remainder?: FlowType;
}
// TypeParam definition
export interface TypeParam {
name: string;
bound: FlowType;
polarity: Polarity;
default: FlowType | null;
}
// EnumInfo types
export type EnumInfo = ConcreteEnum | AbstractEnum;
export interface ConcreteEnum {
kind: 'ConcreteEnum';
enum_name: string;
enum_id: string;
members: Array<string>;
representation_t: FlowType;
has_unknown_members: boolean;
}
export interface AbstractEnum {
kind: 'AbstractEnum';
representation_t: FlowType;
}
// CanonicalRendersForm types
export type CanonicalRendersForm =
| InstrinsicRenders
| NominalRenders
| StructuralRenders
| DefaultRenders;
export interface InstrinsicRenders {
kind: 'InstrinsicRenders';
name: string;
}
export interface NominalRenders {
kind: 'NominalRenders';
renders_id: string;
renders_name: string;
renders_super: FlowType;
}
export interface StructuralRenders {
kind: 'StructuralRenders';
renders_variant: 'RendersNormal' | 'RendersMaybe' | 'RendersStar';
renders_structural_type: FlowType;
}
export interface DefaultRenders {
kind: 'DefaultRenders';
}
// InstanceT definition
export interface InstanceT {
inst: InstType;
static: FlowType;
super: FlowType;
implements: Array<FlowType>;
}
// InstType definition
export interface InstType {
class_name: string | null;
class_id: string;
type_args: Array<{
name: string;
type: FlowType;
polarity: Polarity;
}>;
own_props: PropertyMap;
proto_props: PropertyMap;
call_t: null | {
id: number;
call: FlowType;
};
}
// DefT types
export type DefT =
| NumGeneralType
| StrGeneralType
| BoolGeneralType
| BigIntGeneralType
| EmptyType
| MixedType
| NullType
| VoidType
| SymbolType
| FunType
| ObjType
| ArrType
| ClassType
| InstanceType
| SingletonStrType
| NumericStrKeyType
| SingletonNumType
| SingletonBoolType
| SingletonBigIntType
| TypeType
| PolyType
| ReactAbstractComponentType
| RendersType
| EnumValueType
| EnumObjectType;
export interface NumGeneralType extends BaseFlowType {
kind: 'NumGeneral';
}
export interface StrGeneralType extends BaseFlowType {
kind: 'StrGeneral';
}
export interface BoolGeneralType extends BaseFlowType {
kind: 'BoolGeneral';
}
export interface BigIntGeneralType extends BaseFlowType {
kind: 'BigIntGeneral';
}
export interface EmptyType extends BaseFlowType {
kind: 'Empty';
}
export interface MixedType extends BaseFlowType {
kind: 'Mixed';
}
export interface NullType extends BaseFlowType {
kind: 'Null';
}
export interface VoidType extends BaseFlowType {
kind: 'Void';
}
export interface SymbolType extends BaseFlowType {
kind: 'Symbol';
}
export interface FunType extends BaseFlowType {
kind: 'Fun';
static: FlowType;
funtype: FunTypeObj;
}
export interface ObjType extends BaseFlowType {
kind: 'Obj';
objtype: ObjTypeObj;
}
export interface ArrType extends BaseFlowType {
kind: 'Arr';
arrtype: ArrTypeObj;
}
export interface ClassType extends BaseFlowType {
kind: 'Class';
type: FlowType;
}
export interface InstanceType extends BaseFlowType {
kind: 'Instance';
instance: InstanceT;
}
export interface SingletonStrType extends BaseFlowType {
kind: 'SingletonStr';
from_annot: boolean;
value: string;
}
export interface NumericStrKeyType extends BaseFlowType {
kind: 'NumericStrKey';
number: string;
string: string;
}
export interface SingletonNumType extends BaseFlowType {
kind: 'SingletonNum';
from_annot: boolean;
number: string;
string: string;
}
export interface SingletonBoolType extends BaseFlowType {
kind: 'SingletonBool';
from_annot: boolean;
value: boolean;
}
export interface SingletonBigIntType extends BaseFlowType {
kind: 'SingletonBigInt';
from_annot: boolean;
value: string;
}
export interface TypeType extends BaseFlowType {
kind: 'Type';
type_kind: TypeTKind;
type: FlowType;
}
export type TypeTKind =
| 'TypeAliasKind'
| 'TypeParamKind'
| 'OpaqueKind'
| 'ImportTypeofKind'
| 'ImportClassKind'
| 'ImportEnumKind'
| 'InstanceKind'
| 'RenderTypeKind';
export interface PolyType extends BaseFlowType {
kind: 'Poly';
tparams: Array<TypeParam>;
t_out: FlowType;
id: string;
}
export interface ReactAbstractComponentType extends BaseFlowType {
kind: 'ReactAbstractComponent';
config: FlowType;
renders: FlowType;
instance: ComponentInstance;
component_kind: ComponentKind;
}
export type ComponentInstance =
| {kind: 'RefSetterProp'; type: FlowType}
| {kind: 'Omitted'};
export type ComponentKind =
| {kind: 'Structural'}
| {kind: 'Nominal'; id: string; name: string; types: Array<FlowType> | null};
export interface RendersType extends BaseFlowType {
kind: 'Renders';
form: CanonicalRendersForm;
}
export interface EnumValueType extends BaseFlowType {
kind: 'EnumValue';
enum_info: EnumInfo;
}
export interface EnumObjectType extends BaseFlowType {
kind: 'EnumObject';
enum_value_t: FlowType;
enum_info: EnumInfo;
}
// ObjKind types
export type ObjKind =
| {kind: 'Exact'}
| {kind: 'Inexact'}
| {kind: 'Indexed'; dicttype: DictType};
// DictType definition
export interface DictType {
dict_name: string | null;
key: FlowType;
value: FlowType;
dict_polarity: Polarity;
}
// ArrType types
export type ArrTypeObj = ArrayAT | TupleAT | ROArrayAT;
export interface ArrayAT {
kind: 'ArrayAT';
elem_t: FlowType;
}
export interface TupleAT {
kind: 'TupleAT';
elem_t: FlowType;
elements: Array<TupleElement>;
min_arity: number;
max_arity: number;
inexact: boolean;
}
export interface ROArrayAT {
kind: 'ROArrayAT';
elem_t: FlowType;
}
// TupleElement definition
export interface TupleElement {
name: string | null;
t: FlowType;
polarity: Polarity;
optional: boolean;
}
// Flags definition
export interface Flags {
obj_kind: ObjKind;
}
// Property types
export type Property =
| FieldProperty
| GetProperty
| SetProperty
| GetSetProperty
| MethodProperty;
export interface FieldProperty {
kind: 'Field';
type: FlowType;
polarity: Polarity;
}
export interface GetProperty {
kind: 'Get';
type: FlowType;
}
export interface SetProperty {
kind: 'Set';
type: FlowType;
}
export interface GetSetProperty {
kind: 'GetSet';
get_type: FlowType;
set_type: FlowType;
}
export interface MethodProperty {
kind: 'Method';
type: FlowType;
}
// PropertyMap definition
export interface PropertyMap {
[key: string]: Property; // For other properties in the map
}
// ObjType definition
export interface ObjTypeObj {
flags: Flags;
props: PropertyMap;
proto_t: FlowType;
call_t: number | null;
}
// FunType definition
export interface FunTypeObj {
this_t: {
type: FlowType;
status: ThisStatus;
};
params: Array<{
name: string | null;
type: FlowType;
}>;
rest_param: null | {
name: string | null;
type: FlowType;
};
return_t: FlowType;
type_guard: null | {
inferred: boolean;
param_name: string;
type_guard: FlowType;
one_sided: boolean;
};
effect: Effect;
}
// ThisStatus types
export type ThisStatus =
| {kind: 'This_Method'; unbound: boolean}
| {kind: 'This_Function'};
// Effect types
export type Effect =
| {kind: 'HookDecl'; id: string}
| {kind: 'HookAnnot'}
| {kind: 'ArbitraryEffect'}
| {kind: 'AnyEffect'};
// Destructor types
export type Destructor =
| NonMaybeTypeDestructor
| PropertyTypeDestructor
| ElementTypeDestructor
| OptionalIndexedAccessNonMaybeTypeDestructor
| OptionalIndexedAccessResultTypeDestructor
| ExactTypeDestructor
| ReadOnlyTypeDestructor
| PartialTypeDestructor
| RequiredTypeDestructor
| SpreadTypeDestructor
| SpreadTupleTypeDestructor
| RestTypeDestructor
| ValuesTypeDestructor
| ConditionalTypeDestructor
| TypeMapDestructor
| ReactElementPropsTypeDestructor
| ReactElementConfigTypeDestructor
| ReactCheckComponentConfigDestructor
| ReactDRODestructor
| MakeHooklikeDestructor
| MappedTypeDestructor
| EnumTypeDestructor;
export interface NonMaybeTypeDestructor {
kind: 'NonMaybeType';
}
export interface PropertyTypeDestructor {
kind: 'PropertyType';
name: string;
}
export interface ElementTypeDestructor {
kind: 'ElementType';
index_type: FlowType;
}
export interface OptionalIndexedAccessNonMaybeTypeDestructor {
kind: 'OptionalIndexedAccessNonMaybeType';
index: OptionalIndexedAccessIndex;
}
export type OptionalIndexedAccessIndex =
| {kind: 'StrLitIndex'; name: string}
| {kind: 'TypeIndex'; type: FlowType};
export interface OptionalIndexedAccessResultTypeDestructor {
kind: 'OptionalIndexedAccessResultType';
}
export interface ExactTypeDestructor {
kind: 'ExactType';
}
export interface ReadOnlyTypeDestructor {
kind: 'ReadOnlyType';
}
export interface PartialTypeDestructor {
kind: 'PartialType';
}
export interface RequiredTypeDestructor {
kind: 'RequiredType';
}
export interface SpreadTypeDestructor {
kind: 'SpreadType';
target: SpreadTarget;
operands: Array<SpreadOperand>;
operand_slice: Slice | null;
}
export type SpreadTarget =
| {kind: 'Value'; make_seal: 'Sealed' | 'Frozen' | 'As_Const'}
| {kind: 'Annot'; make_exact: boolean};
export type SpreadOperand = {kind: 'Type'; type: FlowType} | Slice;
export interface Slice {
kind: 'Slice';
prop_map: PropertyMap;
generics: Array<string>;
dict: DictType | null;
reachable_targs: Array<{
type: FlowType;
polarity: Polarity;
}>;
}
export interface SpreadTupleTypeDestructor {
kind: 'SpreadTupleType';
inexact: boolean;
resolved_rev: string;
unresolved: string;
}
export interface RestTypeDestructor {
kind: 'RestType';
merge_mode: RestMergeMode;
type: FlowType;
}
export type RestMergeMode =
| {kind: 'SpreadReversal'}
| {kind: 'ReactConfigMerge'; polarity: Polarity}
| {kind: 'Omit'};
export interface ValuesTypeDestructor {
kind: 'ValuesType';
}
export interface ConditionalTypeDestructor {
kind: 'ConditionalType';
distributive_tparam_name: string | null;
infer_tparams: string;
extends_t: FlowType;
true_t: FlowType;
false_t: FlowType;
}
export interface TypeMapDestructor {
kind: 'ObjectKeyMirror';
}
export interface ReactElementPropsTypeDestructor {
kind: 'ReactElementPropsType';
}
export interface ReactElementConfigTypeDestructor {
kind: 'ReactElementConfigType';
}
export interface ReactCheckComponentConfigDestructor {
kind: 'ReactCheckComponentConfig';
props: {
[key: string]: Property;
};
}
export interface ReactDRODestructor {
kind: 'ReactDRO';
dro_type:
| 'HookReturn'
| 'HookArg'
| 'Props'
| 'ImmutableAnnot'
| 'DebugAnnot';
}
export interface MakeHooklikeDestructor {
kind: 'MakeHooklike';
}
export interface MappedTypeDestructor {
kind: 'MappedType';
homomorphic: Homomorphic;
distributive_tparam_name: string | null;
property_type: FlowType;
mapped_type_flags: {
variance: Polarity;
optional: 'MakeOptional' | 'RemoveOptional' | 'KeepOptionality';
};
}
export type Homomorphic =
| {kind: 'Homomorphic'}
| {kind: 'Unspecialized'}
| {kind: 'SemiHomomorphic'; type: FlowType};
export interface EnumTypeDestructor {
kind: 'EnumType';
}
// Union of all possible Flow types
export type FlowType =
| OpenType
| DefType
| EvalType
| GenericType
| ThisInstanceType
| ThisTypeAppType
| TypeAppType
| FunProtoType
| ObjProtoType
| NullProtoType
| FunProtoBindType
| IntersectionType
| UnionType
| MaybeType
| OptionalType
| KeysType
| AnnotType
| OpaqueType
| NamespaceType
| AnyType
| StrUtilType;

View File

@@ -0,0 +1,138 @@
import {CompilerError, SourceLocation} from '..';
import {
ConcreteType,
printConcrete,
printType,
StructuralValue,
Type,
VariableId,
} from './Types';
export function unsupportedLanguageFeature(
desc: string,
loc: SourceLocation,
): never {
CompilerError.throwInvalidJS({
reason: `Typedchecker does not currently support language feature: ${desc}`,
loc,
});
}
export type UnificationError =
| {
kind: 'TypeUnification';
left: ConcreteType<Type>;
right: ConcreteType<Type>;
}
| {
kind: 'StructuralUnification';
left: StructuralValue;
right: ConcreteType<Type>;
};
function printUnificationError(err: UnificationError): string {
if (err.kind === 'TypeUnification') {
return `${printConcrete(err.left, printType)} is incompatible with ${printConcrete(err.right, printType)}`;
} else {
return `structural ${err.left.kind} is incompatible with ${printConcrete(err.right, printType)}`;
}
}
export function raiseUnificationErrors(
errs: null | Array<UnificationError>,
loc: SourceLocation,
): void {
if (errs != null) {
if (errs.length === 0) {
CompilerError.invariant(false, {
reason: 'Should not have array of zero errors',
description: null,
details: [
{
kind: 'error',
loc,
message: null,
},
],
});
} else if (errs.length === 1) {
CompilerError.throwInvalidJS({
reason: `Unable to unify types because ${printUnificationError(errs[0])}`,
loc,
});
} else {
const messages = errs
.map(err => `\t* ${printUnificationError(err)}`)
.join('\n');
CompilerError.throwInvalidJS({
reason: `Unable to unify types because:\n${messages}`,
loc,
});
}
}
}
export function unresolvableTypeVariable(
id: VariableId,
loc: SourceLocation,
): never {
CompilerError.throwInvalidJS({
reason: `Unable to resolve free variable ${id} to a concrete type`,
loc,
});
}
export function cannotAddVoid(explicit: boolean, loc: SourceLocation): never {
if (explicit) {
CompilerError.throwInvalidJS({
reason: `Undefined is not a valid operand of \`+\``,
loc,
});
} else {
CompilerError.throwInvalidJS({
reason: `Value may be undefined, which is not a valid operand of \`+\``,
loc,
});
}
}
export function unsupportedTypeAnnotation(
desc: string,
loc: SourceLocation,
): never {
CompilerError.throwInvalidJS({
reason: `Typedchecker does not currently support type annotation: ${desc}`,
loc,
});
}
export function checkTypeArgumentArity(
desc: string,
expected: number,
actual: number,
loc: SourceLocation,
): void {
if (expected !== actual) {
CompilerError.throwInvalidJS({
reason: `Expected ${desc} to have ${expected} type parameters, got ${actual}`,
loc,
});
}
}
export function notAFunction(desc: string, loc: SourceLocation): void {
CompilerError.throwInvalidJS({
reason: `Cannot call ${desc} because it is not a function`,
loc,
});
}
export function notAPolymorphicFunction(
desc: string,
loc: SourceLocation,
): void {
CompilerError.throwInvalidJS({
reason: `Cannot call ${desc} with type arguments because it is not a polymorphic function`,
loc,
});
}

View File

@@ -0,0 +1,312 @@
import {GeneratedSource} from '../HIR';
import {assertExhaustive} from '../Utils/utils';
import {unsupportedLanguageFeature} from './TypeErrors';
import {
ConcreteType,
ResolvedType,
TypeParameter,
TypeParameterId,
DEBUG,
printConcrete,
printType,
} from './Types';
export function substitute(
type: ConcreteType<ResolvedType>,
typeParameters: Array<TypeParameter<ResolvedType>>,
typeArguments: Array<ResolvedType>,
): ResolvedType {
const substMap = new Map<TypeParameterId, ResolvedType>();
for (let i = 0; i < typeParameters.length; i++) {
// TODO: Length checks to make sure type params match up with args
const typeParameter = typeParameters[i];
const typeArgument = typeArguments[i];
substMap.set(typeParameter.id, typeArgument);
}
const substitutionFunction = (t: ResolvedType): ResolvedType => {
// TODO: We really want a stateful mapper or visitor here so that we can model nested polymorphic types
if (t.type.kind === 'Generic' && substMap.has(t.type.id)) {
const substitutedType = substMap.get(t.type.id)!;
return substitutedType;
}
return {
kind: 'Concrete',
type: mapType(substitutionFunction, t.type),
platform: t.platform,
};
};
const substituted = mapType(substitutionFunction, type);
if (DEBUG) {
let substs = '';
for (let i = 0; i < typeParameters.length; i++) {
const typeParameter = typeParameters[i];
const typeArgument = typeArguments[i];
substs += `[${typeParameter.name}${typeParameter.id} := ${printType(typeArgument)}]`;
}
console.log(
`${printConcrete(type, printType)}${substs} = ${printConcrete(substituted, printType)}`,
);
}
return {kind: 'Concrete', type: substituted, platform: /* TODO */ 'shared'};
}
export function mapType<T, U>(
f: (t: T) => U,
type: ConcreteType<T>,
): ConcreteType<U> {
switch (type.kind) {
case 'Mixed':
case 'Number':
case 'String':
case 'Boolean':
case 'Void':
return type;
case 'Nullable':
return {
kind: 'Nullable',
type: f(type.type),
};
case 'Array':
return {
kind: 'Array',
element: f(type.element),
};
case 'Set':
return {
kind: 'Set',
element: f(type.element),
};
case 'Map':
return {
kind: 'Map',
key: f(type.key),
value: f(type.value),
};
case 'Function':
return {
kind: 'Function',
typeParameters:
type.typeParameters?.map(param => ({
id: param.id,
name: param.name,
bound: f(param.bound),
})) ?? null,
params: type.params.map(f),
returnType: f(type.returnType),
};
case 'Component': {
return {
kind: 'Component',
children: type.children != null ? f(type.children) : null,
props: new Map([...type.props.entries()].map(([k, v]) => [k, f(v)])),
};
}
case 'Generic':
return {
kind: 'Generic',
id: type.id,
bound: f(type.bound),
};
case 'Object':
return type;
case 'Tuple':
return {
kind: 'Tuple',
id: type.id,
members: type.members.map(f),
};
case 'Structural':
return type;
case 'Enum':
case 'Union':
case 'Instance':
unsupportedLanguageFeature(type.kind, GeneratedSource);
default:
assertExhaustive(type, 'Unknown type kind');
}
}
export function diff<R, T>(
a: ConcreteType<T>,
b: ConcreteType<T>,
onChild: (a: T, b: T) => R,
onChildMismatch: (child: R, cur: R) => R,
onMismatch: (a: ConcreteType<T>, b: ConcreteType<T>, cur: R) => R,
init: R,
): R {
let errors = init;
// Check if kinds match
if (a.kind !== b.kind) {
errors = onMismatch(a, b, errors);
return errors;
}
// Based on kind, check other properties
switch (a.kind) {
case 'Mixed':
case 'Number':
case 'String':
case 'Boolean':
case 'Void':
// Simple types, no further checks needed
break;
case 'Nullable':
// Check the nested type
errors = onChildMismatch(onChild(a.type, (b as typeof a).type), errors);
break;
case 'Array':
case 'Set':
// Check the element type
errors = onChildMismatch(
onChild(a.element, (b as typeof a).element),
errors,
);
break;
case 'Map':
// Check both key and value types
errors = onChildMismatch(onChild(a.key, (b as typeof a).key), errors);
errors = onChildMismatch(onChild(a.value, (b as typeof a).value), errors);
break;
case 'Function': {
const bFunc = b as typeof a;
// Check type parameters
if ((a.typeParameters == null) !== (bFunc.typeParameters == null)) {
errors = onMismatch(a, b, errors);
}
if (a.typeParameters != null && bFunc.typeParameters != null) {
if (a.typeParameters.length !== bFunc.typeParameters.length) {
errors = onMismatch(a, b, errors);
}
// Type parameters are just numbers, so we can compare them directly
for (let i = 0; i < a.typeParameters.length; i++) {
if (a.typeParameters[i] !== bFunc.typeParameters[i]) {
errors = onMismatch(a, b, errors);
}
}
}
// Check parameters
if (a.params.length !== bFunc.params.length) {
errors = onMismatch(a, b, errors);
}
for (let i = 0; i < a.params.length; i++) {
errors = onChildMismatch(onChild(a.params[i], bFunc.params[i]), errors);
}
// Check return type
errors = onChildMismatch(onChild(a.returnType, bFunc.returnType), errors);
break;
}
case 'Component': {
const bComp = b as typeof a;
// Check children
if (a.children !== bComp.children) {
errors = onMismatch(a, b, errors);
}
// Check props
if (a.props.size !== bComp.props.size) {
errors = onMismatch(a, b, errors);
}
for (const [k, v] of a.props) {
const bProp = bComp.props.get(k);
if (bProp == null) {
errors = onMismatch(a, b, errors);
} else {
errors = onChildMismatch(onChild(v, bProp), errors);
}
}
break;
}
case 'Generic': {
// Check that the type parameter IDs match
if (a.id !== (b as typeof a).id) {
errors = onMismatch(a, b, errors);
}
break;
}
case 'Structural': {
const bStruct = b as typeof a;
// Check that the structural IDs match
if (a.id !== bStruct.id) {
errors = onMismatch(a, b, errors);
}
break;
}
case 'Object': {
const bNom = b as typeof a;
// Check that the nominal IDs match
if (a.id !== bNom.id) {
errors = onMismatch(a, b, errors);
}
break;
}
case 'Tuple': {
const bTuple = b as typeof a;
// Check that the tuple IDs match
if (a.id !== bTuple.id) {
errors = onMismatch(a, b, errors);
}
for (let i = 0; i < a.members.length; i++) {
errors = onChildMismatch(
onChild(a.members[i], bTuple.members[i]),
errors,
);
}
break;
}
case 'Enum':
case 'Instance':
case 'Union': {
unsupportedLanguageFeature(a.kind, GeneratedSource);
}
default:
assertExhaustive(a, 'Unknown type kind');
}
return errors;
}
export function filterOptional(t: ResolvedType): ResolvedType {
if (t.kind === 'Concrete' && t.type.kind === 'Nullable') {
return t.type.type;
}
return t;
}

File diff suppressed because it is too large Load Diff

View File

@@ -38,7 +38,13 @@ export function assertConsistentIdentifiers(fn: HIRFunction): void {
CompilerError.invariant(instr.lvalue.identifier.name === null, {
reason: `Expected all lvalues to be temporaries`,
description: `Found named lvalue \`${instr.lvalue.identifier.name}\``,
loc: instr.lvalue.loc,
details: [
{
kind: 'error',
loc: instr.lvalue.loc,
message: null,
},
],
suggestions: null,
});
CompilerError.invariant(!assignments.has(instr.lvalue.identifier.id), {
@@ -46,7 +52,13 @@ export function assertConsistentIdentifiers(fn: HIRFunction): void {
description: `Found duplicate assignment of '${printPlace(
instr.lvalue,
)}'`,
loc: instr.lvalue.loc,
details: [
{
kind: 'error',
loc: instr.lvalue.loc,
message: null,
},
],
suggestions: null,
});
assignments.add(instr.lvalue.identifier.id);
@@ -77,7 +89,13 @@ function validate(
CompilerError.invariant(identifier === previous, {
reason: `Duplicate identifier object`,
description: `Found duplicate identifier object for id ${identifier.id}`,
loc: loc ?? GeneratedSource,
details: [
{
kind: 'error',
loc: loc ?? GeneratedSource,
message: null,
},
],
suggestions: null,
});
}

View File

@@ -18,7 +18,13 @@ export function assertTerminalSuccessorsExist(fn: HIRFunction): void {
description: `Block bb${successor} does not exist for terminal '${printTerminal(
block.terminal,
)}'`,
loc: (block.terminal as any).loc ?? GeneratedSource,
details: [
{
kind: 'error',
loc: (block.terminal as any).loc ?? GeneratedSource,
message: null,
},
],
suggestions: null,
});
return successor;
@@ -33,14 +39,26 @@ export function assertTerminalPredsExist(fn: HIRFunction): void {
CompilerError.invariant(predBlock != null, {
reason: 'Expected predecessor block to exist',
description: `Block ${block.id} references non-existent ${pred}`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
CompilerError.invariant(
[...eachTerminalSuccessor(predBlock.terminal)].includes(block.id),
{
reason: 'Terminal successor does not reference correct predecessor',
description: `Block bb${block.id} has bb${predBlock.id} as a predecessor, but bb${predBlock.id}'s successors do not include bb${block.id}`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
},
);
}

View File

@@ -1,3 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError} from '..';
import {
BlockId,
@@ -124,7 +131,13 @@ export function recursivelyTraverseItems<T, TContext>(
CompilerError.invariant(disjoint || nested, {
reason: 'Invalid nesting in program blocks or scopes',
description: `Items overlap but are not nested: ${maybeParentRange.start}:${maybeParentRange.end}(${currRange.start}:${currRange.end})`,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
if (disjoint) {
exit(maybeParent, context);

View File

@@ -5,13 +5,14 @@
* LICENSE file in the root directory of this source tree.
*/
import invariant from 'invariant';
import {HIRFunction, Identifier, MutableRange} from './HIR';
import {HIRFunction, MutableRange, Place} from './HIR';
import {
eachInstructionLValue,
eachInstructionOperand,
eachTerminalOperand,
} from './visitors';
import {CompilerError} from '..';
import {printPlace} from './PrintHIR';
/*
* Checks that all mutable ranges in the function are well-formed, with
@@ -20,38 +21,49 @@ import {
export function assertValidMutableRanges(fn: HIRFunction): void {
for (const [, block] of fn.body.blocks) {
for (const phi of block.phis) {
visitIdentifier(phi.place.identifier);
for (const [, operand] of phi.operands) {
visitIdentifier(operand.identifier);
visit(phi.place, `phi for block bb${block.id}`);
for (const [pred, operand] of phi.operands) {
visit(operand, `phi predecessor bb${pred} for block bb${block.id}`);
}
}
for (const instr of block.instructions) {
for (const operand of eachInstructionLValue(instr)) {
visitIdentifier(operand.identifier);
visit(operand, `instruction [${instr.id}]`);
}
for (const operand of eachInstructionOperand(instr)) {
visitIdentifier(operand.identifier);
visit(operand, `instruction [${instr.id}]`);
}
}
for (const operand of eachTerminalOperand(block.terminal)) {
visitIdentifier(operand.identifier);
visit(operand, `terminal [${block.terminal.id}]`);
}
}
}
function visitIdentifier(identifier: Identifier): void {
validateMutableRange(identifier.mutableRange);
if (identifier.scope !== null) {
validateMutableRange(identifier.scope.range);
function visit(place: Place, description: string): void {
validateMutableRange(place, place.identifier.mutableRange, description);
if (place.identifier.scope !== null) {
validateMutableRange(place, place.identifier.scope.range, description);
}
}
function validateMutableRange(mutableRange: MutableRange): void {
invariant(
(mutableRange.start === 0 && mutableRange.end === 0) ||
mutableRange.end > mutableRange.start,
'Identifier scope mutableRange was invalid: [%s:%s]',
mutableRange.start,
mutableRange.end,
function validateMutableRange(
place: Place,
range: MutableRange,
description: string,
): void {
CompilerError.invariant(
(range.start === 0 && range.end === 0) || range.end > range.start,
{
reason: `Invalid mutable range: [${range.start}:${range.end}]`,
description: `${printPlace(place)} in ${description}`,
details: [
{
kind: 'error',
loc: place.loc,
message: null,
},
],
},
);
}

View File

@@ -1,3 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError} from '../CompilerError';
import {getScopes, recursivelyTraverseItems} from './AssertValidBlockNesting';
import {Environment} from './Environment';
@@ -227,7 +234,14 @@ function pushEndScopeTerminal(
const fallthroughId = context.fallthroughs.get(scope.id);
CompilerError.invariant(fallthroughId != null, {
reason: 'Expected scope to exist',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
context.rewrites.push({
kind: 'EndScope',

View File

@@ -1,3 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError} from '../CompilerError';
import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables';
import {printDependency} from '../ReactiveScopes/PrintReactiveFunction';
@@ -12,15 +19,19 @@ import {
BasicBlock,
BlockId,
DependencyPathEntry,
FunctionExpression,
GeneratedSource,
getHookKind,
HIRFunction,
Identifier,
IdentifierId,
InstructionId,
InstructionValue,
LoweredFunction,
PropertyLiteral,
ReactiveScopeDependency,
ScopeId,
TInstruction,
} from './HIR';
const DEBUG_PRINT = false;
@@ -112,9 +123,39 @@ export function collectHoistablePropertyLoads(
hoistableFromOptionals,
registry,
nestedFnImmutableContext: null,
assumedInvokedFns: fn.env.config.enableTreatFunctionDepsAsConditional
? new Set()
: getAssumedInvokedFunctions(fn),
});
}
export function collectHoistablePropertyLoadsInInnerFn(
fnInstr: TInstruction<FunctionExpression>,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
hoistableFromOptionals: ReadonlyMap<BlockId, ReactiveScopeDependency>,
): ReadonlyMap<BlockId, BlockInfo> {
const fn = fnInstr.value.loweredFunc.func;
const initialContext: CollectHoistablePropertyLoadsContext = {
temporaries,
knownImmutableIdentifiers: new Set(),
hoistableFromOptionals,
registry: new PropertyPathRegistry(),
nestedFnImmutableContext: null,
assumedInvokedFns: fn.env.config.enableTreatFunctionDepsAsConditional
? new Set()
: getAssumedInvokedFunctions(fn),
};
const nestedFnImmutableContext = new Set(
fn.context
.filter(place =>
isImmutableAtInstr(place.identifier, fnInstr.id, initialContext),
)
.map(place => place.identifier.id),
);
initialContext.nestedFnImmutableContext = nestedFnImmutableContext;
return collectHoistablePropertyLoadsImpl(fn, initialContext);
}
type CollectHoistablePropertyLoadsContext = {
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>;
knownImmutableIdentifiers: ReadonlySet<IdentifierId>;
@@ -127,6 +168,11 @@ type CollectHoistablePropertyLoadsContext = {
* but are currently kept separate for readability.
*/
nestedFnImmutableContext: ReadonlySet<IdentifierId> | null;
/**
* Functions which are assumed to be eventually called (as opposed to ones which might
* not be called, e.g. the 0th argument of Array.map)
*/
assumedInvokedFns: ReadonlySet<LoweredFunction>;
};
function collectHoistablePropertyLoadsImpl(
fn: HIRFunction,
@@ -195,7 +241,10 @@ type PropertyPathNode =
class PropertyPathRegistry {
roots: Map<IdentifierId, RootNode> = new Map();
getOrCreateIdentifier(identifier: Identifier): PropertyPathNode {
getOrCreateIdentifier(
identifier: Identifier,
reactive: boolean,
): PropertyPathNode {
/**
* Reads from a statically scoped variable are always safe in JS,
* with the exception of TDZ (not addressed by this pass).
@@ -209,12 +258,26 @@ class PropertyPathRegistry {
optionalProperties: new Map(),
fullPath: {
identifier,
reactive,
path: [],
},
hasOptional: false,
parent: null,
};
this.roots.set(identifier.id, rootNode);
} else {
CompilerError.invariant(reactive === rootNode.fullPath.reactive, {
reason:
'[HoistablePropertyLoads] Found inconsistencies in `reactive` flag when deduping identifier reads within the same scope',
description: null,
details: [
{
kind: 'error',
loc: identifier.loc,
message: null,
},
],
});
}
return rootNode;
}
@@ -232,6 +295,7 @@ class PropertyPathRegistry {
parent: parent,
fullPath: {
identifier: parent.fullPath.identifier,
reactive: parent.fullPath.reactive,
path: parent.fullPath.path.concat(entry),
},
hasOptional: parent.hasOptional || entry.optional,
@@ -247,7 +311,7 @@ class PropertyPathRegistry {
* so all subpaths of a PropertyLoad should already exist
* (e.g. a.b is added before a.b.c),
*/
let currNode = this.getOrCreateIdentifier(n.identifier);
let currNode = this.getOrCreateIdentifier(n.identifier, n.reactive);
if (n.path.length === 0) {
return currNode;
}
@@ -269,10 +333,11 @@ function getMaybeNonNullInInstruction(
instr: InstructionValue,
context: CollectHoistablePropertyLoadsContext,
): PropertyPathNode | null {
let path = null;
let path: ReactiveScopeDependency | null = null;
if (instr.kind === 'PropertyLoad') {
path = context.temporaries.get(instr.object.identifier.id) ?? {
identifier: instr.object.identifier,
reactive: instr.object.reactive,
path: [],
};
} else if (instr.kind === 'Destructure') {
@@ -335,10 +400,16 @@ function collectNonNullsInBlocks(
) {
const identifier = fn.params[0].identifier;
knownNonNullIdentifiers.add(
context.registry.getOrCreateIdentifier(identifier),
context.registry.getOrCreateIdentifier(identifier, true),
);
}
const nodes = new Map<BlockId, BlockInfo>();
const nodes = new Map<
BlockId,
{
block: BasicBlock;
assumedNonNullObjects: Set<PropertyPathNode>;
}
>();
for (const [_, block] of fn.body.blocks) {
const assumedNonNullObjects = new Set<PropertyPathNode>(
knownNonNullIdentifiers,
@@ -358,32 +429,56 @@ function collectNonNullsInBlocks(
) {
assumedNonNullObjects.add(maybeNonNull);
}
if (
(instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod') &&
!fn.env.config.enableTreatFunctionDepsAsConditional
) {
if (instr.value.kind === 'FunctionExpression') {
const innerFn = instr.value.loweredFunc;
const innerHoistableMap = collectHoistablePropertyLoadsImpl(
innerFn.func,
{
...context,
nestedFnImmutableContext:
context.nestedFnImmutableContext ??
new Set(
innerFn.func.context
.filter(place =>
isImmutableAtInstr(place.identifier, instr.id, context),
)
.map(place => place.identifier.id),
),
},
);
const innerHoistables = assertNonNull(
innerHoistableMap.get(innerFn.func.body.entry),
);
for (const entry of innerHoistables.assumedNonNullObjects) {
assumedNonNullObjects.add(entry);
if (context.assumedInvokedFns.has(innerFn)) {
const innerHoistableMap = collectHoistablePropertyLoadsImpl(
innerFn.func,
{
...context,
nestedFnImmutableContext:
context.nestedFnImmutableContext ??
new Set(
innerFn.func.context
.filter(place =>
isImmutableAtInstr(place.identifier, instr.id, context),
)
.map(place => place.identifier.id),
),
},
);
const innerHoistables = assertNonNull(
innerHoistableMap.get(innerFn.func.body.entry),
);
for (const entry of innerHoistables.assumedNonNullObjects) {
assumedNonNullObjects.add(entry);
}
}
} else if (
fn.env.config.enablePreserveExistingMemoizationGuarantees &&
instr.value.kind === 'StartMemoize' &&
instr.value.deps != null
) {
for (const dep of instr.value.deps) {
if (dep.root.kind === 'NamedLocal') {
if (
!isImmutableAtInstr(dep.root.value.identifier, instr.id, context)
) {
continue;
}
for (let i = 0; i < dep.path.length; i++) {
const pathEntry = dep.path[i]!;
if (pathEntry.optional) {
break;
}
const depNode = context.registry.getOrCreateProperty({
identifier: dep.root.value.identifier,
path: dep.path.slice(0, i),
reactive: dep.root.value.reactive,
});
assumedNonNullObjects.add(depNode);
}
}
}
}
}
@@ -436,7 +531,14 @@ function propagateNonNull(
if (node == null) {
CompilerError.invariant(false, {
reason: `Bad node ${nodeId}, kind: ${direction}`,
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
}
const neighbors = Array.from(
@@ -508,7 +610,14 @@ function propagateNonNull(
CompilerError.invariant(i++ < 100, {
reason:
'[CollectHoistablePropertyLoads] fixed point iteration did not terminate after 100 loops',
loc: GeneratedSource,
description: null,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
changed = false;
@@ -540,7 +649,13 @@ export function assertNonNull<T extends NonNullable<U>, U>(
CompilerError.invariant(value != null, {
reason: 'Unexpected null',
description: source != null ? `(from ${source})` : null,
loc: GeneratedSource,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
});
return value;
}
@@ -566,9 +681,11 @@ function reduceMaybeOptionalChains(
changed = false;
for (const original of optionalChainNodes) {
let {identifier, path: origPath} = original.fullPath;
let currNode: PropertyPathNode =
registry.getOrCreateIdentifier(identifier);
let {identifier, path: origPath, reactive} = original.fullPath;
let currNode: PropertyPathNode = registry.getOrCreateIdentifier(
identifier,
reactive,
);
for (let i = 0; i < origPath.length; i++) {
const entry = origPath[i];
// If the base is known to be non-null, replace with a non-optional load
@@ -591,3 +708,130 @@ function reduceMaybeOptionalChains(
}
} while (changed);
}
function getAssumedInvokedFunctions(
fn: HIRFunction,
temporaries: Map<
IdentifierId,
{fn: LoweredFunction; mayInvoke: Set<LoweredFunction>}
> = new Map(),
): ReadonlySet<LoweredFunction> {
const hoistableFunctions = new Set<LoweredFunction>();
/**
* Step 1: Conservatively collect identifier to function expression mappings
*/
for (const block of fn.body.blocks.values()) {
for (const {lvalue, value} of block.instructions) {
/**
* Conservatively only match function expressions which can have guaranteed ssa.
* ObjectMethods and ObjectProperties do not.
*/
if (value.kind === 'FunctionExpression') {
temporaries.set(lvalue.identifier.id, {
fn: value.loweredFunc,
mayInvoke: new Set(),
});
} else if (value.kind === 'StoreLocal') {
const lvalue = value.lvalue.place.identifier;
const maybeLoweredFunc = temporaries.get(value.value.identifier.id);
if (maybeLoweredFunc != null) {
temporaries.set(lvalue.id, maybeLoweredFunc);
}
} else if (value.kind === 'LoadLocal') {
const maybeLoweredFunc = temporaries.get(value.place.identifier.id);
if (maybeLoweredFunc != null) {
temporaries.set(lvalue.identifier.id, maybeLoweredFunc);
}
}
}
}
/**
* Step 2: Forward pass to do analysis of assumed function calls. Note that
* this is conservative and does not count indirect references through
* containers (e.g. `return {cb: () => {...}})`).
*/
for (const block of fn.body.blocks.values()) {
for (const {lvalue, value} of block.instructions) {
if (value.kind === 'CallExpression') {
const callee = value.callee;
const maybeHook = getHookKind(fn.env, callee.identifier);
const maybeLoweredFunc = temporaries.get(callee.identifier.id);
if (maybeLoweredFunc != null) {
// Direct calls
hoistableFunctions.add(maybeLoweredFunc.fn);
} else if (maybeHook != null) {
/**
* Assume arguments to all hooks are safe to invoke
*/
for (const arg of value.args) {
if (arg.kind === 'Identifier') {
const maybeLoweredFunc = temporaries.get(arg.identifier.id);
if (maybeLoweredFunc != null) {
hoistableFunctions.add(maybeLoweredFunc.fn);
}
}
}
}
} else if (value.kind === 'JsxExpression') {
/**
* Assume JSX attributes and children are safe to invoke
*/
for (const attr of value.props) {
if (attr.kind === 'JsxSpreadAttribute') {
continue;
}
const maybeLoweredFunc = temporaries.get(attr.place.identifier.id);
if (maybeLoweredFunc != null) {
hoistableFunctions.add(maybeLoweredFunc.fn);
}
}
for (const child of value.children ?? []) {
const maybeLoweredFunc = temporaries.get(child.identifier.id);
if (maybeLoweredFunc != null) {
hoistableFunctions.add(maybeLoweredFunc.fn);
}
}
} else if (value.kind === 'FunctionExpression') {
/**
* Recursively traverse into other function expressions which may invoke
* or pass already declared functions to react (e.g. as JSXAttributes).
*
* If lambda A calls lambda B, we assume lambda B is safe to invoke if
* lambda A is -- even if lambda B is conditionally called. (see
* `conditional-call-chain` fixture for example).
*/
const loweredFunc = value.loweredFunc.func;
const lambdasCalled = getAssumedInvokedFunctions(
loweredFunc,
temporaries,
);
const maybeLoweredFunc = temporaries.get(lvalue.identifier.id);
if (maybeLoweredFunc != null) {
for (const called of lambdasCalled) {
maybeLoweredFunc.mayInvoke.add(called);
}
}
}
}
if (block.terminal.kind === 'return') {
/**
* Assume directly returned functions are safe to call
*/
const maybeLoweredFunc = temporaries.get(
block.terminal.value.identifier.id,
);
if (maybeLoweredFunc != null) {
hoistableFunctions.add(maybeLoweredFunc.fn);
}
}
}
for (const [_, {fn, mayInvoke}] of temporaries) {
if (hoistableFunctions.has(fn)) {
for (const called of mayInvoke) {
hoistableFunctions.add(called);
}
}
}
return hoistableFunctions;
}

Some files were not shown because too many files have changed in this diff Show More