Compare commits

..

59 Commits

Author SHA1 Message Date
Alexander Kachkaev
7283a213db Fix headings in React 19 CHANGELOG (#31683)
Co-authored-by: eps1lon <sebastian.silbermann@vercel.com>
2024-12-06 16:55:53 +01:00
Eng Zer Jun
65ba3e87e4 docs: use diff codeblock for useReducer typings change (#31685)
## Summary

This PR updates the 19.0.0 changelog to use diff-styled code blocks for
illustrating changes to `useReducer` typings. Also removes the incorrect
square brackets in the second diff, it should be `Action` instead of
`[Action]`.


## How did you test this change?

**Before**


![2024-12-06_10-49](https://github.com/user-attachments/assets/bde94eec-a7cc-4fc8-bcca-37867633d37e)


**After**


![2024-12-06_10-51](https://github.com/user-attachments/assets/693fb7ac-68a3-45fc-aada-0c7594441b2a)

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2024-12-06 10:12:38 -05:00
Paul Schreiber
c56c623432 docs: fix capitalization of TypeScript (#31682) 2024-12-06 00:04:11 +01:00
owenarthur
d27fa084f6 Fix two typos in CHANGELOG.md (#31678) 2024-12-06 00:03:24 +01:00
Jack Pope
52685a7503 Add changelog for 19.0.0 (#31675)
Co-authored-by: Jack Pope <jackpope@meta.com>
2024-12-05 13:46:52 -05:00
Sebastian Markbåge
1c9b138714 Don't serialize chunk ids for Hint and Console rows (#31671)
Hints and Console logs are side-effects and don't belong to any
particular value. They're `void`. Therefore they don't need a row ID.

In the current parsing scheme it's ok to omit the id. It just becomes
`0` which is the initial value which is then unused for these row types.

So it looks like:

```
:HP[...]
:W[...]
0:{...}
```

We could patch the parsing to encode the tag in the ID so it's more like
the ID is the target of the side-effect.

```
H:P[...]
W:[...]
0:{...}
```

Or move the tagging to the beginning like it used to be.

But this seems simple enough for now.
2024-12-04 19:53:20 -05:00
Josh Story
de68d2f4a2 Register Suspense retry handlers in commit phase (#31667)
To avoid GC pressure and accidentally hanging onto old trees Suspense
boundary retries are now implemented in the commit phase. I used the
Callback flag which was previously only used to schedule callbacks for
Class components. This isn't quite semantically equivalent but it's
unused and seemingly compatible.
2024-12-04 07:58:43 -08:00
Josh Story
16d2bbbd1f Client render dehydrated Suspense boundaries on document load (#31620)
When streaming SSR while hydrating React will wait for Suspense
boundaries to be revealed by the SSR stream before attempting to hydrate
them. The rationale here is that the Server render is likely further
ahead of whatever the client would produce so waiting to let the server
stream in the UI is preferable to retrying on the client and possibly
delaying how quickly the primary content becomes available. However If
the connection closes early (user hits stop for instance) or there is a
server error which prevents additional HTML from being delivered to the
client this can put React into a broken state where the boundary never
resolves nor errors and the hydration never retries that boundary
freezing it in it's fallback state.

Once the document has fully loaded we know there is not way any
additional Suspense boundaries can arrive. This update changes react-dom
on the client to schedule client renders for any unfinished Suspense
boundaries upon document loading.

The technique for client rendering a fallback is pretty straight
forward. When hydrating a Suspense boundary if the Document is in
'complete' readyState we interpret pending boundaries as fallback
boundaries. If the readyState is not 'complete' we register an event to
retry the boundary when the DOMContentLoaded event fires.

To test this I needed JSDOM to model readyState. We previously had a
temporary implementation of readyState for SSR streaming but I ended up
implementing this as a mock of JSDOM that implements a fake readyState
that is mutable. It starts off in 'loading' readyState and you can
advance it by mutating document.readyState. You can also reset it to
'loading'. It fires events when changing states.

This seems like the least invasive way to get closer-to-real-browser
behavior in a way that won't require remembering this subtle detail
every time you create a test that asserts Suspense resolution order.
2024-12-03 13:13:35 -08:00
Jordan Brown
6bcf0d20da [compiler] Empty dep arrays for globals/module-scoped values/imports (#31666)
Any LoadGlobal in the "infer deps" position can safely use an empty dep
array. Globals have no reactive deps!

I just keep messing up sapling. This is the revised version of #31662
2024-12-03 13:54:08 -05:00
mofeiZ
b9b510df2a Revert "Replace deprecated dependency in eslint-plugin-react-compiler" (#31665)
Reverts facebook/react#31629

`@babel/plugin-proposal-private-methods` is not compatible with
`@babel/traverse` versions < 7.25 (see
https://github.com/babel/babel/issues/16851). Internally we have
partners that use a less modern babel version, and we expect this to be
an issue for older codebases in OSS as well.
2024-12-03 11:46:08 -05:00
Jordan Brown
1b1283ade7 [compiler] Support default imports for autodep config (#31657)
## Summary

Allows us to add deps for things like `import useWrapperEffect from
'useWrapperEffect'`

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31657).
* __->__ #31657
* #31652
2024-12-03 07:42:53 -05:00
Jordan Brown
2ab471c8d2 [compiler] Don't include current field accesses in auto-deps (#31652)
## Summary

Drops .current field accesses in inferred dep arrays

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31652).
* #31657
* __->__ #31652
2024-12-03 07:42:28 -05:00
mofeiZ
865d2c418d [compiler] Add meta internal option for useMemoCache import (#31654)
Adds `target: 'donotuse_meta_internal'`, which inserts useMemoCache
imports directly from `react`. Note that this is only valid for Meta
bundles, as others do not [re-export the `c`
function](5b0ef217ef/packages/react/index.fb.js (L68-L70)).

```js
// target=donotuse_meta_internal
import {c as _c} from 'react';

// target=19
import {c as _c} from 'react/compiler-runtime';

// target=17,18
import {c as _c} from 'react-compiler-runtime';
```

Meta is a bit special in that react runtime and compiler are guaranteed
to be up-to-date and compatible. It also has its own bundling and module
resolution logic, which makes importing from `react/compiler-runtime`
tricky.

I'm also fine with implementing the alternative which adds an internal
stub for `react-compiler-runtime` and
[bundles](5b0ef217ef/scripts/rollup/bundles.js (L120))
the runtime for internal builds.
2024-12-02 17:42:58 -05:00
Ricky
5b0ef217ef s/server action/server function (#31005)
## Overview

Changes the error message to say "Server Functions" instead of "Server
Actions" since this error can fire in cases like:

```
<button onClick={serverFunction} />
```

Which is calling a server function, not a server action.
2024-12-02 10:02:31 -05:00
Pavel
7670501b0d Replace deprecated dependency in eslint-plugin-react-compiler (#31629) 2024-11-24 23:32:11 -05:00
Jordan Brown
2a9f4c04e5 [compiler] Infer deps configuration (#31616)
Adds a way to configure how we insert deps for experimental purposes.

```
[
  {
    module: 'react',
    imported: 'useEffect',
    numRequiredArgs: 1,
  },
  {
    module: 'MyExperimentalEffectHooks',
    imported: 'useExperimentalEffect',
    numRequiredArgs: 2,
  },
]
```

would insert dependencies for calls of `useEffect` imported from `react`
if they have 1 argument and calls of useExperimentalEffect` from
`MyExperimentalEffectHooks` if they have 2 arguments. The pushed dep
array is appended to the arg list.
2024-11-22 17:19:20 -05:00
lauren
e3b7ef32be [crud] Only export uRC when flag is enabled (#31617)
It's tricky to do feature detection of uRC currently because it's always
present on the export. Let's conditionally export it instead.
2024-11-22 16:13:42 -05:00
Joseph Savona
eee5ca2a92 [compiler] Prune all unused array destructure items during DCE (#31619)
We didn't originally support holes within array patterns, so DCE was
only able to prune unused items from the end of an array pattern. Now
that we support holes we can replace any unused item with a hole, and
then just prune the items to the last identifier/spread entry.

Note: this was motivated by finding useState where either the state or
setState go unused — both are strong indications that you're violating
the rules in some way. By DCE-ing the unused portions of the useState
destructuring we can easily check if you're ignoring either value.

closes #31603 

This is a redo of that PR not using ghstack
2024-11-22 15:59:59 -05:00
Sebastian Markbåge
aba370f1e4 Add moveBefore Experiment (#31596)
A long standing issue for React has been that if you reorder stateful
nodes, they may lose their state and reload. The thing moving loses its
state. There's no way to solve this in general where two stateful nodes
swap.

The [`moveBefore()`
proposal](https://chromestatus.com/feature/5135990159835136?gate=5177450351558656)
has now moved to
[intent-to-ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/YE_xLH6MkRs/m/_7CD0NYMAAAJ).
This function is kind of like `insertBefore` but preserves state.

There's [a demo here](https://state-preserving-atomic-move.glitch.me/).
Ideally we'd port this demo to a fixture so we can try it.

Currently this flag is always off - even in experimental. That's because
this is still behind a Chrome flag so it's a little early to turn it on
even in experimental. So you need a custom build. It's on in RN but only
because it doesn't apply there which makes it easier to tell that it's
safe to ship once it's on everywhere else.

The other reason it's still off is because there's currently a semantic
breaking change. `moveBefore()` errors if both nodes are disconnected.
That happens if we're inside a completely disconnected React root.
That's not usually how you should use React because it means effects
can't read layout etc. However, it is currently supported. To handle
this we'd have to try/catch the `moveBefore` to handle this case but we
hope this semantic will change before it ships. Before we turn this on
in experimental we either have to wait for the implementation to not
error in the disconnected-disconnected case in Chrome or we'd have to
add try/catch.
2024-11-22 13:24:29 -05:00
Sebastian Markbåge
1345c37941 Mark all lanes in order on every new render (#31615)
This is a hack that ensures that all four lanes as visible whether you
have any tracks in them or not, and that they're in the priority order
within the Scheduler track group. We do want to show all even if they're
not used because it shows what options you're missing out on.

<img width="1035" alt="Screenshot 2024-11-22 at 12 38 30 PM"
src="https://github.com/user-attachments/assets/f30ab0b9-af5e-48ed-b042-138444352575">

In Chrome, the order of tracks within a group are determined by the
earliest start time. We add fake markers at start time zero in that
order eagerly. Ideally we could do this only once but because calls that
aren't recorded aren't considered for ordering purposes, we need to keep
adding these over and over again in case recording has just started. We
can't tell when recording starts.

Currently performance.mark() are in first insertion order but
performance.measure() are in the reverse order. I'm not sure that's
intentional. We can always add the 0 time slot even if it's in the past.
That's still considered for ordering purposes as long as the measurement
is recorded at the time we call it.
2024-11-22 13:04:05 -05:00
Jordan Brown
e697386c10 [compiler] First cut at dep inference (#31386)
This is for researching/prototyping, not a feature we are releasing
imminently.

Putting up an early version of inferring effect dependencies to get
feedback on the approach. We do not plan to ship this as-is, and may not
start by going after direct `useEffect` calls. Until we make that
decision, the heuristic I use to detect when to insert effect deps will
suffice for testing.

The approach is simple: when we see a useEffect call with no dep array
we insert the deps inferred for the lambda passed in. If the first
argument is not a lambda then we do not do anything.

This diff is the easy part. I think the harder part will be ensuring
that we can infer the deps even when we have to bail out of memoization.
We have no other features that *must* run regardless of rules of react
violations. Does anyone foresee any issues using the compiler passes to
infer reactive deps when there may be violations?

I have a few questions:
1. Will there ever be more than one instruction in a block containing a
useEffect? if no, I can get rid of the`addedInstrs` variable that I use
to make sure I insert the effect deps array temp creation at the right
spot.
2. Are there any cases for resolving the first argument beyond just
looking at the lvalue's identifier id that I'll need to take into
account? e.g., do I need to recursively resolve certain bindings?

---------

Co-authored-by: Mofei Zhang <feifei0@meta.com>
2024-11-22 12:15:13 -05:00
Sebastian Markbåge
91061073d5 Mark ping time as update (#31611)
This ensures that we mark the time from ping until we render as
"Blocked".

We intentionally don't want to show the event time even if it's
something like "load" because it draws attention away from interactions
etc.

<img width="577" alt="Screenshot 2024-11-21 at 7 22 39 PM"
src="https://github.com/user-attachments/assets/70cca2e8-bd5e-489f-98f0-b4dfee5940af">
2024-11-21 19:36:38 -05:00
Sebastian Markbåge
a9f14cb44e Fix Logging of Immediately Resolved Promises (#31610)
This avoid re-emitting the yellow "Event" log when we ping inside the
original event. Instead of treating events as repeated when we get
repeated updates, we treat them as repeated if we've ever logged out
this event before.

Additionally, in the case the prerender sibling flag is on we need to
ensure that if a render gets interrupted when it has been suspended we
treat that as "Prewarm" instead of "Interrupted Render".

Before:
<img width="539" alt="Screenshot 2024-11-19 at 2 39 44 PM"
src="https://github.com/user-attachments/assets/190ca50c-5168-40d8-a6fd-6b9a583af1f0">

After:

<img width="1004" alt="Screenshot 2024-11-21 at 4 53 16 PM"
src="https://github.com/user-attachments/assets/0c441ada-1ed1-412c-8935-aaf040c25dfe">
2024-11-21 17:16:54 -05:00
lauren
c11c9510fa [crud] Fix deps comparison bug (#31599)
Fixes a bug with the experimental `useResourceEffect` hook where we
would compare the wrong deps when there happened to be another kind of
effect preceding the ResourceEffect. To do this correctly we need to add
a pointer to the ResourceEffect's identity on the update.

I also unified the previously separate push effect impls for resource
effects since they are always pushed together as a unit.
2024-11-20 16:54:41 -05:00
lauren
64f89510af [crud] Enable on RTR FB builds (#31590) 2024-11-19 17:25:11 -05:00
lauren
7558ffe84d [crud] Fix copy paste typo (#31588)
Happens to the best of us.
2024-11-19 17:13:01 -05:00
Sebastian Markbåge
7c254b6576 Log yielded time in the Component Track (#31563)
Stacked on #31552. Must be tested with `enableSiblingPrerendering` off
since the `use()` optimization is not on there yet.

This adds a span to the Components track when we yield in the middle of
the event loop. In this scenario, the "Render" span continues through
out the Scheduler track. So you can see that the Component itself might
not take a long time but yielding inside of it might.

This lets you see if something was blocking the React render loop while
yielding. If we're blocked 1ms or longer we log that as "Blocked".

If we're yielding due to suspending in the middle of the work loop we
log this as "Suspended".

<img width="837" alt="Screenshot 2024-11-16 at 1 15 14 PM"
src="https://github.com/user-attachments/assets/45a858ea-17e6-416c-af1a-78c126e033f3">

If the render doesn't commit because it restarts due to some other
prewarming or because some non-`use()` suspends, it doesn't have from
context components.

<img width="971" alt="Screenshot 2024-11-16 at 1 13 55 PM"
src="https://github.com/user-attachments/assets/a67724f8-702e-4e7d-9499-9ffc09541a61">

The `useActionState` path doesn't work yet because the `use()`
optimization doesn't work there for some reason. But the idea is that it
should mark the time that the component is blocked as Action instead of
Suspended.
2024-11-19 13:56:22 -05:00
Sebastian Markbåge
6177b18c66 Track suspended time when the render doesn't commit because it suspended (#31552)
When we suspend the render with delay, we won't do any more work until
we get some kind of another update/ping. It's because conceptually
something is suspended and then will update later. We need to highlight
this period to show why it's not doing any work. We fill the empty space
with "Suspended". This stops whenever the same lane group starts
rendering again. Clamped by the preceeding start time/event time/update
time.

<img width="902" alt="Screenshot 2024-11-15 at 1 01 29 PM"
src="https://github.com/user-attachments/assets/acf9dc9a-8fc3-4367-a8b0-d19f9c9eac73">

Ideally we would instead start the next render and suspend the work loop
at all places we suspend. In that mode this will instead show up as a
very long "Render" with a "Suspended" period instead highlighted in the
Components track as one component is suspended. We'll soon have that for
`use()` but not all updates so this covers the rest.

One issue with `useActionState` is that it is implemented as suspending
at the point of the `useActionState` which means that the period of the
Action shows up as a suspended render instead of as an Action which
happens for raw actions. This is not really how you conceptually think
about it so we need some special case for `useActionState`. In the
screenshot above, the first "Suspended" is actually awaiting an Action
and the second "Suspended" is awaiting the data from it.
2024-11-19 13:42:44 -05:00
lauren
6f0dc2947b [compiler] Update hermes deps (#31586)
```
=> Found "hermes-parser@0.25.1"
info Reasons this module exists
   - "_project_#prettier-plugin-hermes-parser" depends on it
   - Hoisted from "_project_#prettier-plugin-hermes-parser#hermes-parser"
   - Hoisted from "_project_#eslint-plugin-react-compiler#hermes-parser"
   - Hoisted from "_project_#snap#hermes-parser"
   - Hoisted from "_project_#snap#babel-plugin-syntax-hermes-parser#hermes-parser"
   - Hoisted from "_project_#eslint-plugin-react-compiler#hermes-eslint#hermes-parser"
info Disk size without dependencies: "1.49MB"
info Disk size with unique dependencies: "1.82MB"
info Disk size with transitive dependencies: "1.82MB"
info Number of shared dependencies: 1
  Done in 0.81s.
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31586).
* __->__ #31586
* #31585
2024-11-19 10:52:48 -05:00
lauren
d2e8954d06 [compiler] Update react deps (#31585)
```
=> Found "react@0.0.0-experimental-4beb1fd8-20241118"
info Reasons this module exists
   - "_project_#babel-plugin-react-compiler" depends on it
   - Hoisted from "_project_#babel-plugin-react-compiler#react"
   - Hoisted from "_project_#snap#react"
info Disk size without dependencies: "252KB"
info Disk size with unique dependencies: "252KB"
info Disk size with transitive dependencies: "252KB"
info Number of shared dependencies: 0
  Done in 0.60s.
```

```
=> Found "react-dom@0.0.0-experimental-4beb1fd8-20241118"
info Reasons this module exists
   - "_project_#babel-plugin-react-compiler" depends on it
   - Hoisted from "_project_#babel-plugin-react-compiler#react-dom"
   - Hoisted from "_project_#snap#react-dom"
info Disk size without dependencies: "8.04MB"
info Disk size with unique dependencies: "8.17MB"
info Disk size with transitive dependencies: "8.17MB"
info Number of shared dependencies: 1
  Done in 0.56s.
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31585).
* #31586
* __->__ #31585
2024-11-19 10:52:38 -05:00
Mark Skelton
eaf2d5c670 fix[eslint-plugin-react-hooks]: Fix error when callback argument is an identifier with an as expression (#31119) 2024-11-19 10:36:30 +01:00
lauren
c866d75060 [playground] Fix broken tests (#31573)
Our e2e setup with monaco is kinda brittle since it relies on the dom.
It seems like longish text gets truncated so let's just simpify all
these test cases.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31573).
* __->__ #31573
* #31572
2024-11-18 19:18:09 -05:00
lauren
ee10c74824 [playground] Fix incorrect ci path and change reporter (#31572)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31572).
* #31573
* __->__ #31572
2024-11-18 19:17:58 -05:00
lauren
2a4f2507a0 [playground] Always upload test results in ci (#31571)
Small change to always upload test results from CI even if the test
failed.
2024-11-18 16:34:10 -05:00
Aditya Subramanyam
579cc2a44c [playground] Add support for "use no memo" (#31561)
Fixes #31331

## Summary
There is a bug in
playground(https://github.com/facebook/react/issues/31331) which doesnt
support 'use memo' or 'use no memo' directives. Its misleading while
debugging components in the playground

## How did you test this change?
Ran test cases and added a few extra test cases as well

## Changes
1) Adds support for 'use memo' and 'use no memo'
2) Cleanup E2E test cases a bit
3) Adds test cases for use memo
4) Added documentation to run test cases

## Implementation
`parseFunctions` returns a set of functions to be compiled. But, it
doesnt filter out/handle memoized opted/un-opted functions using
directives.

ive just created a `compile` flag to enable/disable compiling
[here](https://github.com/facebook/react/pull/31561/files#diff-305de47a3fe3ce778e22d5c5cf438419a59de8e7f785b45f659e7b41b1e30b03R113)

Then I am just skipping those functions from getting compile
[here](https://github.com/facebook/react/pull/31561/files#diff-305de47a3fe3ce778e22d5c5cf438419a59de8e7f785b45f659e7b41b1e30b03R253)
2024-11-18 15:38:22 -05:00
mofeiZ
e33b13795d [compiler] repro for type inference + control flow bug (#31570)
Repro for bug in our type inference system.

We currently propagate inferred types through control flow / potential
type guards. Note that this is inconsistent with both
[Flow](https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA)
and
[Typescript](https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA)
2024-11-18 11:54:11 -05:00
Jack Pope
4beb1fd8ba [compiler] Support enableRefAsProp in jsx transform (#31558)
Since `enableRefAsProp` shipped everywhere, the ReactElement
implementation on prod puts refs on both `element.ref` and
`element.props.ref`. Here we let the `ref` case fall through so its now
available on props, matching the JSX runtime.
2024-11-18 10:51:16 -05:00
lauren
047d95e85f [crud] Basic implementation (#31523)
This PR introduces a new experimental hook `useResourceEffect`, which is
something that we're doing some very early initial tests on.

This may likely not pan out and will be removed or modified if so.
Please do not rely on it as it will break.
2024-11-18 10:16:37 -05:00
Sebastian Markbåge
92c0f5f85f Track separate SuspendedOnAction flag by rethrowing a separate SuspenseActionException sentinel (#31554)
This lets us track separately if something was suspended on an Action
using useActionState rather than suspended on Data.

This approach feels quite bloated and it seems like we'd eventually
might want to read more information about the Promise that suspended and
the context it suspended in. As a more general reason for suspending.

The way useActionState works in combination with the prewarming is quite
unfortunate because 1) it renders blocking to update the isPending flag
whether you use it or not 2) it prewarms and suspends the useActionState
3) then it does another third render to get back into the useActionState
position again.
2024-11-15 17:52:24 -05:00
lauren
053b3cb050 [crud] Rename Effect type (#31557)
Adds a new `Effect` type which for now just points to the `SimpleEffect`
type, in prepartion for later in the stack where we add more.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31557).
* #31523
* __->__ #31557
* #31556
* #31555
2024-11-15 17:50:50 -05:00
lauren
7dd6b9e68a [crud] Add enableUseResourceEffectHook flag (#31556)
Adds a new feature flag for `enableUseResourceEffectHook`.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31556).
* #31523
* #31557
* __->__ #31556
* #31555
2024-11-15 17:49:42 -05:00
lauren
d8afd1c82e [crud] Scaffold initial types (#31555)
Scaffolds the initial `useResourceEffect` dispatcher type. This will
eventually be folded into `useEffect` et al as an overload.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31555).
* #31523
* #31557
* #31556
* __->__ #31555
2024-11-15 17:49:31 -05:00
mofeiZ
0480cdb58c [compiler][be] Clean up nested function context in DCE (#31202)
Now that we rely on function context exclusively, let's clean up
`HIRFunction.context` after DCE. This PR is in preparation of #31204,
which would otherwise have unnecessary declarations (of context values
that become entirely DCE'd)

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31202).
* __->__ #31202
* #31203
* #31201
* #31200
* #31521
2024-11-15 13:06:39 -05:00
mofeiZ
0f3c62b466 [compiler][be] Patch test fixtures for evaluator (#31203)
Add more `FIXTURE_ENTRYPOINT`s

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31203).
* #31202
* __->__ #31203
* #31201
* #31200
* #31521
2024-11-15 13:06:29 -05:00
mofeiZ
858633f900 [compiler] Lower JSXMemberExpression with LoadLocal (#31201)
`JSXMemberExpression` is currently the only instruction (that I know of)
that directly references identifier lvalues without a corresponding
`LoadLocal`.

This has some side effects:
- deadcode elimination and constant propagation now reach
JSXMemberExpressions
- we can delete `LoweredFunction.dependencies` without dangling
references (previously, the only reference to JSXMemberExpression
objects in HIR was in function dependencies)
- JSXMemberExpression now is consistent with all other instructions
(e.g. has a rvalue-producing LoadLocal)

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31201).
* #31202
* #31203
* __->__ #31201
* #31200
* #31521
2024-11-15 13:06:19 -05:00
mofeiZ
c09402aa2f [compiler] Stop using function dependencies in propagateScopeDeps (#31200)
Recursively visit inner function instructions to extract dependencies
instead of using `LoweredFunction.dependencies` directly.

This is currently gated by enableFunctionDependencyRewrite, which needs
to be removed before we delete `LoweredFunction.dependencies` altogether
(#31204).

Some nice side effects
- optional-chaining deps for inner functions
- full DCE and outlining for inner functions (see #31202)
- fewer extraneous instructions (see #31204)

-
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31200).
* #31202
* #31203
* #31201
* __->__ #31200
* #31521
2024-11-15 13:06:05 -05:00
mofeiZ
4972718c26 [compiler] Fix: ref.current now correctly reactive (#31521)
We were previously filtering out `ref.current` dependencies in
propagateScopeDependencies:checkValidDependency`. This is incorrect.

Instead, we now always take a dependency on ref values (the outer box)
as they may be reactive. Pruning is done in
pruneNonReactiveDependencies.

This PR includes a small patch to `collectReactiveIdentifier`. Prior to
this, we conservatively assumed that pruned scopes always produced
reactive declarations. This assumption fixed a bug with non-reactivity,
but some of these declarations are `useRef` calls. Now we have special
handling for this case
```js
// This often produces a pruned scope
React.useRef(1);
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31521).
* #31202
* #31203
* #31201
* #31200
* __->__ #31521
2024-11-15 13:05:55 -05:00
Sebastian Markbåge
3720870a97 Log Render Phases that Never Committed (#31548)
This includes:

- `Interrupted Render`: Interrupted Renders (setState or ping at higher
priority)
- `Prewarm`: Suspended Renders outside a Suspense boundary
(RootSuspendedWithDelay/RootSuspendedAtTheShell)
- `Errored Render`: Render that errored somewhere in the tree (Fatal or
Not) (which may or may not be retried and then complete)
- `Teared Render`: Due to useSyncExternalStore not matching (which will
do another sync attempt)

Suspended Commit:

<img width="893" alt="Screenshot 2024-11-14 at 11 47 40 PM"
src="https://github.com/user-attachments/assets/b25a6a8b-a5e9-4d66-b325-57aef4bf9dad">

Errored with a second recovery attempt that also errors:

<img width="976" alt="Screenshot 2024-11-15 at 12 09 06 AM"
src="https://github.com/user-attachments/assets/9ce52cbb-b587-4f1e-8b67-e51d9073ae5b">
2024-11-15 12:13:24 -05:00
Andrew Clark
e1ef8c9515 Don't dist-tag canaries with rc (#31546) 2024-11-15 10:05:36 +01:00
Sebastian Markbåge
8a41d6ceab Unify RootDidNotComplete and RootSuspendedWithDelay exit path (#31547)
Also rename RootDidNotComplete to RootSuspendedAtTheShell since it
specifically means something suspended in the shell during hydration.
2024-11-14 23:51:51 -05:00
Zack Tanner
63cde684f5 (chore): copy fix in <style> precedence error (#31524)
## Summary

This fixes a typo in the error that gets reported when Float errors
while hoisting a style tag that does not contain both `precedence` and
`href`. There was a typo in _conflict_ and the last part of the sentence
doesn't make sense. I assume it wasn't needed since the message already
suggests moving the style tag to the head manually.
2024-11-14 14:03:59 -08:00
Sebastian Markbåge
b01722d585 Format event with "warning" yellow and prefix with "Event: " (#31536)
It's useful to quickly see where new events are kicking off new
rendering. This uses the new "warning" color (yellow) to do that. This
is to help distinguish it from the purple (secondary color) which is
used for the commit phase which is more of a follow up and it's often
that you have several rerenders within one event which makes it hard to
tell a part where it starts and event otherwise.

For the span marking between previous render within the same event and
the next setState, I use secondary-light (light purple) since it's kind
of still part of the same sequence at that point. It's usually a spawned
render (e.g. setState in useEffect or microtask) but it can also be
sequential flushSync.

I was bothered by that the event name is the only thing that's lower
case so I prefixed it with `Event: ` like the JS traces are.

<img width="1499" alt="Screenshot 2024-11-13 at 7 15 45 PM"
src="https://github.com/user-attachments/assets/0c81c810-6b5d-4fc7-9bc0-d15b53844ade">

It might be a little confusing why our track starts earlier than the JS
one below in the "Main Thread" flamegraph which looks the same. That's
because ours is the start of the event time which is when the click
happens where as the Main Thread one is when the JS event loop gets
around to processing the event.
2024-11-14 16:44:29 -05:00
Sebastian Markbåge
c13986da78 Fix Overlapping "message" Bug in Performance Track (#31528)
When you schedule a microtask from render or effect and then call
setState (or ping) from there, the "event" is the event that React
scheduled (which will be a postMessage). The event time of this new
render will be before the last render finished.

We usually clamp these but in this scenario the update doesn't happen
while a render is happening. Causing overlapping events.

Before:

<img width="1229" alt="Screenshot 2024-11-12 at 11 01 30 PM"
src="https://github.com/user-attachments/assets/9652cf3b-b358-453c-b295-1239cbb15952">

Therefore when we finalize a render we need to store the end of the last
render so when we a new update comes in later with an event time earlier
than that, we know to clamp it.

There's also a special case here where when we enter the
`RootDidNotComplete` or `RootSuspendedWithDelay` case we neither leave
the root as in progress nor commit it. Those needs to finalize too.
Really this should be modeled as a suspended track that we haven't added
yet. That's the gap between "Blocked" and "message" below.

After:

<img width="1471" alt="Screenshot 2024-11-13 at 12 31 34 AM"
src="https://github.com/user-attachments/assets/b24f994e-9055-4b10-ad29-ad9b36302ffc">

I also fixed an issue where we may log the same event name multiple
times if we're rendering more than once in the same event. In this case
I just leave a blank trace between the last commit and the next update.

I also adding ignoring of the "message" event at all in these cases when
the event is from React's scheduling itself.
2024-11-14 16:35:08 -05:00
Sebastian Markbåge
4686872159 Log passive commit phase when it wasn't delayed (#31526)
Fixes a bug.

We're supposed to not log "Waiting for Paint" if the passive effect
phase was forced since we weren't really waiting until the paint.
Instead we just log an empty string when we force it to still ensure
continuity.

We should always log the passive phase. This check was in the wrong
place.
2024-11-14 16:30:05 -05:00
lauren
5d89471ca6 Export __COMPILER_RUNTIME in stable (#31540)
In order to make use of the compiler in stable releases (eg React 19 RC,
canary), we need to export the compiler runtime in the stable channel as
well.
2024-11-14 15:46:35 -05:00
Niklas Mollenhauer
3c15d219aa [compiler] Disable emit of .tsbuildinfo (#31459)
## Summary
`@rollup/plugin-typescript` emits a warning while building, hinting that
`outputToFilesystem` defaults to true.

Although "noEmit" is set to `true` for the tsconfig, rollup writes a
`dist/.tsbuildinfo`. That file is then also shipped inside the npm
module and doesn't offer any benefit for library consumers. Setting this
option to false results in the file not being written and thus omitted
from the npm module.

## How did you test this change?
`dist/.tsbuildinfo` is not emitted any more.
2024-11-14 15:43:36 -05:00
Sebastian Markbåge
3644f0bd21 Use completedRenderEndTime as the start of the commit phase if it's an immediate commit (#31527)
We don't log a phase like "Throttled" or "Suspended" for this case so it
can leave a tiny gap otherwise.

This ensures it connects without a seam.

<img width="1059" alt="Screenshot 2024-11-12 at 9 34 17 PM"
src="https://github.com/user-attachments/assets/7ed4a3c7-b508-4fc1-b956-44c4e31faa4d">
2024-11-14 13:05:38 -05:00
Sebastian Markbåge
8657869999 Separate Tracks for Components and Phases (#31525)
Previously we were showing Components inside each lane track but that
meant that as soon as you expanded a lane you couldn't see the other
line so you couldn't get an overview over how well things were
scheduled.

This instead moves all the Components into a single top-level track and
renames the previous one to a "Scheduler" track group.

<img width="1352" alt="Screenshot 2024-11-12 at 8 26 05 PM"
src="https://github.com/user-attachments/assets/590bc6d3-3540-4ee4-b474-5d733b8d8d8d">

That way you can get an overview over what React is working on first and
then right below see which Component is being worked on.

Ideally the "Scheduler" track would be always expanded since each Track
is always just a single row. Now you have to expand each lane to see the
labels but then you're wasting a lot of vertical real estate. There's
currently no option to create this with the Chrome performance.measure
extensions.

<img width="1277" alt="Screenshot 2024-11-12 at 8 26 16 PM"
src="https://github.com/user-attachments/assets/4fc39e35-10ec-4452-ad32-c1c2e6b5e1a8">
2024-11-14 13:05:20 -05:00
lauren
b15135b9f5 [ez] Update useMemoCache return type (#31539)
Use `mixed` instead of `any`
2024-11-14 12:10:51 -05:00
177 changed files with 6144 additions and 1137 deletions

View File

@@ -6,7 +6,7 @@ on:
pull_request:
paths:
- compiler/**
- .github/workflows/compiler-playground.yml
- .github/workflows/compiler_playground.yml
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
@@ -44,9 +44,12 @@ jobs:
- name: yarn install playground
run: yarn install --frozen-lockfile
- run: npx playwright install --with-deps chromium
- run: yarn test
- run: CI=true yarn test
- run: ls -R test-results
if: '!cancelled()'
- name: Archive test results
if: '!cancelled()'
uses: actions/upload-artifact@v4
with:
name: test-results
path: test-results
path: compiler/apps/playground/test-results

View File

@@ -27,7 +27,7 @@ jobs:
# because this used to be called the "next" channel and some
# 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,rc
dist_tag: canary,next
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -15,7 +15,7 @@ jobs:
with:
commit_sha: ${{ github.sha }}
release_channel: stable
dist_tag: canary,next,rc
dist_tag: canary,next
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -1,3 +1,275 @@
## 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.
> Note: To help make the upgrade to React 19 easier, weve published a react@18.3 release that is identical to 18.2 but adds warnings for deprecated APIs and other changes that are needed for React 19. We recommend upgrading to React 18.3.1 first to help identify any issues before upgrading to React 19.
### New Features
#### React
* Actions: `startTransition` can now accept async functions. Functions passed to `startTransition` are called “Actions”. A given Transition can include one or more Actions which update state in the background and update the UI with one commit. In addition to updating state, Actions can now perform side effects including async requests, and the Action will wait for the work to finish before finishing the Transition. This feature allows Transitions to include side effects like `fetch()` in the pending state, and provides support for error handling, and optimistic updates.
* `useActionState`: is a new hook to order Actions inside of a Transition with access to the state of the action, and the pending state. It accepts a reducer that can call Actions, and the initial state used for first render. It also accepts an optional string that is used if the action is passed to a form `action` prop to support progressive enhancement in forms.
* `useOptimistic`: is a new hook to update state while a Transition is in progress. It returns the state, and a set function that can be called inside a transition to “optimistically” update the state to expected final value immediately while the Transition completes in the background. When the transition finishes, the state is updated to the new value.
* `use`: is a new API that allows reading resources in render. In React 19, `use` accepts a promise or Context. If provided a promise, `use` will suspend until a value is resolved. `use` can only be used in render but can be called conditionally.
* `ref` as a prop: Refs can now be used as props, removing the need for `forwardRef`.
* **Suspense sibling pre-warming**: When a component suspends, React will immediately commit the fallback of the nearest Suspense boundary, without waiting for the entire sibling tree to render. After the fallback commits, React will schedule another render for the suspended siblings to “pre-warm” lazy requests.
#### React DOM Client
* `<form> action` prop: Form Actions allow you to manage forms automatically and integrate with `useFormStatus`. When a `<form> action` succeeds, React will automatically reset the form for uncontrolled components. The form can be reset manually with the new `requestFormReset` API.
* `<button> and <input> formAction` prop: Actions can be passed to the `formAction` prop to configure form submission behavior. This allows using different Actions depending on the input.
* `useFormStatus`: is a new hook that provides the status of the parent `<form> action`, as if the form was a Context provider. The hook returns the values: `pending`, `data`, `method`, and `action`.
* Support for Document Metadata: Weve added support for rendering document metadata tags in components natively. React will automatically hoist them into the `<head>` section of the document.
* Support for Stylesheets: React 19 will ensure stylesheets are inserted into the `<head>` on the client before revealing the content of a Suspense boundary that depends on that stylesheet.
* Support for async scripts: Async scripts can be rendered anywhere in the component tree and React will handle ordering and deduplication.
* Support for preloading resources: React 19 ships with `preinit`, `preload`, `prefetchDNS`, and `preconnect` APIs to optimize initial page loads by moving discovery of additional resources like fonts out of stylesheet loading. They can also be used to prefetch resources used by an anticipated navigation.
#### React DOM Server
* Added `prerender` and `prerenderToNodeStream` APIs for static site generation. They are designed to work with streaming environments like Node.js Streams and Web Streams. Unlike `renderToString`, they wait for data to load for HTML generation.
#### React Server Components
* RSC features such as directives, server components, and server functions are now stable. This means libraries that ship with Server Components can now target React 19 as a peer dependency with a react-server export condition for use in frameworks that support the Full-stack React Architecture. The underlying APIs used to implement a React Server Components bundler or framework do not follow semver and may break between minors in React 19.x. See [docs](https://19.react.dev/reference/rsc/server-components) for how to support React Server Components.
### Deprecations
* Deprecated: `element.ref` access: React 19 supports ref as a prop, so were deprecating `element.ref` in favor of `element.props.ref`. Accessing will result in a warning.
* `react-test-renderer`: In React 19, react-test-renderer logs a deprecation warning and has switched to concurrent rendering for web usage. We recommend migrating your tests to [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/) or [@testing-library/react-native](https://testing-library.com/docs/react-native-testing-library/intro)
### Breaking Changes
React 19 brings in a number of breaking changes, including the removals of long-deprecated APIs. We recommend first upgrading to `18.3.1`, where we've added additional deprecation warnings. Check out the [upgrade guide](https://19.react.dev/blog/2024/04/25/react-19-upgrade-guide) for more details and guidance on codemodding.
### React
* New JSX Transform is now required: We introduced [a new JSX transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) in 2020 to improve bundle size and use JSX without importing React. In React 19, were adding additional improvements like using ref as a prop and JSX speed improvements that require the new transform.
* Errors in render are not re-thrown: Errors that are not caught by an Error Boundary are now reported to window.reportError. Errors that are caught by an Error Boundary are reported to console.error. Weve introduced `onUncaughtError` and `onCaughtError` methods to `createRoot` and `hydrateRoot` to customize this error handling.
* Removed: `propTypes`: Using `propTypes` will now be silently ignored. If required, we recommend migrating to TypeScript or another type-checking solution.
* Removed: `defaultProps` for functions: ES6 default parameters can be used in place. Class components continue to support `defaultProps` since there is no ES6 alternative.
* Removed: `contextTypes` and `getChildContext`: Legacy Context for class components has been removed in favor of the `contextType` API.
* Removed: string refs: Any usage of string refs need to be migrated to ref callbacks.
* Removed: Module pattern factories: A rarely used pattern that can be migrated to regular functions.
* Removed: `React.createFactory`: Now that JSX is broadly supported, all `createFactory` usage can be migrated to JSX components.
* Removed: `react-test-renderer/shallow`: This has been a re-export of [react-shallow-renderer](https://github.com/enzymejs/react-shallow-renderer) since React 18\. If needed, you can continue to use the third-party package directly. We recommend using [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/) or [@testing-library/react-native](https://testing-library.com/docs/react-native-testing-library/intro) instead.
#### React DOM
* Removed: `react-dom/test-utils`: Weve moved `act` from `react-dom/test-utils` to react. All other utilities have been removed.
* Removed: `ReactDOM`.`render`, `ReactDOM`.`hydrate`: These have been removed in favor of the concurrent equivalents: `ReactDOM`.`createRoot` and `ReactDOM.hydrateRoot`.
* Removed: `unmountComponentAtNode`: Removed in favor of `root.unmount()`.
* Removed: `ReactDOM`.`findDOMNode`: You can replace `ReactDOM`.`findDOMNode` with DOM Refs.
### Notable Changes
#### React
* `<Context>` as a provider: You can now render `<Context>` as a provider instead of `<Context.Provider>`.
* Cleanup functions for refs: When the component unmounts, React will call the cleanup function returned from the ref callback.
* `useDeferredValue` initial value argument: When provided, `useDeferredValue` will return the initial value for the initial render of a component, then schedule a re-render in the background with the `deferredValue` returned.
* Support for Custom Elements: React 19 now passes all tests on [Custom Elements Everywhere](https://custom-elements-everywhere.com/).
* StrictMode changes: `useMemo` and `useCallback` will now reuse the memoized results from the first render, during the second render. Additionally, StrictMode will now double-invoke ref callback functions on initial mount.
* UMD builds removed: To load React 19 with a script tag, we recommend using an ESM-based CDN such as [esm.sh](http://esm.sh).
#### React DOM
* Diffs for hydration errors: In the case of a mismatch, React 19 logs a single error with a diff of the mismatched content.
* Compatibility with third-party scripts and extensions: React will now force a client re-render to fix up any mismatched content caused by elements inserted by third-party JS.
### TypeScript Changes
The most common changes can be codemodded with `npx types-react-codemod@latest preset-19 ./path-to-your-react-ts-files`.
* Removed deprecated TypeScript types:
* `ReactChild` (replacement: `React.ReactElement | number | string)`
* `ReactFragment` (replacement: `Iterable<React.ReactNode>`)
* `ReactNodeArray` (replacement: `ReadonlyArray<React.ReactNode>`)
* `ReactText` (replacement: `number | string`)
* `VoidFunctionComponent` (replacement: `FunctionComponent`)
* `VFC` (replacement: `FC`)
* Moved to `prop-types`: `Requireable`, `ValidationMap`, `Validator`, `WeakValidationMap`
* Moved to `create-react-class`: `ClassicComponentClass`, `ClassicComponent`, `ClassicElement`, `ComponentSpec`, `Mixin`, `ReactChildren`, `ReactHTML`, `ReactSVG`, `SFCFactory`
* Disallow implicit return in refs: refs can now accept cleanup functions. When you return something else, we cant tell if you intentionally returned something not meant to clean up or returned the wrong value. Implicit returns of anything but functions will now error.
* Require initial argument to `useRef`: The initial argument is now required to match `useState`, `createContext` etc
* Refs are mutable by default: Ref objects returned from `useRef()` are now always mutable instead of sometimes being immutable. This feature was too confusing for users and conflicted with legit cases where refs were managed by React and manually written to.
* Strict `ReactElement` typing: The props of React elements now default to `unknown` instead of `any` if the element is typed as `ReactElement`
* JSX namespace in TypeScript: The global `JSX` namespace is removed to improve interoperability with other libraries using JSX. Instead, the JSX namespace is available from the React package: `import { JSX } from 'react'`
* Better `useReducer` typings: Most `useReducer` usage should not require explicit type arguments.
For example,
```diff
-useReducer<React.Reducer<State, Action>>(reducer)
+useReducer(reducer)
```
or
```diff
-useReducer<React.Reducer<State, Action>>(reducer)
+useReducer<State, Action>(reducer)
```
### All Changes
#### React
* Add support for async Actions ([\#26621](https://github.com/facebook/react/pull/26621), [\#26726](https://github.com/facebook/react/pull/26726), [\#28078](https://github.com/facebook/react/pull/28078), [\#28097](https://github.com/facebook/react/pull/28097), [\#29226](https://github.com/facebook/react/pull/29226), [\#29618](https://github.com/facebook/react/pull/29618), [\#29670](https://github.com/facebook/react/pull/29670), [\#26716](https://github.com/facebook/react/pull/26716) by [@acdlite](https://github.com/acdlite) and [@sebmarkbage](https://github.com/sebmarkbage))
* Add `useActionState()` hook to update state based on the result of a Form Action ([\#27270](https://github.com/facebook/react/pull/27270), [\#27278](https://github.com/facebook/react/pull/27278), [\#27309](https://github.com/facebook/react/pull/27309), [\#27302](https://github.com/facebook/react/pull/27302), [\#27307](https://github.com/facebook/react/pull/27307), [\#27366](https://github.com/facebook/react/pull/27366), [\#27370](https://github.com/facebook/react/pull/27370), [\#27321](https://github.com/facebook/react/pull/27321), [\#27374](https://github.com/facebook/react/pull/27374), [\#27372](https://github.com/facebook/react/pull/27372), [\#27397](https://github.com/facebook/react/pull/27397), [\#27399](https://github.com/facebook/react/pull/27399), [\#27460](https://github.com/facebook/react/pull/27460), [\#28557](https://github.com/facebook/react/pull/28557), [\#27570](https://github.com/facebook/react/pull/27570), [\#27571](https://github.com/facebook/react/pull/27571), [\#28631](https://github.com/facebook/react/pull/28631), [\#28788](https://github.com/facebook/react/pull/28788), [\#29694](https://github.com/facebook/react/pull/29694), [\#29695](https://github.com/facebook/react/pull/29695), [\#29694](https://github.com/facebook/react/pull/29694), [\#29665](https://github.com/facebook/react/pull/29665), [\#28232](https://github.com/facebook/react/pull/28232), [\#28319](https://github.com/facebook/react/pull/28319) by [@acdlite](https://github.com/acdlite), [@eps1lon](https://github.com/eps1lon), and [@rickhanlonii](https://github.com/rickhanlonii))
* Add `use()` API to read resources in render ([\#25084](https://github.com/facebook/react/pull/25084), [\#25202](https://github.com/facebook/react/pull/25202), [\#25207](https://github.com/facebook/react/pull/25207), [\#25214](https://github.com/facebook/react/pull/25214), [\#25226](https://github.com/facebook/react/pull/25226), [\#25247](https://github.com/facebook/react/pull/25247), [\#25539](https://github.com/facebook/react/pull/25539), [\#25538](https://github.com/facebook/react/pull/25538), [\#25537](https://github.com/facebook/react/pull/25537), [\#25543](https://github.com/facebook/react/pull/25543), [\#25561](https://github.com/facebook/react/pull/25561), [\#25620](https://github.com/facebook/react/pull/25620), [\#25615](https://github.com/facebook/react/pull/25615), [\#25922](https://github.com/facebook/react/pull/25922), [\#25641](https://github.com/facebook/react/pull/25641), [\#25634](https://github.com/facebook/react/pull/25634), [\#26232](https://github.com/facebook/react/pull/26232), [\#26536](https://github.com/facebook/react/pull/26535), [\#26739](https://github.com/facebook/react/pull/26739), [\#28233](https://github.com/facebook/react/pull/28233) by [@acdlite](https://github.com/acdlite), [@MofeiZ](https://github.com/mofeiZ), [@sebmarkbage](https://github.com/sebmarkbage), [@sophiebits](https://github.com/sophiebits), [@eps1lon](https://github.com/eps1lon), and [@hansottowirtz](https://github.com/hansottowirtz))
* Add `useOptimistic()` hook to display mutated state optimistically during an async mutation ([\#26740](https://github.com/facebook/react/pull/26740), [\#26772](https://github.com/facebook/react/pull/26772), [\#27277](https://github.com/facebook/react/pull/27277), [\#27453](https://github.com/facebook/react/pull/27453), [\#27454](https://github.com/facebook/react/pull/27454), [\#27936](https://github.com/facebook/react/pull/27936) by [@acdlite](https://github.com/acdlite))
* Added an `initialValue` argument to `useDeferredValue()` hook ([\#27500](https://github.com/facebook/react/pull/27500), [\#27509](https://github.com/facebook/react/pull/27509), [\#27512](https://github.com/facebook/react/pull/27512), [\#27888](https://github.com/facebook/react/pull/27888), [\#27550](https://github.com/facebook/react/pull/27550) by [@acdlite](https://github.com/acdlite))
* Support refs as props, warn on `element.ref` access ([\#28348](https://github.com/facebook/react/pull/28348), [\#28464](https://github.com/facebook/react/pull/28464), [\#28731](https://github.com/facebook/react/pull/28731) by [@acdlite](https://github.com/acdlite))
* Support Custom Elements ([\#22184](https://github.com/facebook/react/pull/22184), [\#26524](https://github.com/facebook/react/pull/26524), [\#26523](https://github.com/facebook/react/pull/26523), [\#27511](https://github.com/facebook/react/pull/27511), [\#24541](https://github.com/facebook/react/pull/24541) by [@josepharhar](https://github.com/josepharhar), [@sebmarkbage](https://github.com/sebmarkbage), [@gnoff](https://github.com/gnoff) and [@eps1lon](https://github.com/eps1lon))
* Add ref cleanup function ([\#25686](https://github.com/facebook/react/pull/25686), [\#28883](https://github.com/facebook/react/pull/28883), [\#28910](https://github.com/facebook/react/pull/28910) by [@sammy-SC](https://github.com/sammy-SC), [@jackpope](https://github.com/jackpope), and [@kassens](https://github.com/kassens))
* Sibling pre-rendering replaced by sibling pre-warming ([\#26380](https://github.com/facebook/react/pull/26380), [\#26549](https://github.com/facebook/react/pull/26549), [\#30761](https://github.com/facebook/react/pull/30761), [\#30800](https://github.com/facebook/react/pull/30800), [\#30762](https://github.com/facebook/react/pull/30762), [\#30879](https://github.com/facebook/react/pull/30879), [\#30934](https://github.com/facebook/react/pull/30934), [\#30952](https://github.com/facebook/react/pull/30952), [\#31056](https://github.com/facebook/react/pull/31056), [\#31452](https://github.com/facebook/react/pull/31452) by [@sammy-SC](https://github.com/sammy-SC), [@acdlite](https://github.com/acdlite), [@gnoff](https://github.com/gnoff), [@jackpope](https://github.com/jackpope), [@rickhanlonii](https://github.com/rickhanlonii))
* Dont rethrow errors at the root ([\#28627](https://github.com/facebook/react/pull/28627), [\#28641](https://github.com/facebook/react/pull/28641) by [@sebmarkbage](https://github.com/sebmarkbage))
* Batch sync discrete, continuous, and default lanes ([\#25700](https://github.com/facebook/react/pull/25700) by [@tyao1](https://github.com/tyao1))
* Switch `<Context>` to mean `<Context.Provider>` ([\#28226](https://github.com/facebook/react/pull/28226) by [@gaearon](https://github.com/gaearon))
* Changes to *StrictMode*
* Handle `info`, `group`, and `groupCollapsed` in *StrictMode* logging ([\#25172](https://github.com/facebook/react/pull/25172) by [@timneutkens](https://github.com/timneutkens))
* Refs are now attached/detached/attached in *StrictMode* ([\#25049](https://github.com/facebook/react/pull/25049) by [@sammy-SC](https://github.com/sammy-SC))
* Fix `useSyncExternalStore()` hydration in *StrictMode* ([\#26791](https://github.com/facebook/react/pull/26791) by [@sophiebits](https://github.com/sophiebits))
* Always trigger `componentWillUnmount()` in *StrictMode* ([\#26842](https://github.com/facebook/react/pull/26842) by [@tyao1](https://github.com/tyao1))
* Restore double invoking `useState()` and `useReducer()` initializer functions in *StrictMode* ([\#28248](https://github.com/facebook/react/pull/28248) by [@eps1lon](https://github.com/eps1lon))
* Reuse memoized result from first pass ([\#25583](https://github.com/facebook/react/pull/25583) by [@acdlite](https://github.com/acdlite))
* Fix `useId()` in *StrictMode* ([\#25713](https://github.com/facebook/react/pull/25713) by [@gnoff](https://github.com/gnoff))
* Add component name to *StrictMode* error messages ([\#25718](https://github.com/facebook/react/pull/25718) by [@sammy-SC](https://github.com/sammy-SC))
* Add support for rendering BigInt ([\#24580](https://github.com/facebook/react/pull/24580) by [@eps1lon](https://github.com/eps1lon))
* `act()` no longer checks `shouldYield` which can be inaccurate in test environments ([\#26317](https://github.com/facebook/react/pull/26317) by [@acdlite](https://github.com/acdlite))
* Warn when keys are spread with props ([\#25697](https://github.com/facebook/react/pull/25697), [\#26080](https://github.com/facebook/react/pull/26080) by [@sebmarkbage](https://github.com/sebmarkbage) and [@kassens](https://github.com/kassens))
* Generate sourcemaps for production build artifacts ([\#26446](https://github.com/facebook/react/pull/26446) by [@markerikson](https://github.com/markerikson))
* Improve stack diffing algorithm ([\#27132](https://github.com/facebook/react/pull/27132) by [@KarimP](https://github.com/KarimP))
* Suspense throttling lowered from 500ms to 300ms ([\#26803](https://github.com/facebook/react/pull/26803) by [@acdlite](https://github.com/acdlite))
* Lazily propagate context changes ([\#20890](https://github.com/facebook/react/pull/20890) by [@acdlite](https://github.com/acdlite) and [@gnoff](https://github.com/gnoff))
* Immediately rerender pinged fiber ([\#25074](https://github.com/facebook/react/pull/25074) by [@acdlite](https://github.com/acdlite))
* Move update scheduling to microtask ([\#26512](https://github.com/facebook/react/pull/26512) by [@acdlite](https://github.com/acdlite))
* Consistently apply throttled retries ([\#26611](https://github.com/facebook/react/pull/26611), [\#26802](https://github.com/facebook/react/pull/26802) by [@acdlite](https://github.com/acdlite))
* Suspend Thenable/Lazy if it's used in React.Children ([\#28284](https://github.com/facebook/react/pull/28284) by [@sebmarkbage](https://github.com/sebmarkbage))
* Detect infinite update loops caused by render phase updates ([\#26625](https://github.com/facebook/react/pull/26625) by [@acdlite](https://github.com/acdlite))
* Update conditional hooks warning ([\#29626](https://github.com/facebook/react/pull/29626) by [@sophiebits](https://github.com/sophiebits))
* Update error URLs to go to new docs ([\#27240](https://github.com/facebook/react/pull/27240) by [@rickhanlonii](https://github.com/rickhanlonii))
* Rename the `react.element` symbol to `react.transitional.element` ([\#28813](https://github.com/facebook/react/pull/28813) by [@sebmarkbage](https://github.com/sebmarkbage))
* Fix crash when suspending in shell during `useSyncExternalStore()` re-render ([\#27199](https://github.com/facebook/react/pull/27199) by [@acdlite](https://github.com/acdlite))
* Fix incorrect “detected multiple renderers" error in tests ([\#22797](https://github.com/facebook/react/pull/22797) by [@eps1lon](https://github.com/eps1lon))
* Fix bug where effect cleanup may be called twice after bailout ([\#26561](https://github.com/facebook/react/pull/26561) by [@acdlite](https://github.com/acdlite))
* Fix suspending in shell during discrete update ([\#25495](https://github.com/facebook/react/pull/25495) by [@acdlite](https://github.com/acdlite))
* Fix memory leak after repeated setState bailouts ([\#25309](https://github.com/facebook/react/pull/25309) by [@acdlite](https://github.com/acdlite))
* Fix `useSyncExternalStore()` dropped update when state is dispatched in render phase ([\#25578](https://github.com/facebook/react/pull/25578) by [@pandaiolo](https://github.com/pandaiolo))
* Fix logging when rendering a lazy fragment ([\#30372](https://github.com/facebook/react/pull/30372) by [@tom-sherman](https://github.com/tom-sherman))
* Remove string refs ([\#25383](https://github.com/facebook/react/pull/25383), [\#28322](https://github.com/facebook/react/pull/28322) by [@eps1lon](https://github.com/eps1lon) and [@acdlite](https://github.com/acdlite))
* Remove Legacy Context (\#30319 by [@kassens](https://github.com/kassens))
* Remove `RefreshRuntime.findAffectedHostInstances` ([\#30538](https://github.com/facebook/react/pull/30538) by [@gaearon](https://github.com/gaearon))
* Remove client caching from `cache()` API ([\#27977](https://github.com/facebook/react/pull/27977), [\#28250](https://github.com/facebook/react/pull/28250) by [@acdlite](https://github.com/acdlite) and [@gnoff](https://github.com/gnoff))
* Remove `propTypes` ([\#28324](https://github.com/facebook/react/pull/28324), [\#28326](https://github.com/facebook/react/pull/28326) by [@gaearon](https://github.com/gaearon))
* Remove `defaultProps` support, except for classes ([\#28733](https://github.com/facebook/react/pull/28733) by [@acdlite](https://github.com/acdlite))
* Remove UMD builds ([\#28735](https://github.com/facebook/react/pull/28735) by [@gnoff](https://github.com/gnoff))
* Remove delay for non-transition updates ([\#26597](https://github.com/facebook/react/pull/26597) by [@acdlite](https://github.com/acdlite))
* Remove `createFactory` ([\#27798](https://github.com/facebook/react/pull/27798) by [@kassens](https://github.com/kassens))
#### React DOM
* Adds Form Actions to handle form submission ([\#26379](https://github.com/facebook/react/pull/26379), [\#26674](https://github.com/facebook/react/pull/26674), [\#26689](https://github.com/facebook/react/pull/26689), [\#26708](https://github.com/facebook/react/pull/26708), [\#26714](https://github.com/facebook/react/pull/26714), [\#26735](https://github.com/facebook/react/pull/26735), [\#26846](https://github.com/facebook/react/pull/26846), [\#27358](https://github.com/facebook/react/pull/27358), [\#28056](https://github.com/facebook/react/pull/28056) by [@sebmarkbage](https://github.com/sebmarkbage), [@acdlite](https://github.com/acdlite), and [@jupapios](https://github.com/jupapios))
* Add `useFormStatus()` hook to provide status information of the last form submission ([\#26719](https://github.com/facebook/react/pull/26719), [\#26722](https://github.com/facebook/react/pull/26722), [\#26788](https://github.com/facebook/react/pull/26788), [\#29019](https://github.com/facebook/react/pull/29019), [\#28728](https://github.com/facebook/react/pull/28728), [\#28413](https://github.com/facebook/react/pull/28413) by [@acdlite](https://github.com/acdlite) and [@eps1lon](https://github.com/eps1lon))
* Support for Document Metadata. Adds `preinit`, `preinitModule`, `preconnect`, `prefetchDNS`, `preload`, and `preloadModule` APIs.
* [\#25060](https://github.com/facebook/react/pull/25060), [\#25243](https://github.com/facebook/react/pull/25243), [\#25388](https://github.com/facebook/react/pull/25388), [\#25432](https://github.com/facebook/react/pull/25432), [\#25436](https://github.com/facebook/react/pull/25436), [\#25426](https://github.com/facebook/react/pull/25426), [\#25500](https://github.com/facebook/react/pull/25500), [\#25480](https://github.com/facebook/react/pull/25480), [\#25508](https://github.com/facebook/react/pull/25508), [\#25515](https://github.com/facebook/react/pull/25515), [\#25514](https://github.com/facebook/react/pull/25514), [\#25532](https://github.com/facebook/react/pull/25532), [\#25536](https://github.com/facebook/react/pull/25536), [\#25534](https://github.com/facebook/react/pull/25534), [\#25546](https://github.com/facebook/react/pull/25546), [\#25559](https://github.com/facebook/react/pull/25559), [\#25569](https://github.com/facebook/react/pull/25569), [\#25599](https://github.com/facebook/react/pull/25599), [\#25689](https://github.com/facebook/react/pull/25689), [\#26106](https://github.com/facebook/react/pull/26106), [\#26152](https://github.com/facebook/react/pull/26152), [\#26239](https://github.com/facebook/react/pull/26239), [\#26237](https://github.com/facebook/react/pull/26237), [\#26280](https://github.com/facebook/react/pull/26280), [\#26154](https://github.com/facebook/react/pull/26154), [\#26256](https://github.com/facebook/react/pull/26256), [\#26353](https://github.com/facebook/react/pull/26353), [\#26427](https://github.com/facebook/react/pull/26427), [\#26450](https://github.com/facebook/react/pull/26450), [\#26502](https://github.com/facebook/react/pull/26502), [\#26514](https://github.com/facebook/react/pull/26514), [\#26531](https://github.com/facebook/react/pull/26531), [\#26532](https://github.com/facebook/react/pull/26532), [\#26557](https://github.com/facebook/react/pull/26557), [\#26871](https://github.com/facebook/react/pull/26871), [\#26881](https://github.com/facebook/react/pull/26881), [\#26877](https://github.com/facebook/react/pull/26877), [\#26873](https://github.com/facebook/react/pull/26873), [\#26880](https://github.com/facebook/react/pull/26880), [\#26942](https://github.com/facebook/react/pull/26942), [\#26938](https://github.com/facebook/react/pull/26938), [\#26940](https://github.com/facebook/react/pull/26940), [\#26939](https://github.com/facebook/react/pull/26939), [\#27030](https://github.com/facebook/react/pull/27030), [\#27201](https://github.com/facebook/react/pull/27201), [\#27212](https://github.com/facebook/react/pull/27212), [\#27217](https://github.com/facebook/react/pull/27217), [\#27218](https://github.com/facebook/react/pull/27218), [\#27220](https://github.com/facebook/react/pull/27220), [\#27224](https://github.com/facebook/react/pull/27224), [\#27223](https://github.com/facebook/react/pull/27223), [\#27269](https://github.com/facebook/react/pull/27269), [\#27260](https://github.com/facebook/react/pull/27260), [\#27347](https://github.com/facebook/react/pull/27347), [\#27346](https://github.com/facebook/react/pull/27346), [\#27361](https://github.com/facebook/react/pull/27361), [\#27400](https://github.com/facebook/react/pull/27400), [\#27541](https://github.com/facebook/react/pull/27541), [\#27610](https://github.com/facebook/react/pull/27610), [\#28110](https://github.com/facebook/react/pull/28110), [\#29693](https://github.com/facebook/react/pull/29693), [\#29732](https://github.com/facebook/react/pull/29732), [\#29811](https://github.com/facebook/react/pull/29811), [\#27586](https://github.com/facebook/react/pull/27586), [\#28069](https://github.com/facebook/react/pull/28069) by [@gnoff](https://github.com/gnoff), [@sebmarkbage](https://github.com/sebmarkbage), [@acdlite](https://github.com/acdlite), [@kassens](https://github.com/kassens), [@sokra](https://github.com/sokra), [@sweetliquid](https://github.com/sweetliquid)
* Add `fetchPriority` to `<img>` and `<link>` ([\#25927](https://github.com/facebook/react/pull/25927) by [@styfle](https://github.com/styfle))
* Add support for SVG `transformOrigin` prop ([\#26130](https://github.com/facebook/react/pull/26130) by [@arav-ind](https://github.com/arav-ind))
* Add support for `onScrollEnd` event ([\#26789](https://github.com/facebook/react/pull/26789) by [@devongovett](https://github.com/devongovett))
* Allow `<hr>` as child of `<select>` ([\#27632](https://github.com/facebook/react/pull/27632) by [@SouSingh](https://github.com/SouSingh))
* Add support for Popover API ([\#27981](https://github.com/facebook/react/pull/27981) by [@eps1lon](https://github.com/eps1lon))
* Add support for `inert` ([\#24730](https://github.com/facebook/react/pull/24730) by [@eps1lon](https://github.com/eps1lon))
* Add support for `imageSizes` and `imageSrcSet` ([\#22550](https://github.com/facebook/react/pull/22550) by [@eps1lon](https://github.com/eps1lon))
* Synchronously flush transitions in popstate events ([\#26025](https://github.com/facebook/react/pull/26025), [\#27559](https://github.com/facebook/react/pull/27559), [\#27505](https://github.com/facebook/react/pull/27505), [\#30759](https://github.com/facebook/react/pull/30759) by [@tyao1](https://github.com/tyao1) and [@acdlite](https://github.com/acdlite))
* `flushSync` exhausts queue even if something throws ([\#26366](https://github.com/facebook/react/pull/26366) by [@acdlite](https://github.com/acdlite))
* Throw error if `react` and `react-dom` versions dont match ([\#29236](https://github.com/facebook/react/pull/29236) by [@acdlite](https://github.com/acdlite))
* Ensure `srcset` and `src` are assigned last on `<img>` instances ([\#30340](https://github.com/facebook/react/pull/30340) by [@gnoff](https://github.com/gnoff))
* Javascript URLs are replaced with functions that throw errors ([\#26507](https://github.com/facebook/react/pull/26507), [\#29808](https://github.com/facebook/react/pull/29808) by [@sebmarkbage](https://github.com/sebmarkbage) and [@kassens](https://github.com/kassens))
* Treat toggle and beforetoggle as discrete events ([\#29176](https://github.com/facebook/react/pull/29176) by [@eps1lon](https://github.com/eps1lon))
* Filter out empty `src` and `href` attributes (unless for `<a href=”” />`) ([\#18513](https://github.com/facebook/react/pull/18513), [\#28124](https://github.com/facebook/react/pull/28124) by [@bvaughn](https://github.com/bvaughn) and [@eps1lon](https://github.com/eps1lon))
* Fix unitless `scale` style property ([\#25601](https://github.com/facebook/react/pull/25601) by [@JonnyBurger](https://github.com/JonnyBurger))
* Fix `onChange` error message for controlled `<select>` ([\#27740](https://github.com/facebook/react/pull/27740) by [@Biki-das](https://github.com/Biki-das))
* Fix focus restore in child windows after element reorder ([\#30951](https://github.com/facebook/react/pull/30951) by [@ling1726](https://github.com/ling1726))
* Remove `render`, `hydrate`, `findDOMNode`, `unmountComponentAtNode`, `unstable_createEventHandle`, `unstable_renderSubtreeIntoContainer`, and `unstable_runWithPriority`. Move `createRoot` and `hydrateRoot` to `react-dom/client`. ([\#28271](https://github.com/facebook/react/pull/28271) by [@gnoff](https://github.com/gnoff))
* Remove `test-utils` ([\#28541](https://github.com/facebook/react/pull/28541) by [@eps1lon](https://github.com/eps1lon))
* Remove `unstable_flushControlled` ([\#26397](https://github.com/facebook/react/pull/26397) by [@kassens](https://github.com/kassens))
* Remove legacy mode ([\#28468](https://github.com/facebook/react/pull/28468) by [@gnoff](https://github.com/gnoff))
* Remove `renderToStaticNodeStream()` ([\#28873](https://github.com/facebook/react/pull/28873) by @gnoff)
* Remove `unstable_renderSubtreeIntoContainer` ([\#29771](https://github.com/facebook/react/pull/29771) by [@kassens](https://github.com/kassens))
#### React DOM Server
* Stable release of React Server Components ([Many, many PRs](https://github.com/facebook/react/pulls?q=is%3Apr+is%3Aclosed+%5BFlight%5D+in%3Atitle+created%3A%3C2024-12-01+) by [@sebmarkbage](https://github.com/sebmarkbage), [@acdlite](https://github.com/acdlite), [@gnoff](https://github.com/gnoff), [@sammy-SC](https://github.com/sammy-SC), [@gaearon](https://github.com/gaearon), [@sophiebits](https://github.com/sophiebits), [@unstubbable](https://github.com/unstubbable), [@lubieowoce](https://github.com/lubieowoce))
* Support Server Actions ([\#26124](https://github.com/facebook/react/pull/26124), [\#26632](https://github.com/facebook/react/pull/26632), [\#27459](https://github.com/facebook/react/pull/27459) by [@sebmarkbage](https://github.com/sebmarkbage) and [@acdlite](https://github.com/acdlite))
* Changes to SSR
* Add external runtime which bootstraps hydration on the client for binary transparency ([\#25437](https://github.com/facebook/react/pull/25437), [\#26169](https://github.com/facebook/react/pull/26169), [\#25499](https://github.com/facebook/react/pull/25499) by [@MofeiZ](https://github.com/mofeiZ) and [@acdlite](https://github.com/acdlite))
* Support subresource integrity for `bootstrapScripts` and `bootstrapModules` ([\#25104](https://github.com/facebook/react/pull/25104) by [@gnoff](https://github.com/gnoff))
* Fix null bytes written at text chunk boundaries ([\#26228](https://github.com/facebook/react/pull/26228) by [@sophiebits](https://github.com/sophiebits))
* Fix logic around attribute serialization ([\#26526](https://github.com/facebook/react/pull/26526) by [@gnoff](https://github.com/gnoff))
* Fix precomputed chunk cleared on Node 18 ([\#25645](https://github.com/facebook/react/pull/25645) by [@feedthejim](https://github.com/feedthejim))
* Optimize end tag chunks ([\#27522](https://github.com/facebook/react/pull/27522) by [@yujunjung](https://github.com/yujunjung))
* Gracefully handle suspending in DOM configs ([\#26768](https://github.com/facebook/react/pull/26768) by [@sebmarkbage](https://github.com/sebmarkbage))
* Check for nullish values on ReactCustomFormAction ([\#26770](https://github.com/facebook/react/pull/26770) by [@sebmarkbage](https://github.com/sebmarkbage))
* Preload `bootstrapModules`, `bootstrapScripts`, and update priority queue ([\#26754](https://github.com/facebook/react/pull/26754), [\#26753](https://github.com/facebook/react/pull/26753), [\#27190](https://github.com/facebook/react/pull/27190), [\#27189](https://github.com/facebook/react/pull/27189) by [@gnoff](https://github.com/gnoff))
* Client render the nearest child or parent suspense boundary if replay errors or is aborted ([\#27386](https://github.com/facebook/react/pull/27386) by [@sebmarkbage](https://github.com/sebmarkbage))
* Don't bail out of flushing if we still have pending root tasks ([\#27385](https://github.com/facebook/react/pull/27385) by [@sebmarkbage](https://github.com/sebmarkbage))
* Ensure Resumable State is Serializable ([\#27388](https://github.com/facebook/react/pull/27388) by [@sebmarkbage](https://github.com/sebmarkbage))
* Remove extra render pass when reverting to client render ([\#26445](https://github.com/facebook/react/pull/26445) by [@acdlite](https://github.com/acdlite))
* Fix unwinding context during selective hydration ([\#25876](https://github.com/facebook/react/pull/25876) by [@tyao1](https://github.com/tyao1))
* Stop flowing and then abort if a stream is cancelled ([\#27405](https://github.com/facebook/react/pull/27405) by [@sebmarkbage](https://github.com/sebmarkbage))
* Pass cancellation reason to abort ([\#27536](https://github.com/facebook/react/pull/27536) by [@sebmarkbage](https://github.com/sebmarkbage))
* Add `onHeaders` entrypoint option ([\#27641](https://github.com/facebook/react/pull/27641), [\#27712](https://github.com/facebook/react/pull/27712) by [@gnoff](https://github.com/gnoff))
* Escape `<style>` and `<script>` textContent to enable rendering inner content without dangerouslySetInnerHTML ([\#28870](https://github.com/facebook/react/pull/28870), [\#28871](https://github.com/facebook/react/pull/28871) by [@gnoff](https://github.com/gnoff))
* Fallback to client replaying actions for Blob serialization ([\#28987](https://github.com/facebook/react/pull/28987) by [@sebmarkbage](https://github.com/sebmarkbage))
* Render Suspense fallback if boundary contains new stylesheet during sync update ([\#28965](https://github.com/facebook/react/pull/28965) by [@gnoff](https://github.com/gnoff))
* Fix header length tracking (\#30327 by [@gnoff](https://github.com/gnoff))
* Use `srcset` to trigger load event on mount (\#30351 by [@gnoff](https://github.com/gnoff))
* Don't perform work when closing stream (\#30497 by [@gnoff](https://github.com/gnoff))
* Allow aborting during render (\#30488, [\#30730](https://github.com/facebook/react/pull/30730) by [@gnoff](https://github.com/gnoff))
* Start initial work immediately (\#31079 by [@gnoff](https://github.com/gnoff))
* A transition flowing into a dehydrated boundary no longer suspends when showing fallback ([\#27230](https://github.com/facebook/react/pull/27230) by [@acdlite](https://github.com/acdlite))
* Fix selective hydration triggers false update loop error ([\#27439](https://github.com/facebook/react/pull/27439) by [@acdlite](https://github.com/acdlite))
* Warn for Child Iterator of all types but allow Generator Components ([\#28853](https://github.com/facebook/react/pull/28853) by [@sebmarkbage](https://github.com/sebmarkbage))
* Include regular stack trace in serialized errors ([\#28684](https://github.com/facebook/react/pull/28684), [\#28738](https://github.com/facebook/react/pull/28738) by [@sebmarkbage](https://github.com/sebmarkbage))
* Aborting early no longer infinitely suspends ([\#24751](https://github.com/facebook/react/pull/24751) by [@sebmarkbage](https://github.com/sebmarkbage))
* Fix hydration warning suppression in text comparisons ([\#24784](https://github.com/facebook/react/pull/24784) by [@gnoff](https://github.com/gnoff))
* Changes to error handling in SSR
* Add diffs to hydration warnings ([\#28502](https://github.com/facebook/react/pull/28502), [\#28512](https://github.com/facebook/react/pull/28512) by [@sebmarkbage](https://github.com/sebmarkbage))
* Make Error creation lazy ([\#24728](https://github.com/facebook/react/pull/24728) by [@sebmarkbage](https://github.com/sebmarkbage))
* Remove recoverable error when a sync update flows into a dehydrated boundary ([\#25692](https://github.com/facebook/react/pull/25692) by [@sebmarkbage](https://github.com/sebmarkbage))
* Don't "fix up" mismatched text content with suppressedHydrationWarning ([\#26391](https://github.com/facebook/react/pull/26391) by [@sebmarkbage](https://github.com/sebmarkbage))
* Fix component stacks in errors ([\#27456](https://github.com/facebook/react/pull/27456) by [@sebmarkbage](https://github.com/sebmarkbage))
* Add component stacks to `onError` ([\#27761](https://github.com/facebook/react/pull/27761), [\#27850](https://github.com/facebook/react/pull/27850) by [@gnoff](https://github.com/gnoff) and [@sebmarkbage](https://github.com/sebmarkbage))
* Throw hydration mismatch errors once ([\#28502](https://github.com/facebook/react/pull/28502) by [@sebmarkbage](https://github.com/sebmarkbage))
* Add Bun streaming server renderer ([\#25597](https://github.com/facebook/react/pull/25597) by [@colinhacks](https://github.com/colinhacks))
* Add nonce support to bootstrap scripts ([\#26738](https://github.com/facebook/react/pull/26738) by [@danieltott](https://github.com/danieltott))
* Add `crossorigin` support to bootstrap scripts ([\#26844](https://github.com/facebook/react/pull/26844) by [@HenriqueLimas](https://github.com/HenriqueLimas))
* Support `nonce` and `fetchpriority` in preload links ([\#26826](https://github.com/facebook/react/pull/26826) by [@liuyenwei](https://github.com/liuyenwei))
* Add `referrerPolicy` to `ReactDOM.preload()` ([\#27096](https://github.com/facebook/react/pull/27096) by [@styfle](https://github.com/styfle))
* Add server condition for `react/jsx-dev-runtime` ([\#28921](https://github.com/facebook/react/pull/28921) by [@himself65](https://github.com/himself65))
* Export version ([\#29596](https://github.com/facebook/react/pull/29596) by [@unstubbable](https://github.com/unstubbable))
* Rename the secret export of Client and Server internals ([\#28786](https://github.com/facebook/react/pull/28786), [\#28789](https://github.com/facebook/react/pull/28789) by [@sebmarkbage](https://github.com/sebmarkbage))
* Remove layout effect warning on server ([\#26395](https://github.com/facebook/react/pull/26395) by [@rickhanlonii](https://github.com/rickhanlonii))
* Remove `errorInfo.digest` from `onRecoverableError` ([\#28222](https://github.com/facebook/react/pull/28222) by [@gnoff](https://github.com/gnoff))
#### ReactTestRenderer
* Add deprecation error to `react-test-renderer` on web ([\#27903](https://github.com/facebook/react/pull/27903), [\#28904](https://github.com/facebook/react/pull/28904) by [@jackpope](https://github.com/jackpope) and [@acdlite](https://github.com/acdlite))
* Render with ConcurrentRoot on web ([\#28498](https://github.com/facebook/react/pull/28498) by [@jackpope](https://github.com/jackpope))
* Remove `react-test-renderer/shallow` export ([\#25475](https://github.com/facebook/react/pull/25475), [\#28497](https://github.com/facebook/react/pull/28497) by [@sebmarkbage](https://github.com/sebmarkbage) and [@jackpope](https://github.com/jackpope))
#### React Reconciler
* Enable suspending commits without blocking render ([\#26398](https://github.com/facebook/react/pull/26398), [\#26427](https://github.com/facebook/react/pull/26427) by [@acdlite](https://github.com/acdlite))
* Remove `prepareUpdate` ([\#26583](https://github.com/facebook/react/pull/26583), [\#27409](http://github.com/facebook/react/pull/27409) by [@sebmarkbage](https://github.com/sebmarkbage) and [@sophiebits](https://github.com/sophiebits))
#### React-Is
* Enable tree shaking ([\#27701](https://github.com/facebook/react/pull/27701) by [@markerikson](https://github.com/markerikson))
* Remove `isConcurrentMode` and `isAsyncMode` methods ([\#28224](https://github.com/facebook/react/pull/28224) by @gaearon)
#### useSyncExternalStore
* Remove React internals access ([\#29868](https://github.com/facebook/react/pull/29868) by [@phryneas](https://github.com/phryneas))
* Fix stale selectors keeping previous store references ([\#25969](https://github.com/facebook/react/pull/25968) by [@jellevoost](https://github.com/jellevoost))
## 18.3.1 (April 26, 2024)
- Export `act` from `react` [f1338f](https://github.com/facebook/react/commit/f1338f8080abd1386454a10bbf93d67bfe37ce85)

View File

@@ -26,6 +26,13 @@ $ npm run dev
$ yarn
```
## Testing
```sh
# Install playwright browser binaries
$ npx playwright install --with-deps
# Run tests
$ yarn test
```
## Deployment
This project has been deployed using Vercel. Vercel does the exact same thing as we would

View File

@@ -0,0 +1,13 @@
function TestComponent(t0) {
const $ = _c(2);
const { x } = t0;
let t1;
if ($[0] !== x) {
t1 = <Button>{x}</Button>;
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}

View File

@@ -0,0 +1,11 @@
function MyApp() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <div>Hello World</div>;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}

View File

@@ -1,11 +0,0 @@
function MyApp() {
  const $ = _c(1);
  let t0;
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
    t0 = <div>Hello World</div>;
    $[0] = t0;
  } else {
    t0 = $[0];
  }
  return t0;
}

View File

@@ -0,0 +1,14 @@
function TestComponent(t0) {
"use memo";
const $ = _c(2);
const { x } = t0;
let t1;
if ($[0] !== x) {
t1 = <Button>{x}</Button>;
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}

View File

@@ -0,0 +1,13 @@
function TestComponent(t0) {
const $ = _c(2);
const { x } = t0;
let t1;
if ($[0] !== x) {
t1 = <Button>{x}</Button>;
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}

View File

@@ -0,0 +1,3 @@
function TestComponent({ x }) {
return <Button>{x}</Button>;
}

View File

@@ -0,0 +1,28 @@
function TestComponent(t0) {
"use memo";
const $ = _c(2);
const { x } = t0;
let t1;
if ($[0] !== x) {
t1 = <Button>{x}</Button>;
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
function anonymous_1(t0) {
"use memo";
const $ = _c(2);
const { x } = t0;
let t1;
if ($[0] !== x) {
t1 = <Button>{x}</Button>;
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}

View File

@@ -0,0 +1,8 @@
function anonymous_1() {
"use no memo";
return <Button>{x}</Button>;
}
function anonymous_3({ x }) {
"use no memo";
return <Button>{x}</Button>;
}

View File

@@ -1,13 +0,0 @@
function TestComponent(t0) {
  const $ = _c(2);
  const { x } = t0;
  let t1;
  if ($[0] !== x) {
    t1 = <Button>{x}</Button>;
    $[0] = x;
    $[1] = t1;
  } else {
    t1 = $[1];
  }
  return t1;
}

View File

@@ -7,43 +7,137 @@
import {expect, test} from '@playwright/test';
import {encodeStore, type Store} from '../../lib/stores';
import {format} from 'prettier';
const STORE: Store = {
source: `export default function TestComponent({ x }) {
function print(data: Array<string>): Promise<string> {
return format(data.join(''), {parser: 'babel'});
}
const DIRECTIVE_TEST_CASES = [
{
name: 'module-scope-use-memo',
input: `
'use memo';
export default function TestComponent({ x }) {
return <Button>{x}</Button>;
}`,
},
{
name: 'module-scope-use-no-memo',
input: `
'use no memo';
export default function TestComponent({ x }) {
return <Button>{x}</Button>;
}`,
},
{
name: 'use-memo',
input: `
function TestComponent({ x }) {
'use memo';
return <Button>{x}</Button>;
}
`,
const TestComponent2 = ({ x }) => {
'use memo';
return <Button>{x}</Button>;
};`,
},
{
name: 'use-no-memo',
input: `
const TestComponent = function() {
'use no memo';
return <Button>{x}</Button>;
};
const HASH = encodeStore(STORE);
const TestComponent2 = ({ x }) => {
'use no memo';
return <Button>{x}</Button>;
};`,
},
{
name: 'function-scope-beats-module-scope',
input: `
'use no memo';
function TestComponent({ x }) {
'use memo';
return <Button>{x}</Button>;
}`,
},
];
function concat(data: Array<string>): string {
return data.join('');
}
test('editor should compile successfully', async ({page}) => {
await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'});
test('editor should open successfully', async ({page}) => {
await page.goto(`/`, {waitUntil: 'networkidle'});
await page.screenshot({
fullPage: true,
path: 'test-results/00-on-networkidle.png',
path: 'test-results/00-fresh-page.png',
});
});
test('editor should compile from hash successfully', async ({page}) => {
const store: Store = {
source: `export default function TestComponent({ x }) {
return <Button>{x}</Button>;
}
`,
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
// User input from hash compiles
await page.screenshot({
fullPage: true,
path: 'test-results/01-show-js-before.png',
path: 'test-results/01-compiles-from-hash.png',
});
const userInput =
const text =
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
expect(concat(userInput)).toMatchSnapshot('user-input.txt');
const output = await print(text);
expect(output).not.toEqual('');
expect(output).toMatchSnapshot('01-user-output.txt');
});
test('reset button works', async ({page}) => {
const store: Store = {
source: `export default function TestComponent({ x }) {
return <Button>{x}</Button>;
}
`,
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
// Reset button works
page.on('dialog', dialog => dialog.accept());
await page.getByRole('button', {name: 'Reset'}).click();
await page.screenshot({
fullPage: true,
path: 'test-results/02-show-js-after.png',
path: 'test-results/02-reset-button-works.png',
});
const defaultInput =
const text =
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
expect(concat(defaultInput)).toMatchSnapshot('default-input.txt');
const output = await print(text);
expect(output).not.toEqual('');
expect(output).toMatchSnapshot('02-default-output.txt');
});
DIRECTIVE_TEST_CASES.forEach((t, idx) =>
test(`directives work: ${t.name}`, async ({page}) => {
const store: Store = {
source: t.input,
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
await page.screenshot({
fullPage: true,
path: `test-results/03-0${idx}-${t.name}.png`,
});
const text =
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
const output = await print(text);
expect(output).not.toEqual('');
expect(output).toMatchSnapshot(`${t.name}-output.txt`);
}),
);

View File

@@ -18,6 +18,8 @@ import {
ValueKind,
runPlayground,
type Hook,
findDirectiveDisablingMemoization,
findDirectiveEnablingMemoization,
} from 'babel-plugin-react-compiler/src';
import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment';
import clsx from 'clsx';
@@ -43,6 +45,25 @@ import {
import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR';
import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction';
type FunctionLike =
| NodePath<t.FunctionDeclaration>
| NodePath<t.ArrowFunctionExpression>
| NodePath<t.FunctionExpression>;
enum MemoizeDirectiveState {
Enabled = 'Enabled',
Disabled = 'Disabled',
Undefined = 'Undefined',
}
const MEMOIZE_ENABLED_OR_UNDEFINED_STATES = new Set([
MemoizeDirectiveState.Enabled,
MemoizeDirectiveState.Undefined,
]);
const MEMOIZE_ENABLED_OR_DISABLED_STATES = new Set([
MemoizeDirectiveState.Enabled,
MemoizeDirectiveState.Disabled,
]);
function parseInput(input: string, language: 'flow' | 'typescript'): any {
// Extract the first line to quickly check for custom test directives
if (language === 'flow') {
@@ -63,29 +84,36 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any {
function parseFunctions(
source: string,
language: 'flow' | 'typescript',
): Array<
| NodePath<t.FunctionDeclaration>
| NodePath<t.ArrowFunctionExpression>
| NodePath<t.FunctionExpression>
> {
const items: Array<
| NodePath<t.FunctionDeclaration>
| NodePath<t.ArrowFunctionExpression>
| NodePath<t.FunctionExpression>
> = [];
): Array<{
compilationEnabled: boolean;
fn: FunctionLike;
}> {
const items: Array<{
compilationEnabled: boolean;
fn: FunctionLike;
}> = [];
try {
const ast = parseInput(source, language);
traverse(ast, {
FunctionDeclaration(nodePath) {
items.push(nodePath);
items.push({
compilationEnabled: shouldCompile(nodePath),
fn: nodePath,
});
nodePath.skip();
},
ArrowFunctionExpression(nodePath) {
items.push(nodePath);
items.push({
compilationEnabled: shouldCompile(nodePath),
fn: nodePath,
});
nodePath.skip();
},
FunctionExpression(nodePath) {
items.push(nodePath);
items.push({
compilationEnabled: shouldCompile(nodePath),
fn: nodePath,
});
nodePath.skip();
},
});
@@ -98,9 +126,48 @@ function parseFunctions(
suggestions: null,
});
}
return items;
}
function shouldCompile(fn: FunctionLike): boolean {
const {body} = fn.node;
if (t.isBlockStatement(body)) {
const selfCheck = checkExplicitMemoizeDirectives(body.directives);
if (selfCheck === MemoizeDirectiveState.Enabled) return true;
if (selfCheck === MemoizeDirectiveState.Disabled) return false;
const parentWithDirective = fn.findParent(parentPath => {
if (parentPath.isBlockStatement() || parentPath.isProgram()) {
const directiveCheck = checkExplicitMemoizeDirectives(
parentPath.node.directives,
);
return MEMOIZE_ENABLED_OR_DISABLED_STATES.has(directiveCheck);
}
return false;
});
if (!parentWithDirective) return true;
const parentDirectiveCheck = checkExplicitMemoizeDirectives(
(parentWithDirective.node as t.Program | t.BlockStatement).directives,
);
return MEMOIZE_ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck);
}
return false;
}
function checkExplicitMemoizeDirectives(
directives: Array<t.Directive>,
): MemoizeDirectiveState {
if (findDirectiveEnablingMemoization(directives).length) {
return MemoizeDirectiveState.Enabled;
}
if (findDirectiveDisablingMemoization(directives).length) {
return MemoizeDirectiveState.Disabled;
}
return MemoizeDirectiveState.Undefined;
}
const COMMON_HOOKS: Array<[string, Hook]> = [
[
'useFragment',
@@ -209,18 +276,38 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
// Extract the first line to quickly check for custom test directives
const pragma = source.substring(0, source.indexOf('\n'));
const config = parseConfigPragmaForTests(pragma);
for (const fn of parseFunctions(source, language)) {
const id = withIdentifier(getFunctionIdentifier(fn));
const parsedFunctions = parseFunctions(source, language);
for (const func of parsedFunctions) {
const id = withIdentifier(getFunctionIdentifier(func.fn));
const fnName = id.name;
if (!func.compilationEnabled) {
upsert({
kind: 'ast',
fnName,
name: 'CodeGen',
value: {
type: 'FunctionDeclaration',
id:
func.fn.isArrowFunctionExpression() ||
func.fn.isFunctionExpression()
? withIdentifier(null)
: func.fn.node.id,
async: func.fn.node.async,
generator: !!func.fn.node.generator,
body: func.fn.node.body as t.BlockStatement,
params: func.fn.node.params,
},
});
continue;
}
for (const result of runPlayground(
fn,
func.fn,
{
...config,
customHooks: new Map([...COMMON_HOOKS]),
},
getReactFunctionType(id),
)) {
const fnName = id.name;
switch (result.kind) {
case 'ast': {
upsert({

View File

@@ -18,6 +18,8 @@ const baseURL = `http://localhost:${PORT}`;
export default defineConfig({
// Timeout per test
timeout: 30 * 1000,
// Run all tests in parallel.
fullyParallel: true,
// Test directory
testDir: path.join(__dirname, '__tests__/e2e'),
// If a test fails, retry it additional 2 times
@@ -36,6 +38,10 @@ export default defineConfig({
reuseExistingServer: !process.env.CI,
},
// 'github' for GitHub Actions CI to generate annotations, plus a concise 'dot'
// default 'list' when running locally
reporter: process.env.CI ? 'github' : 'list',
use: {
// Use baseURL so to make navigations relative.
// More information: https://playwright.dev/docs/api/class-testoptions#test-options-base-url

View File

@@ -22,7 +22,8 @@
"npm:publish": "node scripts/release/publish"
},
"dependencies": {
"fs-extra": "^4.0.2"
"fs-extra": "^4.0.2",
"react-is": "0.0.0-experimental-4beb1fd8-20241118"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^25.0.7",
@@ -36,7 +37,7 @@
"object-assign": "^4.1.1",
"ora": "5.4.1",
"prettier": "^3.3.3",
"prettier-plugin-hermes-parser": "^0.23.0",
"prettier-plugin-hermes-parser": "^0.25.1",
"prompt-promise": "^1.0.3",
"rollup": "^4.22.4",
"rollup-plugin-banner2": "^1.2.3",
@@ -45,8 +46,5 @@
"wait-on": "^7.2.0",
"yargs": "^17.7.2"
},
"resolutions": {
"react-is": "19.0.0-beta-b498834eab-20240506"
},
"packageManager": "yarn@1.22.22"
}

View File

@@ -5,7 +5,8 @@
"main": "dist/index.js",
"license": "MIT",
"files": [
"dist"
"dist",
"!*.tsbuildinfo"
],
"scripts": {
"build": "rimraf dist && rollup --config --bundleConfigAsCjs",
@@ -48,8 +49,8 @@
"jest": "^29.0.3",
"jest-environment-jsdom": "^29.0.3",
"pretty-format": "^24",
"react": "0.0.0-experimental-0bc30748-20241028",
"react-dom": "0.0.0-experimental-0bc30748-20241028",
"react": "0.0.0-experimental-4beb1fd8-20241118",
"react-dom": "0.0.0-experimental-4beb1fd8-20241118",
"rimraf": "^3.0.2",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",

View File

@@ -28,6 +28,7 @@ const DEV_ROLLUP_CONFIG = {
plugins: [
typescript({
tsconfig: './tsconfig.json',
outputToFilesystem: true,
compilerOptions: {
noEmit: true,
},

View File

@@ -121,7 +121,22 @@ export type PluginOptions = {
target: CompilerReactTarget;
};
const CompilerReactTargetSchema = z.enum(['17', '18', '19']);
const CompilerReactTargetSchema = z.union([
z.literal('17'),
z.literal('18'),
z.literal('19'),
/**
* Used exclusively for Meta apps which are guaranteed to have compatible
* react runtime and compiler versions. Note that only the FB-internal bundles
* re-export useMemoCache (see
* https://github.com/facebook/react/blob/5b0ef217ef32333a8e56f39be04327c89efa346f/packages/react/index.fb.js#L68-L70),
* so this option is invalid / creates runtime errors for open-source users.
*/
z.object({
kind: z.literal('donotuse_meta_internal'),
runtimeModule: z.string().default('react'),
}),
]);
export type CompilerReactTarget = z.infer<typeof CompilerReactTargetSchema>;
const CompilationModeSchema = z.enum([

View File

@@ -36,6 +36,7 @@ import {
inferReactivePlaces,
inferReferenceEffects,
inlineImmediatelyInvokedFunctionExpressions,
inferEffectDependencies,
} from '../Inference';
import {
constantPropagation,
@@ -354,6 +355,10 @@ function* runWithEnvironment(
value: hir,
});
if (env.config.inferEffectDependencies) {
inferEffectDependencies(hir);
}
if (env.config.inlineJsxTransform) {
inlineJsxTransform(hir, env.config.inlineJsxTransform);
yield log({

View File

@@ -42,10 +42,10 @@ export type CompilerPass = {
comments: Array<t.CommentBlock | t.CommentLine>;
code: string | null;
};
const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']);
export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']);
export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']);
function findDirectiveEnablingMemoization(
export function findDirectiveEnablingMemoization(
directives: Array<t.Directive>,
): Array<t.Directive> {
return directives.filter(directive =>
@@ -53,7 +53,7 @@ function findDirectiveEnablingMemoization(
);
}
function findDirectiveDisablingMemoization(
export function findDirectiveDisablingMemoization(
directives: Array<t.Directive>,
): Array<t.Directive> {
return directives.filter(directive =>
@@ -1123,30 +1123,23 @@ function checkFunctionReferencedBeforeDeclarationAtTopLevel(
return errors.details.length > 0 ? errors : null;
}
type ReactCompilerRuntimeModule =
| 'react/compiler-runtime' // from react namespace
| 'react-compiler-runtime'; // npm package
function getReactCompilerRuntimeModule(
opts: PluginOptions,
): ReactCompilerRuntimeModule {
let moduleName: ReactCompilerRuntimeModule | null = null;
switch (opts.target) {
case '17':
case '18': {
moduleName = 'react-compiler-runtime';
break;
}
case '19': {
moduleName = 'react/compiler-runtime';
break;
}
default:
CompilerError.invariant(moduleName != null, {
function getReactCompilerRuntimeModule(opts: PluginOptions): string {
if (opts.target === '19') {
return 'react/compiler-runtime'; // from react namespace
} else if (opts.target === '17' || opts.target === '18') {
return 'react-compiler-runtime'; // npm package
} else {
CompilerError.invariant(
opts.target != null &&
opts.target.kind === 'donotuse_meta_internal' &&
typeof opts.target.runtimeModule === 'string',
{
reason: 'Expected target to already be validated',
description: null,
loc: null,
suggestions: null,
});
},
);
return opts.target.runtimeModule;
}
return moduleName;
}

View File

@@ -3186,7 +3186,13 @@ function lowerJsxMemberExpression(
loc: object.node.loc ?? null,
suggestions: null,
});
objectPlace = lowerIdentifier(builder, object);
const kind = getLoadKind(builder, object);
objectPlace = lowerValueToTemporary(builder, {
kind: kind,
place: lowerIdentifier(builder, object),
loc: exprPath.node.loc ?? GeneratedSource,
});
}
const property = exprPath.get('property').node.name;
return lowerValueToTemporary(builder, {

View File

@@ -231,6 +231,52 @@ const EnvironmentConfigSchema = z.object({
*/
enableUseTypeAnnotations: z.boolean().default(false),
enableFunctionDependencyRewrite: z.boolean().default(true),
/**
* Enables inference of optional dependency chains. Without this flag
* a property chain such as `props?.items?.foo` will infer as a dep on
* just `props`. With this flag enabled, we'll infer that full path as
* the dependency.
*/
enableOptionalDependencies: z.boolean().default(true),
/**
* Enables inference and auto-insertion of effect dependencies. Takes in an array of
* configurable module and import pairs to allow for user-land experimentation. For example,
* [
* {
* module: 'react',
* imported: 'useEffect',
* numRequiredArgs: 1,
* },{
* module: 'MyExperimentalEffectHooks',
* imported: 'useExperimentalEffect',
* numRequiredArgs: 2,
* },
* ]
* would insert dependencies for calls of `useEffect` imported from `react` and calls of
* useExperimentalEffect` from `MyExperimentalEffectHooks`.
*
* `numRequiredArgs` tells the compiler the amount of arguments required to append a dependency
* array to the end of the call. With the configuration above, we'd insert dependencies for
* `useEffect` if it is only given a single argument and it would be appended to the argument list.
*
* numRequiredArgs must always be greater than 0, otherwise there is no function to analyze for dependencies
*
* Still experimental.
*/
inferEffectDependencies: z
.nullable(
z.array(
z.object({
function: ExternalFunctionSchema,
numRequiredArgs: z.number(),
}),
),
)
.default(null),
/**
* Enables inlining ReactElement object literals in place of JSX
* An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime
@@ -599,6 +645,29 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = {
source: 'react-compiler-runtime',
importSpecifierName: 'useContext_withSelector',
},
inferEffectDependencies: [
{
function: {
source: 'react',
importSpecifierName: 'useEffect',
},
numRequiredArgs: 1,
},
{
function: {
source: 'shared-runtime',
importSpecifierName: 'useSpecialEffect',
},
numRequiredArgs: 2,
},
{
function: {
source: 'useEffectWrapper',
importSpecifierName: 'default',
},
numRequiredArgs: 1,
},
],
};
/**
@@ -1085,3 +1154,5 @@ export function tryParseExternalFunction(
suggestions: null,
});
}
export const DEFAULT_EXPORT = 'default';

View File

@@ -440,14 +440,6 @@ class Context {
// Checks if identifier is a valid dependency in the current scope
#checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean {
// ref.current access is not a valid dep
if (
isUseRefType(maybeDependency.identifier) &&
maybeDependency.path.at(0)?.property === 'current'
) {
return false;
}
// ref value is not a valid dep
if (isRefValueType(maybeDependency.identifier)) {
return false;
@@ -549,6 +541,16 @@ class Context {
});
}
// ref.current access is not a valid dep
if (
isUseRefType(maybeDependency.identifier) &&
maybeDependency.path.at(0)?.property === 'current'
) {
maybeDependency = {
identifier: maybeDependency.identifier,
path: [],
};
}
if (this.#checkValidDependency(maybeDependency)) {
this.#dependencies.value!.push(maybeDependency);
}
@@ -661,35 +663,54 @@ function collectDependencies(
const scopeTraversal = new ScopeBlockTraversal();
for (const [blockId, block] of fn.body.blocks) {
scopeTraversal.recordScopes(block);
const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId);
if (scopeBlockInfo?.kind === 'begin') {
context.enterScope(scopeBlockInfo.scope);
} else if (scopeBlockInfo?.kind === 'end') {
context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned);
}
const handleFunction = (fn: HIRFunction): void => {
for (const [blockId, block] of fn.body.blocks) {
scopeTraversal.recordScopes(block);
const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId);
if (scopeBlockInfo?.kind === 'begin') {
context.enterScope(scopeBlockInfo.scope);
} else if (scopeBlockInfo?.kind === 'end') {
context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned);
}
// Record referenced optional chains in phis
for (const phi of block.phis) {
for (const operand of phi.operands) {
const maybeOptionalChain = temporaries.get(operand[1].identifier.id);
if (maybeOptionalChain) {
context.visitDependency(maybeOptionalChain);
}
}
}
for (const instr of block.instructions) {
if (
fn.env.config.enableFunctionDependencyRewrite &&
(instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod')
) {
context.declare(instr.lvalue.identifier, {
id: instr.id,
scope: context.currentScope,
});
/**
* Recursively visit the inner function to extract dependencies there
*/
const wasInInnerFn = context.inInnerFn;
context.inInnerFn = true;
handleFunction(instr.value.loweredFunc.func);
context.inInnerFn = wasInInnerFn;
} else if (!processedInstrsInOptional.has(instr)) {
handleInstruction(instr, context);
}
}
// Record referenced optional chains in phis
for (const phi of block.phis) {
for (const operand of phi.operands) {
const maybeOptionalChain = temporaries.get(operand[1].identifier.id);
if (maybeOptionalChain) {
context.visitDependency(maybeOptionalChain);
if (!processedInstrsInOptional.has(block.terminal)) {
for (const place of eachTerminalOperand(block.terminal)) {
context.visitOperand(place);
}
}
}
for (const instr of block.instructions) {
if (!processedInstrsInOptional.has(instr)) {
handleInstruction(instr, context);
}
}
};
if (!processedInstrsInOptional.has(block.terminal)) {
for (const place of eachTerminalOperand(block.terminal)) {
context.visitOperand(place);
}
}
}
handleFunction(fn);
return context.deps;
}

View File

@@ -0,0 +1,296 @@
import {CompilerError, SourceLocation} from '..';
import {
ArrayExpression,
Effect,
Environment,
FunctionExpression,
GeneratedSource,
HIRFunction,
IdentifierId,
Instruction,
makeInstructionId,
TInstruction,
InstructionId,
ScopeId,
ReactiveScopeDependency,
Place,
ReactiveScopeDependencies,
} from '../HIR';
import {DEFAULT_EXPORT} from '../HIR/Environment';
import {
createTemporaryPlace,
fixScopeAndIdentifierRanges,
markInstructionIds,
} from '../HIR/HIRBuilder';
import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors';
import {getOrInsertWith} from '../Utils/utils';
/**
* Infers reactive dependencies captured by useEffect lambdas and adds them as
* a second argument to the useEffect call if no dependency array is provided.
*/
export function inferEffectDependencies(fn: HIRFunction): void {
let hasRewrite = false;
const fnExpressions = new Map<
IdentifierId,
TInstruction<FunctionExpression>
>();
const autodepFnConfigs = new Map<string, Map<string, number>>();
for (const effectTarget of fn.env.config.inferEffectDependencies!) {
const moduleTargets = getOrInsertWith(
autodepFnConfigs,
effectTarget.function.source,
() => new Map<string, number>(),
);
moduleTargets.set(
effectTarget.function.importSpecifierName,
effectTarget.numRequiredArgs,
);
}
const autodepFnLoads = new Map<IdentifierId, number>();
const scopeInfos = new Map<
ScopeId,
{pruned: boolean; deps: ReactiveScopeDependencies; hasSingleInstr: boolean}
>();
const loadGlobals = new Set<IdentifierId>();
/**
* When inserting LoadLocals, we need to retain the reactivity of the base
* identifier, as later passes e.g. PruneNonReactiveDeps take the reactivity of
* a base identifier as the "maximal" reactivity of all its references.
* Concretely,
* reactive(Identifier i) = Union_{reference of i}(reactive(reference))
*/
const reactiveIds = inferReactiveIdentifiers(fn);
for (const [, block] of fn.body.blocks) {
if (
block.terminal.kind === 'scope' ||
block.terminal.kind === 'pruned-scope'
) {
const scopeBlock = fn.body.blocks.get(block.terminal.block)!;
scopeInfos.set(block.terminal.scope.id, {
pruned: block.terminal.kind === 'pruned-scope',
deps: block.terminal.scope.dependencies,
hasSingleInstr:
scopeBlock.instructions.length === 1 &&
scopeBlock.terminal.kind === 'goto' &&
scopeBlock.terminal.block === block.terminal.fallthrough,
});
}
const rewriteInstrs = new Map<InstructionId, Array<Instruction>>();
for (const instr of block.instructions) {
const {value, lvalue} = instr;
if (value.kind === 'FunctionExpression') {
fnExpressions.set(
lvalue.identifier.id,
instr as TInstruction<FunctionExpression>,
);
} else if (value.kind === 'LoadGlobal') {
loadGlobals.add(lvalue.identifier.id);
if (
value.binding.kind === 'ImportSpecifier' ||
value.binding.kind === 'ImportDefault'
) {
const moduleTargets = autodepFnConfigs.get(value.binding.module);
if (moduleTargets != null) {
const importSpecifierName =
value.binding.kind === 'ImportSpecifier'
? value.binding.imported
: DEFAULT_EXPORT;
const numRequiredArgs = moduleTargets.get(importSpecifierName);
if (numRequiredArgs != null) {
autodepFnLoads.set(lvalue.identifier.id, numRequiredArgs);
}
}
}
} else if (
/*
* TODO: Handle method calls
*/
value.kind === 'CallExpression' &&
autodepFnLoads.get(value.callee.identifier.id) === value.args.length &&
value.args[0].kind === 'Identifier'
) {
const effectDeps: Array<Place> = [];
const newInstructions: Array<Instruction> = [];
const deps: ArrayExpression = {
kind: 'ArrayExpression',
elements: effectDeps,
loc: GeneratedSource,
};
const depsPlace = createTemporaryPlace(fn.env, GeneratedSource);
depsPlace.effect = Effect.Read;
const fnExpr = fnExpressions.get(value.args[0].identifier.id);
if (fnExpr != null) {
// We have a function expression, so we can infer its dependencies
const scopeInfo =
fnExpr.lvalue.identifier.scope != null
? scopeInfos.get(fnExpr.lvalue.identifier.scope.id)
: null;
CompilerError.invariant(scopeInfo != null, {
reason: 'Expected function expression scope to exist',
loc: value.loc,
});
if (scopeInfo.pruned || !scopeInfo.hasSingleInstr) {
/**
* TODO: retry pipeline that ensures effect function expressions
* are placed into their own scope
*/
CompilerError.throwTodo({
reason:
'[InferEffectDependencies] Expected effect function to have non-pruned scope and its scope to have exactly one instruction',
loc: fnExpr.loc,
});
}
/**
* Step 1: push dependencies to the effect deps array
*
* Note that it's invalid to prune non-reactive deps in this pass, see
* the `infer-effect-deps/pruned-nonreactive-obj` fixture for an
* explanation.
*/
for (const dep of scopeInfo.deps) {
const {place, instructions} = writeDependencyToInstructions(
dep,
reactiveIds.has(dep.identifier.id),
fn.env,
fnExpr.loc,
);
newInstructions.push(...instructions);
effectDeps.push(place);
}
newInstructions.push({
id: makeInstructionId(0),
loc: GeneratedSource,
lvalue: {...depsPlace, effect: Effect.Mutate},
value: deps,
});
// Step 2: push the inferred deps array as an argument of the useEffect
value.args.push({...depsPlace, effect: Effect.Freeze});
rewriteInstrs.set(instr.id, newInstructions);
} else if (loadGlobals.has(value.args[0].identifier.id)) {
// Global functions have no reactive dependencies, so we can insert an empty array
newInstructions.push({
id: makeInstructionId(0),
loc: GeneratedSource,
lvalue: {...depsPlace, effect: Effect.Mutate},
value: deps,
});
value.args.push({...depsPlace, effect: Effect.Freeze});
rewriteInstrs.set(instr.id, newInstructions);
}
}
}
if (rewriteInstrs.size > 0) {
hasRewrite = true;
const newInstrs = [];
for (const instr of block.instructions) {
const newInstr = rewriteInstrs.get(instr.id);
if (newInstr != null) {
newInstrs.push(...newInstr, instr);
} else {
newInstrs.push(instr);
}
}
block.instructions = newInstrs;
}
}
if (hasRewrite) {
// Renumber instructions and fix scope ranges
markInstructionIds(fn.body);
fixScopeAndIdentifierRanges(fn.body);
}
}
function writeDependencyToInstructions(
dep: ReactiveScopeDependency,
reactive: boolean,
env: Environment,
loc: SourceLocation,
): {place: Place; instructions: Array<Instruction>} {
const instructions: Array<Instruction> = [];
let currValue = createTemporaryPlace(env, GeneratedSource);
currValue.reactive = reactive;
instructions.push({
id: makeInstructionId(0),
loc: GeneratedSource,
lvalue: {...currValue, effect: Effect.Mutate},
value: {
kind: 'LoadLocal',
place: {
kind: 'Identifier',
identifier: dep.identifier,
effect: Effect.Capture,
reactive,
loc: loc,
},
loc: loc,
},
});
for (const path of dep.path) {
if (path.optional) {
/**
* TODO: instead of truncating optional paths, reuse
* instructions from hoisted dependencies block(s)
*/
break;
}
if (path.property === 'current') {
/*
* Prune ref.current accesses. This may over-capture for non-ref values with
* a current property, but that's fine.
*/
break;
}
const nextValue = createTemporaryPlace(env, GeneratedSource);
nextValue.reactive = reactive;
instructions.push({
id: makeInstructionId(0),
loc: GeneratedSource,
lvalue: {...nextValue, effect: Effect.Mutate},
value: {
kind: 'PropertyLoad',
object: {...currValue, effect: Effect.Capture},
property: path.property,
loc: loc,
},
});
currValue = nextValue;
}
currValue.effect = Effect.Freeze;
return {place: currValue, instructions};
}
function inferReactiveIdentifiers(fn: HIRFunction): Set<IdentifierId> {
const reactiveIds: Set<IdentifierId> = new Set();
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
/**
* No need to traverse into nested functions as
* 1. their effects are recorded in `LoweredFunction.dependencies`
* 2. we don't mark `reactive` in these anyways
*/
for (const place of eachInstructionOperand(instr)) {
if (place.reactive) {
reactiveIds.add(place.identifier.id);
}
}
}
for (const place of eachTerminalOperand(block.terminal)) {
if (place.reactive) {
reactiveIds.add(place.identifier.id);
}
}
}
return reactiveIds;
}

View File

@@ -11,3 +11,4 @@ export {inferMutableRanges} from './InferMutableRanges';
export {inferReactivePlaces} from './InferReactivePlaces';
export {default as inferReferenceEffects} from './InferReferenceEffects';
export {inlineImmediatelyInvokedFunctionExpressions} from './InlineImmediatelyInvokedFunctionExpressions';
export {inferEffectDependencies} from './InferEffectDependencies';

View File

@@ -6,7 +6,6 @@
*/
import {
ArrayPattern,
BlockId,
HIRFunction,
Identifier,
@@ -58,6 +57,14 @@ export function deadCodeElimination(fn: HIRFunction): void {
}
}
}
/**
* Constant propagation and DCE may have deleted or rewritten instructions
* that reference context variables.
*/
retainWhere(fn.context, contextVar =>
state.isIdOrNameUsed(contextVar.identifier),
);
}
class State {
@@ -176,29 +183,28 @@ function rewriteInstruction(instr: Instruction, state: State): void {
switch (instr.value.lvalue.pattern.kind) {
case 'ArrayPattern': {
/*
* For arrays, we can only eliminate unused items from the end of the array,
* so we iterate from the end and break once we find a used item. Note that
* we already know at least one item is used, from the pruneableValue check.
* For arrays, we can prune items prior to the end by replacing
* them with a hole. Items at the end can simply be dropped.
*/
let nextItems: ArrayPattern['items'] | null = null;
const originalItems = instr.value.lvalue.pattern.items;
for (let i = originalItems.length - 1; i >= 0; i--) {
const item = originalItems[i];
let lastEntryIndex = 0;
const items = instr.value.lvalue.pattern.items;
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.kind === 'Identifier') {
if (state.isIdOrNameUsed(item.identifier)) {
nextItems = originalItems.slice(0, i + 1);
break;
if (!state.isIdOrNameUsed(item.identifier)) {
items[i] = {kind: 'Hole'};
} else {
lastEntryIndex = i;
}
} else if (item.kind === 'Spread') {
if (state.isIdOrNameUsed(item.place.identifier)) {
nextItems = originalItems.slice(0, i + 1);
break;
if (!state.isIdOrNameUsed(item.place.identifier)) {
items[i] = {kind: 'Hole'};
} else {
lastEntryIndex = i;
}
}
}
if (nextItems !== null) {
instr.value.lvalue.pattern.items = nextItems;
}
items.length = lastEntryIndex + 1;
break;
}
case 'ObjectPattern': {

View File

@@ -559,28 +559,45 @@ function createPropsProperties(
propAttributes.forEach(prop => {
switch (prop.kind) {
case 'JsxAttribute': {
if (prop.name === 'ref') {
refProperty = {
kind: 'ObjectProperty',
key: {name: 'ref', kind: 'string'},
type: 'property',
place: {...prop.place},
};
} else if (prop.name === 'key') {
keyProperty = {
kind: 'ObjectProperty',
key: {name: 'key', kind: 'string'},
type: 'property',
place: {...prop.place},
};
} else {
const attributeProperty: ObjectProperty = {
kind: 'ObjectProperty',
key: {name: prop.name, kind: 'string'},
type: 'property',
place: {...prop.place},
};
props.push(attributeProperty);
switch (prop.name) {
case 'key': {
keyProperty = {
kind: 'ObjectProperty',
key: {name: 'key', kind: 'string'},
type: 'property',
place: {...prop.place},
};
break;
}
case 'ref': {
/**
* In the current JSX implementation, ref is both
* a property on the element and a property on props.
*/
refProperty = {
kind: 'ObjectProperty',
key: {name: 'ref', kind: 'string'},
type: 'property',
place: {...prop.place},
};
const refPropProperty: ObjectProperty = {
kind: 'ObjectProperty',
key: {name: 'ref', kind: 'string'},
type: 'property',
place: {...prop.place},
};
props.push(refPropProperty);
break;
}
default: {
const attributeProperty: ObjectProperty = {
kind: 'ObjectProperty',
key: {name: prop.name, kind: 'string'},
type: 'property',
place: {...prop.place},
};
props.push(attributeProperty);
}
}
break;
}

View File

@@ -12,6 +12,8 @@ import {
PrunedReactiveScopeBlock,
ReactiveFunction,
isPrimitiveType,
isUseRefType,
Identifier,
} from '../HIR/HIR';
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
@@ -50,13 +52,21 @@ class Visitor extends ReactiveFunctionVisitor<Set<IdentifierId>> {
this.traversePrunedScope(scopeBlock, state);
for (const [id, decl] of scopeBlock.scope.declarations) {
if (!isPrimitiveType(decl.identifier)) {
if (
!isPrimitiveType(decl.identifier) &&
!isStableRefType(decl.identifier, state)
) {
state.add(id);
}
}
}
}
function isStableRefType(
identifier: Identifier,
reactiveIdentifiers: Set<IdentifierId>,
): boolean {
return isUseRefType(identifier) && !reactiveIdentifiers.has(identifier.id);
}
/*
* Computes a set of identifiers which are reactive, using the analysis previously performed
* in `InferReactivePlaces`.

View File

@@ -28,7 +28,7 @@ function Component() {
t0 = () => {
"worklet";
setCount((count_0) => count_0 + 1);
setCount(_temp);
};
$[0] = t0;
} else {
@@ -45,6 +45,9 @@ function Component() {
}
return t1;
}
function _temp(count_0) {
return count_0 + 1;
}
```

View File

@@ -1,89 +0,0 @@
## Input
```javascript
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
/**
* Bug: we're currently filtering out `ref.current` dependencies in
* `propagateScopeDependencies:checkValidDependency`. This is incorrect.
* Instead, we should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
* Forget:
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
*/
function Component({cond}) {
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
const cb = () => ref.current;
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: false}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
import { Stringify } from "shared-runtime";
/**
* Bug: we're currently filtering out `ref.current` dependencies in
* `propagateScopeDependencies:checkValidDependency`. This is incorrect.
* Instead, we should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
* Forget:
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
*/
function Component(t0) {
const $ = _c(1);
const { cond } = t0;
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const cb = () => ref.current;
t1 = <Stringify cb={cb} shouldInvokeFns={true} />;
$[0] = t1;
} else {
t1 = $[0];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true }],
sequentialRenders: [{ cond: true }, { cond: false }],
};
```

View File

@@ -1,33 +0,0 @@
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
/**
* Bug: we're currently filtering out `ref.current` dependencies in
* `propagateScopeDependencies:checkValidDependency`. This is incorrect.
* Instead, we should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
* Forget:
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
*/
function Component({cond}) {
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
const cb = () => ref.current;
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: false}],
};

View File

@@ -0,0 +1,114 @@
## Input
```javascript
import {arrayPush, CONST_NUMBER0, mutate} from 'shared-runtime';
/**
* Repro for bug in our type inference system. We currently propagate inferred
* types through control flow / potential type guards. Note that this is
* inconsistent with both Flow and Typescript.
* https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA
* https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* [2]
* [3]
* Forget:
* (kind: ok)
* [2]
* [2,3]
*/
function useFoo({cond, value}: {cond: boolean; value: number}) {
const x = {value: cond ? CONST_NUMBER0 : []};
mutate(x);
const xValue = x.value;
let result;
if (typeof xValue === 'number') {
result = xValue + 1; // (1) here we infer xValue is a primitive
} else {
result = arrayPush(xValue, value); // (2) and propagate it to all other xValue references
}
return result;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{cond: true}],
sequentialRenders: [
{cond: false, value: 2},
{cond: false, value: 3},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { arrayPush, CONST_NUMBER0, mutate } from "shared-runtime";
/**
* Repro for bug in our type inference system. We currently propagate inferred
* types through control flow / potential type guards. Note that this is
* inconsistent with both Flow and Typescript.
* https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA
* https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* [2]
* [3]
* Forget:
* (kind: ok)
* [2]
* [2,3]
*/
function useFoo(t0) {
const $ = _c(5);
const { cond, value } = t0;
let x;
if ($[0] !== cond) {
x = { value: cond ? CONST_NUMBER0 : [] };
mutate(x);
$[0] = cond;
$[1] = x;
} else {
x = $[1];
}
const xValue = x.value;
let result;
if (typeof xValue === "number") {
result = xValue + 1;
} else {
let t1;
if ($[2] !== value || $[3] !== xValue) {
t1 = arrayPush(xValue, value);
$[2] = value;
$[3] = xValue;
$[4] = t1;
} else {
t1 = $[4];
}
result = t1;
}
return result;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ cond: true }],
sequentialRenders: [
{ cond: false, value: 2 },
{ cond: false, value: 3 },
],
};
```

View File

@@ -0,0 +1,41 @@
import {arrayPush, CONST_NUMBER0, mutate} from 'shared-runtime';
/**
* Repro for bug in our type inference system. We currently propagate inferred
* types through control flow / potential type guards. Note that this is
* inconsistent with both Flow and Typescript.
* https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA
* https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* [2]
* [3]
* Forget:
* (kind: ok)
* [2]
* [2,3]
*/
function useFoo({cond, value}: {cond: boolean; value: number}) {
const x = {value: cond ? CONST_NUMBER0 : []};
mutate(x);
const xValue = x.value;
let result;
if (typeof xValue === 'number') {
result = xValue + 1; // (1) here we infer xValue is a primitive
} else {
result = arrayPush(xValue, value); // (2) and propagate it to all other xValue references
}
return result;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{cond: true}],
sequentialRenders: [
{cond: false, value: 2},
{cond: false, value: 3},
],
};

View File

@@ -55,11 +55,7 @@ function getNativeLogFunction(level) {
if (arguments.length === 1 && typeof arguments[0] === "string") {
str = arguments[0];
} else {
str = Array.prototype.map
.call(arguments, function (arg) {
return inspect(arg, { depth: 10 });
})
.join(", ");
str = Array.prototype.map.call(arguments, _temp).join(", ");
}
const firstArg = arguments[0];
@@ -92,6 +88,9 @@ function getNativeLogFunction(level) {
}
return t0;
}
function _temp(arg) {
return inspect(arg, { depth: 10 });
}
```

View File

@@ -2,39 +2,55 @@
## Input
```javascript
function component(foo, bar) {
import {mutate} from 'shared-runtime';
function Component({foo, bar}) {
let x = {foo};
let y = {bar};
const f0 = function () {
let a = {y};
let a = [y];
let b = x;
a.x = b;
// this writes y.x = x
a[0].x = b;
};
f0();
mutate(y);
mutate(y.x);
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{foo: 3, bar: 4}],
sequentialRenders: [
{foo: 3, bar: 4},
{foo: 3, bar: 5},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function component(foo, bar) {
import { mutate } from "shared-runtime";
function Component(t0) {
const $ = _c(3);
const { foo, bar } = t0;
let y;
if ($[0] !== bar || $[1] !== foo) {
const x = { foo };
y = { bar };
const f0 = function () {
const a = { y };
const a = [y];
const b = x;
a.x = b;
a[0].x = b;
};
f0();
mutate(y);
mutate(y.x);
$[0] = bar;
$[1] = foo;
$[2] = y;
@@ -44,5 +60,17 @@ function component(foo, bar) {
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ foo: 3, bar: 4 }],
sequentialRenders: [
{ foo: 3, bar: 4 },
{ foo: 3, bar: 5 },
],
};
```
### Eval output
(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}}
{"bar":5,"x":{"foo":3,"wat0":"joe"}}

View File

@@ -1,12 +1,24 @@
function component(foo, bar) {
import {mutate} from 'shared-runtime';
function Component({foo, bar}) {
let x = {foo};
let y = {bar};
const f0 = function () {
let a = {y};
let a = [y];
let b = x;
a.x = b;
// this writes y.x = x
a[0].x = b;
};
f0();
mutate(y);
mutate(y.x);
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{foo: 3, bar: 4}],
sequentialRenders: [
{foo: 3, bar: 4},
{foo: 3, bar: 5},
],
};

View File

@@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function component(a, b) {
const $ = _c(5);
let t0;
if ($[0] !== b) {
t0 = { b };
$[0] = b;
$[1] = t0;
} else {
t0 = $[1];
}
const y = t0;
const $ = _c(2);
const y = { b };
let z;
if ($[2] !== a || $[3] !== y) {
if ($[0] !== a) {
z = { a };
const x = function () {
z.a = 2;
};
x();
$[2] = a;
$[3] = y;
$[4] = z;
$[0] = a;
$[1] = z;
} else {
z = $[4];
z = $[1];
}
return z;
}

View File

@@ -2,21 +2,28 @@
## Input
```javascript
function component(a, b) {
import {mutate} from 'shared-runtime';
function Component({a, b}) {
let z = {a};
let y = {b};
let y = {b: {b}};
let x = function () {
z.a = 2;
console.log(y.b);
mutate(y.b);
};
x();
return z;
return [y, z];
}
export const FIXTURE_ENTRYPOINT = {
fn: component,
params: ['TodoAdd'],
isComponent: 'TodoAdd',
fn: Component,
params: [{a: 2, b: 3}],
sequentialRenders: [
{a: 2, b: 3},
{a: 2, b: 3},
{a: 4, b: 3},
{a: 4, b: 5},
],
};
```
@@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function component(a, b) {
import { mutate } from "shared-runtime";
function Component(t0) {
const $ = _c(3);
let z;
const { a, b } = t0;
let t1;
if ($[0] !== a || $[1] !== b) {
z = { a };
const y = { b };
const z = { a };
const y = { b: { b } };
const x = function () {
z.a = 2;
console.log(y.b);
mutate(y.b);
};
x();
t1 = [y, z];
$[0] = a;
$[1] = b;
$[2] = z;
$[2] = t1;
} else {
z = $[2];
t1 = $[2];
}
return z;
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: component,
params: ["TodoAdd"],
isComponent: "TodoAdd",
fn: Component,
params: [{ a: 2, b: 3 }],
sequentialRenders: [
{ a: 2, b: 3 },
{ a: 2, b: 3 },
{ a: 4, b: 3 },
{ a: 4, b: 5 },
],
};
```
### Eval output
(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}]
[{"b":{"b":3,"wat0":"joe"}},{"a":2}]
[{"b":{"b":3,"wat0":"joe"}},{"a":2}]
[{"b":{"b":5,"wat0":"joe"}},{"a":2}]

View File

@@ -1,16 +1,23 @@
function component(a, b) {
import {mutate} from 'shared-runtime';
function Component({a, b}) {
let z = {a};
let y = {b};
let y = {b: {b}};
let x = function () {
z.a = 2;
console.log(y.b);
mutate(y.b);
};
x();
return z;
return [y, z];
}
export const FIXTURE_ENTRYPOINT = {
fn: component,
params: ['TodoAdd'],
isComponent: 'TodoAdd',
fn: Component,
params: [{a: 2, b: 3}],
sequentialRenders: [
{a: 2, b: 3},
{a: 2, b: 3},
{a: 4, b: 3},
{a: 4, b: 5},
],
};

View File

@@ -0,0 +1,72 @@
## Input
```javascript
function Component({a, b}) {
let z = {a};
let y = {b};
let x = function () {
z.a = 2;
return Math.max(y.b, 0);
};
x();
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 2, b: 3}],
sequentialRenders: [
{a: 2, b: 3},
{a: 2, b: 3},
{a: 4, b: 3},
{a: 4, b: 5},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(t0) {
const $ = _c(3);
const { a, b } = t0;
let z;
if ($[0] !== a || $[1] !== b) {
z = { a };
const y = { b };
const x = function () {
z.a = 2;
return Math.max(y.b, 0);
};
x();
$[0] = a;
$[1] = b;
$[2] = z;
} else {
z = $[2];
}
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: 2, b: 3 }],
sequentialRenders: [
{ a: 2, b: 3 },
{ a: 2, b: 3 },
{ a: 4, b: 3 },
{ a: 4, b: 5 },
],
};
```
### Eval output
(kind: ok) {"a":2}
{"a":2}
{"a":2}
{"a":2}

View File

@@ -0,0 +1,21 @@
function Component({a, b}) {
let z = {a};
let y = {b};
let x = function () {
z.a = 2;
return Math.max(y.b, 0);
};
x();
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 2, b: 3}],
sequentialRenders: [
{a: 2, b: 3},
{a: 2, b: 3},
{a: 4, b: 3},
{a: 4, b: 5},
],
};

View File

@@ -16,7 +16,7 @@ function component() {
import { c as _c } from "react/compiler-runtime";
function component() {
const $ = _c(1);
const [x, setX] = useState(0);
const [, setX] = useState(0);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const handler = (v) => setX(v);

View File

@@ -35,8 +35,7 @@ function Component(props) {
}
let d;
if ($[2] !== props.c) {
const [c, ...t0] = props.c;
d = t0;
[, ...d] = props.c;
$[2] = props.c;
$[3] = d;
} else {

View File

@@ -34,7 +34,7 @@ function Component() {
t0 = function update() {
"worklet";
setCount((count_0) => count_0 + 1);
setCount(_temp);
};
$[0] = t0;
} else {
@@ -51,6 +51,9 @@ function Component() {
}
return t1;
}
function _temp(count_0) {
return count_0 + 1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,

View File

@@ -24,7 +24,7 @@ function Component(props) {
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(2);
const [value, setValue] = useState(null);
const [, setValue] = useState(null);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (e) => setValue((value_0) => value_0 + e.target.value);

View File

@@ -0,0 +1,61 @@
## Input
```javascript
// @inferEffectDependencies
import {print, useSpecialEffect} from 'shared-runtime';
function CustomConfig({propVal}) {
// Insertion
useSpecialEffect(() => print(propVal), [propVal]);
// No insertion
useSpecialEffect(() => print(propVal), [propVal], [propVal]);
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { print, useSpecialEffect } from "shared-runtime";
function CustomConfig(t0) {
const $ = _c(7);
const { propVal } = t0;
let t1;
let t2;
if ($[0] !== propVal) {
t1 = () => print(propVal);
t2 = [propVal];
$[0] = propVal;
$[1] = t1;
$[2] = t2;
} else {
t1 = $[1];
t2 = $[2];
}
useSpecialEffect(t1, t2, [propVal]);
let t3;
let t4;
let t5;
if ($[3] !== propVal) {
t3 = () => print(propVal);
t4 = [propVal];
t5 = [propVal];
$[3] = propVal;
$[4] = t3;
$[5] = t4;
$[6] = t5;
} else {
t3 = $[4];
t4 = $[5];
t5 = $[6];
}
useSpecialEffect(t3, t4, t5);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,9 @@
// @inferEffectDependencies
import {print, useSpecialEffect} from 'shared-runtime';
function CustomConfig({propVal}) {
// Insertion
useSpecialEffect(() => print(propVal), [propVal]);
// No insertion
useSpecialEffect(() => print(propVal), [propVal], [propVal]);
}

View File

@@ -0,0 +1,150 @@
## Input
```javascript
// @inferEffectDependencies
import {useEffect, useRef} from 'react';
import useEffectWrapper from 'useEffectWrapper';
const moduleNonReactive = 0;
function Component({foo, bar}) {
const localNonreactive = 0;
const ref = useRef(0);
const localNonPrimitiveReactive = {
foo,
};
const localNonPrimitiveNonreactive = {};
useEffect(() => {
console.log(foo);
console.log(bar);
console.log(moduleNonReactive);
console.log(localNonreactive);
console.log(globalValue);
console.log(ref.current);
console.log(localNonPrimitiveReactive);
console.log(localNonPrimitiveNonreactive);
});
// Optional chains and property accesses
// TODO: we may be able to save bytes by omitting property accesses if the
// object of the member expression is already included in the inferred deps
useEffect(() => {
console.log(bar?.baz);
console.log(bar.qux);
});
function f() {
console.log(foo);
}
// No inferred dep array, the argument is not a lambda
useEffect(f);
useEffectWrapper(() => {
console.log(foo);
});
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useEffect, useRef } from "react";
import useEffectWrapper from "useEffectWrapper";
const moduleNonReactive = 0;
function Component(t0) {
const $ = _c(14);
const { foo, bar } = t0;
const ref = useRef(0);
let t1;
if ($[0] !== foo) {
t1 = { foo };
$[0] = foo;
$[1] = t1;
} else {
t1 = $[1];
}
const localNonPrimitiveReactive = t1;
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = {};
$[2] = t2;
} else {
t2 = $[2];
}
const localNonPrimitiveNonreactive = t2;
let t3;
if ($[3] !== bar || $[4] !== foo || $[5] !== localNonPrimitiveReactive) {
t3 = () => {
console.log(foo);
console.log(bar);
console.log(moduleNonReactive);
console.log(0);
console.log(globalValue);
console.log(ref.current);
console.log(localNonPrimitiveReactive);
console.log(localNonPrimitiveNonreactive);
};
$[3] = bar;
$[4] = foo;
$[5] = localNonPrimitiveReactive;
$[6] = t3;
} else {
t3 = $[6];
}
useEffect(t3, [
foo,
bar,
ref,
localNonPrimitiveReactive,
localNonPrimitiveNonreactive,
]);
let t4;
if ($[7] !== bar.baz || $[8] !== bar.qux) {
t4 = () => {
console.log(bar?.baz);
console.log(bar.qux);
};
$[7] = bar.baz;
$[8] = bar.qux;
$[9] = t4;
} else {
t4 = $[9];
}
useEffect(t4, [bar.baz, bar.qux]);
let t5;
if ($[10] !== foo) {
t5 = function f() {
console.log(foo);
};
$[10] = foo;
$[11] = t5;
} else {
t5 = $[11];
}
const f = t5;
useEffect(f);
let t6;
if ($[12] !== foo) {
t6 = () => {
console.log(foo);
};
$[12] = foo;
$[13] = t6;
} else {
t6 = $[13];
}
useEffectWrapper(t6, [foo]);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,43 @@
// @inferEffectDependencies
import {useEffect, useRef} from 'react';
import useEffectWrapper from 'useEffectWrapper';
const moduleNonReactive = 0;
function Component({foo, bar}) {
const localNonreactive = 0;
const ref = useRef(0);
const localNonPrimitiveReactive = {
foo,
};
const localNonPrimitiveNonreactive = {};
useEffect(() => {
console.log(foo);
console.log(bar);
console.log(moduleNonReactive);
console.log(localNonreactive);
console.log(globalValue);
console.log(ref.current);
console.log(localNonPrimitiveReactive);
console.log(localNonPrimitiveNonreactive);
});
// Optional chains and property accesses
// TODO: we may be able to save bytes by omitting property accesses if the
// object of the member expression is already included in the inferred deps
useEffect(() => {
console.log(bar?.baz);
console.log(bar.qux);
});
function f() {
console.log(foo);
}
// No inferred dep array, the argument is not a lambda
useEffect(f);
useEffectWrapper(() => {
console.log(foo);
});
}

View File

@@ -91,7 +91,7 @@ function Parent(t0) {
type: "div",
ref: ref,
key: null,
props: { children: children },
props: { ref: ref, children: children },
};
}
$[0] = children;
@@ -180,7 +180,7 @@ function ParentAndRefAndKey(props) {
type: Parent,
ref: testRef,
key: "testKey",
props: { a: "a", b: { b: "b" }, c: C },
props: { a: "a", b: { b: "b" }, c: C, ref: testRef },
};
}
$[0] = t0;

View File

@@ -0,0 +1,75 @@
## Input
```javascript
import {Throw} from 'shared-runtime';
/**
* Note: this is disabled in the evaluator due to different devmode errors.
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
*
* Forget:
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: [
* 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
* 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
* ]
*/
function useFoo() {
const invalidTag = Throw;
/**
* Need to be careful to not parse `invalidTag` as a localVar (i.e. render
* Throw). Note that the jsx transform turns this into a string tag:
* `jsx("invalidTag"...
*/
return <invalidTag val={{val: 2}} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Throw } from "shared-runtime";
/**
* Note: this is disabled in the evaluator due to different devmode errors.
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
*
* Forget:
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: [
* 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
* 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
* ]
*/
function useFoo() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <invalidTag val={{ val: 2 }} />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```

View File

@@ -0,0 +1,29 @@
import {Throw} from 'shared-runtime';
/**
* Note: this is disabled in the evaluator due to different devmode errors.
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
*
* Forget:
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: [
* 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
* 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
* ]
*/
function useFoo() {
const invalidTag = Throw;
/**
* Need to be careful to not parse `invalidTag` as a localVar (i.e. render
* Throw). Note that the jsx transform turns this into a string tag:
* `jsx("invalidTag"...
*/
return <invalidTag val={{val: 2}} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};

View File

@@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime";
function useFoo(t0) {
const $ = _c(1);
const { cond } = t0;
const MyLocal = SharedRuntime;
if (cond) {
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <MyLocal.Text value={4} />;
t1 = <SharedRuntime.Text value={4} />;
$[0] = t1;
} else {
t1 = $[0];

View File

@@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
function useFoo() {
const $ = _c(1);
const MyLocal = SharedRuntime;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <MyLocal.Text value={4} />;
t0 = <SharedRuntime.Text value={4} />;
$[0] = t0;
} else {
t0 = $[0];

View File

@@ -0,0 +1,59 @@
## Input
```javascript
import * as SharedRuntime from 'shared-runtime';
import {invoke} from 'shared-runtime';
function useComponentFactory({name}) {
const localVar = SharedRuntime;
const cb = () => <localVar.Stringify>hello world {name}</localVar.Stringify>;
return invoke(cb);
}
export const FIXTURE_ENTRYPOINT = {
fn: useComponentFactory,
params: [{name: 'sathya'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
import { invoke } from "shared-runtime";
function useComponentFactory(t0) {
const $ = _c(4);
const { name } = t0;
let t1;
if ($[0] !== name) {
t1 = () => (
<SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>
);
$[0] = name;
$[1] = t1;
} else {
t1 = $[1];
}
const cb = t1;
let t2;
if ($[2] !== cb) {
t2 = invoke(cb);
$[2] = cb;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: useComponentFactory,
params: [{ name: "sathya" }],
};
```
### Eval output
(kind: ok) <div>{"children":["hello world ","sathya"]}</div>

View File

@@ -0,0 +1,12 @@
import * as SharedRuntime from 'shared-runtime';
import {invoke} from 'shared-runtime';
function useComponentFactory({name}) {
const localVar = SharedRuntime;
const cb = () => <localVar.Stringify>hello world {name}</localVar.Stringify>;
return invoke(cb);
}
export const FIXTURE_ENTRYPOINT = {
fn: useComponentFactory,
params: [{name: 'sathya'}],
};

View File

@@ -0,0 +1,45 @@
## Input
```javascript
import * as SharedRuntime from 'shared-runtime';
function Component({name}) {
const localVar = SharedRuntime;
return <localVar.Stringify>hello world {name}</localVar.Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{name: 'sathya'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
function Component(t0) {
const $ = _c(2);
const { name } = t0;
let t1;
if ($[0] !== name) {
t1 = <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
$[0] = name;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ name: "sathya" }],
};
```
### Eval output
(kind: ok) <div>{"children":["hello world ","sathya"]}</div>

View File

@@ -0,0 +1,10 @@
import * as SharedRuntime from 'shared-runtime';
function Component({name}) {
const localVar = SharedRuntime;
return <localVar.Stringify>hello world {name}</localVar.Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{name: 'sathya'}],
};

View File

@@ -0,0 +1,44 @@
## Input
```javascript
import * as SharedRuntime from 'shared-runtime';
function Component({name}) {
return <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{name: 'sathya'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
function Component(t0) {
const $ = _c(2);
const { name } = t0;
let t1;
if ($[0] !== name) {
t1 = <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
$[0] = name;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ name: "sathya" }],
};
```
### Eval output
(kind: ok) <div>{"children":["hello world ","sathya"]}</div>

View File

@@ -0,0 +1,9 @@
import * as SharedRuntime from 'shared-runtime';
function Component({name}) {
return <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{name: 'sathya'}],
};

View File

@@ -25,10 +25,9 @@ import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
function useFoo() {
const $ = _c(1);
const MyLocal = SharedRuntime;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const callback = () => <MyLocal.Text value={4} />;
const callback = () => <SharedRuntime.Text value={4} />;
t0 = callback();
$[0] = t0;

View File

@@ -32,7 +32,7 @@ function Component() {
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
setState((s) => s + 1);
setState(_temp);
};
$[0] = t0;
} else {
@@ -61,6 +61,9 @@ function Component() {
}
return t2;
}
function _temp(s) {
return s + 1;
}
```

View File

@@ -101,4 +101,4 @@ export const FIXTURE_ENTRYPOINT = {
```
### Eval output
(kind: ok) [{"Children":{"map":"[[ function params=3 ]]","forEach":"[[ function params=3 ]]","count":"[[ function params=1 ]]","toArray":"[[ function params=1 ]]","only":"[[ function params=1 ]]"},"Component":"[[ function params=3 ]]","PureComponent":"[[ function params=3 ]]","__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE":{"H":{"readContext":"[[ function params=1 ]]","use":"[[ function params=1 ]]","useCallback":"[[ function params=2 ]]","useContext":"[[ function params=1 ]]","useEffect":"[[ function params=2 ]]","useImperativeHandle":"[[ function params=3 ]]","useInsertionEffect":"[[ function params=2 ]]","useLayoutEffect":"[[ function params=2 ]]","useMemo":"[[ function params=2 ]]","useReducer":"[[ function params=3 ]]","useRef":"[[ function params=1 ]]","useState":"[[ function params=1 ]]","useDebugValue":"[[ function params=0 ]]","useDeferredValue":"[[ function params=2 ]]","useTransition":"[[ function params=0 ]]","useSyncExternalStore":"[[ function params=3 ]]","useId":"[[ function params=0 ]]","useCacheRefresh":"[[ function params=0 ]]","useMemoCache":"[[ function params=1 ]]","useEffectEvent":"[[ function params=1 ]]","useHostTransitionStatus":"[[ function params=0 ]]","useFormState":"[[ function params=2 ]]","useActionState":"[[ function params=2 ]]","useOptimistic":"[[ function params=1 ]]"},"A":{"getCacheForType":"[[ function params=1 ]]","getOwner":"[[ function params=0 ]]"},"T":null,"S":"[[ function params=2 ]]","actQueue":["[[ function params=0 ]]","[[ function params=1 ]]"],"isBatchingLegacy":false,"didScheduleLegacyUpdate":false,"didUsePromise":false,"thrownErrors":[],"getCurrentStack":"[[ function params=0 ]]"},"__COMPILER_RUNTIME":{"c":"[[ function params=1 ]]"},"act":"[[ function params=1 ]]","cache":"[[ function params=1 ]]","captureOwnerStack":"[[ function params=0 ]]","cloneElement":"[[ function params=3 ]]","createContext":"[[ function params=1 ]]","createElement":"[[ function params=3 ]]","createRef":"[[ function params=0 ]]","experimental_useEffectEvent":"[[ function params=1 ]]","experimental_useOptimistic":"[[ function params=2 ]]","forwardRef":"[[ function params=1 ]]","isValidElement":"[[ function params=1 ]]","lazy":"[[ function params=1 ]]","memo":"[[ function params=2 ]]","startTransition":"[[ function params=1 ]]","unstable_getCacheForType":"[[ function params=1 ]]","unstable_postpone":"[[ function params=1 ]]","unstable_useCacheRefresh":"[[ function params=0 ]]","use":"[[ function params=1 ]]","useActionState":"[[ function params=3 ]]","useCallback":"[[ function params=2 ]]","useContext":"[[ function params=1 ]]","useDebugValue":"[[ function params=2 ]]","useDeferredValue":"[[ function params=2 ]]","useEffect":"[[ function params=2 ]]","useId":"[[ function params=0 ]]","useImperativeHandle":"[[ function params=3 ]]","useInsertionEffect":"[[ function params=2 ]]","useLayoutEffect":"[[ function params=2 ]]","useMemo":"[[ function params=2 ]]","useOptimistic":"[[ function params=2 ]]","useReducer":"[[ function params=3 ]]","useRef":"[[ function params=1 ]]","useState":"[[ function params=1 ]]","useSyncExternalStore":"[[ function params=3 ]]","useTransition":"[[ function params=0 ]]","version":"19.0.0-experimental-0bc30748-20241028"},"[[ cyclic ref *6 ]]",true,true,true,true,"[[ function params=0 ]]",true,"[[ function params=0 ]]"]
(kind: ok) [{"Children":{"map":"[[ function params=3 ]]","forEach":"[[ function params=3 ]]","count":"[[ function params=1 ]]","toArray":"[[ function params=1 ]]","only":"[[ function params=1 ]]"},"Component":"[[ function params=3 ]]","PureComponent":"[[ function params=3 ]]","__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE":{"H":{"readContext":"[[ function params=1 ]]","use":"[[ function params=1 ]]","useCallback":"[[ function params=2 ]]","useContext":"[[ function params=1 ]]","useEffect":"[[ function params=2 ]]","useImperativeHandle":"[[ function params=3 ]]","useInsertionEffect":"[[ function params=2 ]]","useLayoutEffect":"[[ function params=2 ]]","useMemo":"[[ function params=2 ]]","useReducer":"[[ function params=3 ]]","useRef":"[[ function params=1 ]]","useState":"[[ function params=1 ]]","useDebugValue":"[[ function params=0 ]]","useDeferredValue":"[[ function params=2 ]]","useTransition":"[[ function params=0 ]]","useSyncExternalStore":"[[ function params=3 ]]","useId":"[[ function params=0 ]]","useCacheRefresh":"[[ function params=0 ]]","useMemoCache":"[[ function params=1 ]]","useEffectEvent":"[[ function params=1 ]]","useHostTransitionStatus":"[[ function params=0 ]]","useFormState":"[[ function params=2 ]]","useActionState":"[[ function params=2 ]]","useOptimistic":"[[ function params=1 ]]"},"A":{"getCacheForType":"[[ function params=1 ]]","getOwner":"[[ function params=0 ]]"},"T":null,"S":"[[ function params=2 ]]","actQueue":["[[ function params=0 ]]","[[ function params=1 ]]"],"isBatchingLegacy":false,"didScheduleLegacyUpdate":false,"didUsePromise":false,"thrownErrors":[],"getCurrentStack":"[[ function params=0 ]]"},"__COMPILER_RUNTIME":{"c":"[[ function params=1 ]]"},"act":"[[ function params=1 ]]","cache":"[[ function params=1 ]]","captureOwnerStack":"[[ function params=0 ]]","cloneElement":"[[ function params=3 ]]","createContext":"[[ function params=1 ]]","createElement":"[[ function params=3 ]]","createRef":"[[ function params=0 ]]","experimental_useEffectEvent":"[[ function params=1 ]]","experimental_useOptimistic":"[[ function params=2 ]]","experimental_useResourceEffect":"[[ function params=0 ]]","forwardRef":"[[ function params=1 ]]","isValidElement":"[[ function params=1 ]]","lazy":"[[ function params=1 ]]","memo":"[[ function params=2 ]]","startTransition":"[[ function params=1 ]]","unstable_getCacheForType":"[[ function params=1 ]]","unstable_postpone":"[[ function params=1 ]]","unstable_useCacheRefresh":"[[ function params=0 ]]","use":"[[ function params=1 ]]","useActionState":"[[ function params=3 ]]","useCallback":"[[ function params=2 ]]","useContext":"[[ function params=1 ]]","useDebugValue":"[[ function params=2 ]]","useDeferredValue":"[[ function params=2 ]]","useEffect":"[[ function params=2 ]]","useId":"[[ function params=0 ]]","useImperativeHandle":"[[ function params=3 ]]","useInsertionEffect":"[[ function params=2 ]]","useLayoutEffect":"[[ function params=2 ]]","useMemo":"[[ function params=2 ]]","useOptimistic":"[[ function params=2 ]]","useReducer":"[[ function params=3 ]]","useRef":"[[ function params=1 ]]","useState":"[[ function params=1 ]]","useSyncExternalStore":"[[ function params=3 ]]","useTransition":"[[ function params=0 ]]","version":"19.0.0-experimental-4beb1fd8-20241118"},"[[ cyclic ref *6 ]]",true,true,true,true,"[[ function params=0 ]]",true,"[[ function params=0 ]]"]

View File

@@ -39,7 +39,7 @@ import { useState } from "react";
function Component(props) {
const $ = _c(1);
const [_state, setState] = useState();
const [, setState] = useState();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const a = () => b();

View File

@@ -0,0 +1,80 @@
## Input
```javascript
// @inferEffectDependencies
import {useEffect} from 'react';
import {makeObject_Primitives, print} from 'shared-runtime';
/**
* Note that `obj` is currently added to the effect dependency array, even
* though it's non-reactive due to memoization.
*
* This is a TODO in effect dependency inference. Note that we cannot simply
* filter out non-reactive effect dependencies, as some non-reactive (by data
* flow) values become reactive due to scope pruning. See the
* `infer-effect-deps/pruned-nonreactive-obj` fixture for why this matters.
*
* Realizing that this `useEffect` should have an empty dependency array
* requires effect dependency inference to be structured similarly to memo
* dependency inference.
* Pass 1: add all potential dependencies regardless of dataflow reactivity
* Pass 2: (todo) prune non-reactive dependencies
*
* Note that instruction reordering should significantly reduce scope pruning
*/
function NonReactiveDepInEffect() {
const obj = makeObject_Primitives();
useEffect(() => print(obj));
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useEffect } from "react";
import { makeObject_Primitives, print } from "shared-runtime";
/**
* Note that `obj` is currently added to the effect dependency array, even
* though it's non-reactive due to memoization.
*
* This is a TODO in effect dependency inference. Note that we cannot simply
* filter out non-reactive effect dependencies, as some non-reactive (by data
* flow) values become reactive due to scope pruning. See the
* `infer-effect-deps/pruned-nonreactive-obj` fixture for why this matters.
*
* Realizing that this `useEffect` should have an empty dependency array
* requires effect dependency inference to be structured similarly to memo
* dependency inference.
* Pass 1: add all potential dependencies regardless of dataflow reactivity
* Pass 2: (todo) prune non-reactive dependencies
*
* Note that instruction reordering should significantly reduce scope pruning
*/
function NonReactiveDepInEffect() {
const $ = _c(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = makeObject_Primitives();
$[0] = t0;
} else {
t0 = $[0];
}
const obj = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => print(obj);
$[1] = t1;
} else {
t1 = $[1];
}
useEffect(t1, [obj]);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,25 @@
// @inferEffectDependencies
import {useEffect} from 'react';
import {makeObject_Primitives, print} from 'shared-runtime';
/**
* Note that `obj` is currently added to the effect dependency array, even
* though it's non-reactive due to memoization.
*
* This is a TODO in effect dependency inference. Note that we cannot simply
* filter out non-reactive effect dependencies, as some non-reactive (by data
* flow) values become reactive due to scope pruning. See the
* `infer-effect-deps/pruned-nonreactive-obj` fixture for why this matters.
*
* Realizing that this `useEffect` should have an empty dependency array
* requires effect dependency inference to be structured similarly to memo
* dependency inference.
* Pass 1: add all potential dependencies regardless of dataflow reactivity
* Pass 2: (todo) prune non-reactive dependencies
*
* Note that instruction reordering should significantly reduce scope pruning
*/
function NonReactiveDepInEffect() {
const obj = makeObject_Primitives();
useEffect(() => print(obj));
}

View File

@@ -0,0 +1,89 @@
## Input
```javascript
// @inferEffectDependencies
import {useEffect} from 'react';
import {print} from 'shared-runtime';
/**
* We never include a .current access in a dep array because it may be a ref access.
* This might over-capture objects that are not refs and happen to have fields named
* current, but that should be a rare case and the result would still be correct
* (assuming the effect is idempotent). In the worst case, you can always write a manual
* dep array.
*/
function RefsInEffects() {
const ref = useRefHelper();
const wrapped = useDeeperRefHelper();
useEffect(() => {
print(ref.current);
print(wrapped.foo.current);
});
}
function useRefHelper() {
return useRef(0);
}
function useDeeperRefHelper() {
return {foo: useRefHelper()};
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useEffect } from "react";
import { print } from "shared-runtime";
/**
* We never include a .current access in a dep array because it may be a ref access.
* This might over-capture objects that are not refs and happen to have fields named
* current, but that should be a rare case and the result would still be correct
* (assuming the effect is idempotent). In the worst case, you can always write a manual
* dep array.
*/
function RefsInEffects() {
const $ = _c(3);
const ref = useRefHelper();
const wrapped = useDeeperRefHelper();
let t0;
if ($[0] !== ref.current || $[1] !== wrapped.foo.current) {
t0 = () => {
print(ref.current);
print(wrapped.foo.current);
};
$[0] = ref.current;
$[1] = wrapped.foo.current;
$[2] = t0;
} else {
t0 = $[2];
}
useEffect(t0, [ref, wrapped.foo]);
}
function useRefHelper() {
return useRef(0);
}
function useDeeperRefHelper() {
const $ = _c(2);
const t0 = useRefHelper();
let t1;
if ($[0] !== t0) {
t1 = { foo: t0 };
$[0] = t0;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,27 @@
// @inferEffectDependencies
import {useEffect} from 'react';
import {print} from 'shared-runtime';
/**
* We never include a .current access in a dep array because it may be a ref access.
* This might over-capture objects that are not refs and happen to have fields named
* current, but that should be a rare case and the result would still be correct
* (assuming the effect is idempotent). In the worst case, you can always write a manual
* dep array.
*/
function RefsInEffects() {
const ref = useRefHelper();
const wrapped = useDeeperRefHelper();
useEffect(() => {
print(ref.current);
print(wrapped.foo.current);
});
}
function useRefHelper() {
return useRef(0);
}
function useDeeperRefHelper() {
return {foo: useRefHelper()};
}

View File

@@ -0,0 +1,51 @@
## Input
```javascript
// @inferEffectDependencies
import {useEffect, useRef} from 'react';
import {print} from 'shared-runtime';
/**
* Special case of `infer-effect-deps/nonreactive-dep`.
*
* We know that local `useRef` return values are stable, regardless of
* inferred memoization.
*/
function NonReactiveRefInEffect() {
const ref = useRef('initial value');
useEffect(() => print(ref.current));
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useEffect, useRef } from "react";
import { print } from "shared-runtime";
/**
* Special case of `infer-effect-deps/nonreactive-dep`.
*
* We know that local `useRef` return values are stable, regardless of
* inferred memoization.
*/
function NonReactiveRefInEffect() {
const $ = _c(1);
const ref = useRef("initial value");
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => print(ref.current);
$[0] = t0;
} else {
t0 = $[0];
}
useEffect(t0, [ref]);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,14 @@
// @inferEffectDependencies
import {useEffect, useRef} from 'react';
import {print} from 'shared-runtime';
/**
* Special case of `infer-effect-deps/nonreactive-dep`.
*
* We know that local `useRef` return values are stable, regardless of
* inferred memoization.
*/
function NonReactiveRefInEffect() {
const ref = useRef('initial value');
useEffect(() => print(ref.current));
}

View File

@@ -0,0 +1,46 @@
## Input
```javascript
// @inferEffectDependencies
import {useEffect} from 'react';
import {print} from 'shared-runtime';
/**
* This compiled output is technically incorrect but this is currently the same
* case as a bailout (an effect that overfires).
*
* To ensure an empty deps array is passed, we need special case
* `InferEffectDependencies` for outlined functions (likely easier) or run it
* before OutlineFunctions
*/
function OutlinedFunctionInEffect() {
useEffect(() => print('hello world!'));
}
```
## Code
```javascript
// @inferEffectDependencies
import { useEffect } from "react";
import { print } from "shared-runtime";
/**
* This compiled output is technically incorrect but this is currently the same
* case as a bailout (an effect that overfires).
*
* To ensure an empty deps array is passed, we need special case
* `InferEffectDependencies` for outlined functions (likely easier) or run it
* before OutlineFunctions
*/
function OutlinedFunctionInEffect() {
useEffect(_temp, []);
}
function _temp() {
return print("hello world!");
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,14 @@
// @inferEffectDependencies
import {useEffect} from 'react';
import {print} from 'shared-runtime';
/**
* This compiled output is technically incorrect but this is currently the same
* case as a bailout (an effect that overfires).
*
* To ensure an empty deps array is passed, we need special case
* `InferEffectDependencies` for outlined functions (likely easier) or run it
* before OutlineFunctions
*/
function OutlinedFunctionInEffect() {
useEffect(() => print('hello world!'));
}

View File

@@ -0,0 +1,53 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
/**
* TODO: we're currently bailing out because `contextVar` is a context variable
* and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad
* sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted
* `LoadContext` and `PropertyLoad` instructions into the outer function, which
* we took as eligible dependencies.
*
* One solution is to simply record `LoadContext` identifiers into the
* temporaries sidemap when the instruction occurs *after* the context
* variable's mutable range.
*/
function Foo(props) {
let contextVar;
if (props.cond) {
contextVar = {val: 2};
} else {
contextVar = {};
}
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{cond: true}],
};
```
## Error
```
22 | }
23 |
> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]);
| ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24)
25 |
26 | return <Stringify cb={cb} shouldInvokeFns={true} />;
27 | }
```

View File

@@ -0,0 +1,32 @@
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
/**
* TODO: we're currently bailing out because `contextVar` is a context variable
* and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad
* sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted
* `LoadContext` and `PropertyLoad` instructions into the outer function, which
* we took as eligible dependencies.
*
* One solution is to simply record `LoadContext` identifiers into the
* temporaries sidemap when the instruction occurs *after* the context
* variable's mutable range.
*/
function Foo(props) {
let contextVar;
if (props.cond) {
contextVar = {val: 2};
} else {
contextVar = {};
}
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{cond: true}],
};

View File

@@ -44,8 +44,6 @@ function Component({propA, propB}) {
| ^^^^^^^^^^^^^^^^^
> 14 | }, [propA?.a, propB.x.y]);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14)
CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14)
15 | }
16 |
```

View File

@@ -28,7 +28,7 @@ import { useCallback, useTransition } from "react";
function useFoo() {
const $ = _c(1);
const [t, start] = useTransition();
const [, start] = useTransition();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {

View File

@@ -1,81 +0,0 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
function Foo(props) {
let contextVar;
if (props.cond) {
contextVar = {val: 2};
} else {
contextVar = {};
}
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{cond: true}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
import { useCallback } from "react";
import { Stringify } from "shared-runtime";
function Foo(props) {
const $ = _c(6);
let contextVar;
if ($[0] !== props.cond) {
if (props.cond) {
contextVar = { val: 2 };
} else {
contextVar = {};
}
$[0] = props.cond;
$[1] = contextVar;
} else {
contextVar = $[1];
}
const t0 = contextVar;
let t1;
if ($[2] !== t0.val) {
t1 = () => [contextVar.val];
$[2] = t0.val;
$[3] = t1;
} else {
t1 = $[3];
}
contextVar;
const cb = t1;
let t2;
if ($[4] !== cb) {
t2 = <Stringify cb={cb} shouldInvokeFns={true} />;
$[4] = cb;
$[5] = t2;
} else {
t2 = $[5];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{ cond: true }],
};
```
### Eval output
(kind: ok) <div>{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}</div>

View File

@@ -1,21 +0,0 @@
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
function Foo(props) {
let contextVar;
if (props.cond) {
contextVar = {val: 2};
} else {
contextVar = {};
}
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{cond: true}],
};

View File

@@ -45,18 +45,16 @@ function Foo(props) {
} else {
x = $[1];
}
const t0 = x;
let t1;
if ($[2] !== t0) {
t1 = () => [x];
$[2] = t0;
$[3] = t1;
let t0;
if ($[2] !== x) {
t0 = () => [x];
$[2] = x;
$[3] = t0;
} else {
t1 = $[3];
t0 = $[3];
}
x;
const cb = t1;
const cb = t0;
return cb;
}

View File

@@ -70,28 +70,26 @@ function useBar(t0, cond) {
if (cond) {
x = b;
}
const t2 = x;
let t3;
if ($[1] !== a || $[2] !== t2) {
t3 = () => [a, x];
let t2;
if ($[1] !== a || $[2] !== x) {
t2 = () => [a, x];
$[1] = a;
$[2] = t2;
$[3] = t3;
$[2] = x;
$[3] = t2;
} else {
t3 = $[3];
t2 = $[3];
}
x;
const cb = t3;
let t4;
const cb = t2;
let t3;
if ($[4] !== cb) {
t4 = <Stringify cb={cb} shouldInvoke={true} />;
t3 = <Stringify cb={cb} shouldInvoke={true} />;
$[4] = cb;
$[5] = t4;
$[5] = t3;
} else {
t4 = $[5];
t3 = $[5];
}
return t4;
return t3;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -34,9 +34,9 @@ function useFoo(t0) {
const $ = _c(2);
const { a } = t0;
let t1;
if ($[0] !== a.b) {
if ($[0] !== a.b?.c.d?.e) {
t1 = <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
$[0] = a.b;
$[0] = a.b?.c.d?.e;
$[1] = t1;
} else {
t1 = $[1];

View File

@@ -0,0 +1,119 @@
## Input
```javascript
// @inferEffectDependencies
import {useIdentity, mutate, makeObject} from 'shared-runtime';
import {useEffect} from 'react';
/**
* When a semantically non-reactive value has a pruned scope (i.e. the object
* identity becomes reactive, but the underlying value it represents should be
* constant), the compiler can choose to either
* - add it as a dependency (and rerun the effect)
* - not add it as a dependency
*
* We keep semantically non-reactive values in both memo block and effect
* dependency arrays to avoid versioning invariants e.g. `x !== y.aliasedX`.
* ```js
* function Component() {
* // obj is semantically non-reactive, but its memo scope is pruned due to
* // the interleaving hook call
* const obj = {};
* useHook();
* write(obj);
*
* const ref = useRef();
*
* // this effect needs to be rerun when obj's referential identity changes,
* // because it might alias obj to a useRef / mutable store.
* useEffect(() => ref.current = obj, ???);
*
* // in a custom hook (or child component), the user might expect versioning
* // invariants to hold
* useHook(ref, obj);
* }
*
* // defined elsewhere
* function useHook(someRef, obj) {
* useEffect(
* () => assert(someRef.current === obj),
* [someRef, obj]
* );
* }
* ```
*/
function PrunedNonReactive() {
const obj = makeObject();
useIdentity(null);
mutate(obj);
useEffect(() => print(obj.value));
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useIdentity, mutate, makeObject } from "shared-runtime";
import { useEffect } from "react";
/**
* When a semantically non-reactive value has a pruned scope (i.e. the object
* identity becomes reactive, but the underlying value it represents should be
* constant), the compiler can choose to either
* - add it as a dependency (and rerun the effect)
* - not add it as a dependency
*
* We keep semantically non-reactive values in both memo block and effect
* dependency arrays to avoid versioning invariants e.g. `x !== y.aliasedX`.
* ```js
* function Component() {
* // obj is semantically non-reactive, but its memo scope is pruned due to
* // the interleaving hook call
* const obj = {};
* useHook();
* write(obj);
*
* const ref = useRef();
*
* // this effect needs to be rerun when obj's referential identity changes,
* // because it might alias obj to a useRef / mutable store.
* useEffect(() => ref.current = obj, ???);
*
* // in a custom hook (or child component), the user might expect versioning
* // invariants to hold
* useHook(ref, obj);
* }
*
* // defined elsewhere
* function useHook(someRef, obj) {
* useEffect(
* () => assert(someRef.current === obj),
* [someRef, obj]
* );
* }
* ```
*/
function PrunedNonReactive() {
const $ = _c(2);
const obj = makeObject();
useIdentity(null);
mutate(obj);
let t0;
if ($[0] !== obj.value) {
t0 = () => print(obj.value);
$[0] = obj.value;
$[1] = t0;
} else {
t0 = $[1];
}
useEffect(t0, [obj.value]);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,48 @@
// @inferEffectDependencies
import {useIdentity, mutate, makeObject} from 'shared-runtime';
import {useEffect} from 'react';
/**
* When a semantically non-reactive value has a pruned scope (i.e. the object
* identity becomes reactive, but the underlying value it represents should be
* constant), the compiler can choose to either
* - add it as a dependency (and rerun the effect)
* - not add it as a dependency
*
* We keep semantically non-reactive values in both memo block and effect
* dependency arrays to avoid versioning invariants e.g. `x !== y.aliasedX`.
* ```js
* function Component() {
* // obj is semantically non-reactive, but its memo scope is pruned due to
* // the interleaving hook call
* const obj = {};
* useHook();
* write(obj);
*
* const ref = useRef();
*
* // this effect needs to be rerun when obj's referential identity changes,
* // because it might alias obj to a useRef / mutable store.
* useEffect(() => ref.current = obj, ???);
*
* // in a custom hook (or child component), the user might expect versioning
* // invariants to hold
* useHook(ref, obj);
* }
*
* // defined elsewhere
* function useHook(someRef, obj) {
* useEffect(
* () => assert(someRef.current === obj),
* [someRef, obj]
* );
* }
* ```
*/
function PrunedNonReactive() {
const obj = makeObject();
useIdentity(null);
mutate(obj);
useEffect(() => print(obj.value));
}

View File

@@ -32,7 +32,7 @@ function Component(props) {
const $ = _c(5);
React.useContext(FooContext);
const ref = React.useRef();
const [x, setX] = React.useState(false);
const [, setX] = React.useState(false);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {

View File

@@ -61,8 +61,8 @@ import { useState } from "react";
function Component(props) {
const $ = _c(5);
const [x, setX] = useState(false);
const [y, setY] = useState(false);
const [, setX] = useState(false);
const [, setY] = useState(false);
let setState;
if (props.cond) {
setState = setX;

View File

@@ -0,0 +1,49 @@
## Input
```javascript
// @inferEffectDependencies
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function ReactiveMemberExprMerge({propVal}) {
const obj = {a: {b: propVal}};
useEffect(() => print(obj.a, obj.a.b));
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useEffect } from "react";
import { print } from "shared-runtime";
function ReactiveMemberExprMerge(t0) {
const $ = _c(4);
const { propVal } = t0;
let t1;
if ($[0] !== propVal) {
t1 = { a: { b: propVal } };
$[0] = propVal;
$[1] = t1;
} else {
t1 = $[1];
}
const obj = t1;
let t2;
if ($[2] !== obj.a) {
t2 = () => print(obj.a, obj.a.b);
$[2] = obj.a;
$[3] = t2;
} else {
t2 = $[3];
}
useEffect(t2, [obj.a]);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,8 @@
// @inferEffectDependencies
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function ReactiveMemberExprMerge({propVal}) {
const obj = {a: {b: propVal}};
useEffect(() => print(obj.a, obj.a.b));
}

View File

@@ -0,0 +1,49 @@
## Input
```javascript
// @inferEffectDependencies
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function ReactiveMemberExpr({propVal}) {
const obj = {a: {b: propVal}};
useEffect(() => print(obj.a.b));
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useEffect } from "react";
import { print } from "shared-runtime";
function ReactiveMemberExpr(t0) {
const $ = _c(4);
const { propVal } = t0;
let t1;
if ($[0] !== propVal) {
t1 = { a: { b: propVal } };
$[0] = propVal;
$[1] = t1;
} else {
t1 = $[1];
}
const obj = t1;
let t2;
if ($[2] !== obj.a.b) {
t2 = () => print(obj.a.b);
$[2] = obj.a.b;
$[3] = t2;
} else {
t2 = $[3];
}
useEffect(t2, [obj.a.b]);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,8 @@
// @inferEffectDependencies
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function ReactiveMemberExpr({propVal}) {
const obj = {a: {b: propVal}};
useEffect(() => print(obj.a.b));
}

View File

@@ -0,0 +1,60 @@
## Input
```javascript
// @inferEffectDependencies
import {useEffect} from 'react';
import {print} from 'shared-runtime';
// TODO: take optional chains as dependencies
function ReactiveMemberExpr({cond, propVal}) {
const obj = {a: cond ? {b: propVal} : null};
useEffect(() => print(obj.a?.b));
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useEffect } from "react";
import { print } from "shared-runtime";
// TODO: take optional chains as dependencies
function ReactiveMemberExpr(t0) {
const $ = _c(7);
const { cond, propVal } = t0;
let t1;
if ($[0] !== cond || $[1] !== propVal) {
t1 = cond ? { b: propVal } : null;
$[0] = cond;
$[1] = propVal;
$[2] = t1;
} else {
t1 = $[2];
}
let t2;
if ($[3] !== t1) {
t2 = { a: t1 };
$[3] = t1;
$[4] = t2;
} else {
t2 = $[4];
}
const obj = t2;
let t3;
if ($[5] !== obj.a?.b) {
t3 = () => print(obj.a?.b);
$[5] = obj.a?.b;
$[6] = t3;
} else {
t3 = $[6];
}
useEffect(t3, [obj.a]);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,9 @@
// @inferEffectDependencies
import {useEffect} from 'react';
import {print} from 'shared-runtime';
// TODO: take optional chains as dependencies
function ReactiveMemberExpr({cond, propVal}) {
const obj = {a: cond ? {b: propVal} : null};
useEffect(() => print(obj.a?.b));
}

View File

@@ -0,0 +1,102 @@
## Input
```javascript
import {useRef, forwardRef} from 'react';
import {Stringify} from 'shared-runtime';
/**
* Fixture showing that Ref types may be reactive.
* We should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*/
function Parent({cond}) {
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
return <Child ref={ref} />;
}
function ChildImpl(_props, ref) {
const cb = () => ref.current;
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
const Child = forwardRef(ChildImpl);
export const FIXTURE_ENTRYPOINT = {
fn: Parent,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: false}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef, forwardRef } from "react";
import { Stringify } from "shared-runtime";
/**
* Fixture showing that Ref types may be reactive.
* We should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*/
function Parent(t0) {
const $ = _c(2);
const { cond } = t0;
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
let t1;
if ($[0] !== ref) {
t1 = <Child ref={ref} />;
$[0] = ref;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
function ChildImpl(_props, ref) {
const $ = _c(4);
let t0;
if ($[0] !== ref) {
t0 = () => ref.current;
$[0] = ref;
$[1] = t0;
} else {
t0 = $[1];
}
const cb = t0;
let t1;
if ($[2] !== cb) {
t1 = <Stringify cb={cb} shouldInvokeFns={true} />;
$[2] = cb;
$[3] = t1;
} else {
t1 = $[3];
}
return t1;
}
const Child = forwardRef(ChildImpl);
export const FIXTURE_ENTRYPOINT = {
fn: Parent,
params: [{ cond: true }],
sequentialRenders: [{ cond: true }, { cond: false }],
};
```
### Eval output
(kind: ok) <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>

View File

@@ -0,0 +1,29 @@
import {useRef, forwardRef} from 'react';
import {Stringify} from 'shared-runtime';
/**
* Fixture showing that Ref types may be reactive.
* We should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*/
function Parent({cond}) {
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
return <Child ref={ref} />;
}
function ChildImpl(_props, ref) {
const cb = () => ref.current;
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
const Child = forwardRef(ChildImpl);
export const FIXTURE_ENTRYPOINT = {
fn: Parent,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: false}],
};

View File

@@ -0,0 +1,79 @@
## Input
```javascript
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
/**
* Fixture showing that Ref types may be reactive.
* We should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*/
function Component({cond}) {
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
const cb = () => ref.current;
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: false}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
import { Stringify } from "shared-runtime";
/**
* Fixture showing that Ref types may be reactive.
* We should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*/
function Component(t0) {
const $ = _c(4);
const { cond } = t0;
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
let t1;
if ($[0] !== ref) {
t1 = () => ref.current;
$[0] = ref;
$[1] = t1;
} else {
t1 = $[1];
}
const cb = t1;
let t2;
if ($[2] !== cb) {
t2 = <Stringify cb={cb} shouldInvokeFns={true} />;
$[2] = cb;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: true }],
sequentialRenders: [{ cond: true }, { cond: false }],
};
```
### Eval output
(kind: ok) <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>

View File

@@ -0,0 +1,22 @@
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
/**
* Fixture showing that Ref types may be reactive.
* We should always take a dependency on ref values (the outer box) as
* they may be reactive. Pruning should be done in
* `pruneNonReactiveDependencies`
*/
function Component({cond}) {
const ref1 = useRef(1);
const ref2 = useRef(2);
const ref = cond ? ref1 : ref2;
const cb = () => ref.current;
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: false}],
};

View File

@@ -0,0 +1,49 @@
## Input
```javascript
// @inferEffectDependencies
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function ReactiveVariable({propVal}) {
const arr = [propVal];
useEffect(() => print(arr));
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useEffect } from "react";
import { print } from "shared-runtime";
function ReactiveVariable(t0) {
const $ = _c(4);
const { propVal } = t0;
let t1;
if ($[0] !== propVal) {
t1 = [propVal];
$[0] = propVal;
$[1] = t1;
} else {
t1 = $[1];
}
const arr = t1;
let t2;
if ($[2] !== arr) {
t2 = () => print(arr);
$[2] = arr;
$[3] = t2;
} else {
t2 = $[3];
}
useEffect(t2, [arr]);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,8 @@
// @inferEffectDependencies
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function ReactiveVariable({propVal}) {
const arr = [propVal];
useEffect(() => print(arr));
}

Some files were not shown because too many files have changed in this diff Show More