Compare commits

...

276 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
Andrew Clark
7aa5dda3b3 Bump RC version to 19.0.0-rc.1 (#31542) 2024-11-14 11:48:33 -05:00
Andrew Clark
988e217670 Turn on enableSiblingPrerendering in canary (#31541)
In preparation for the next RC, I set this feature flag to true
everywhere. I did not delete the feature flag yet, in case there are yet
more bugs to be discovered.

I also didn't remove the dynamic feature flag from the Meta builds; I'll
let the Meta folks handle that.
2024-11-14 11:48:14 -05:00
lauren
380f5d675d Fix sizebot (#31535)
Our CI workflows generally cache `**/node_modules` (note the glob, it
caches all transitive node_module directories) to speed up startup for
new jobs that don't change any dependencies. However it seems like one
of our caches got into a weird state (not sure how it happened) where
the `build` directory (used in various other scripts as the directory
for compiled React packages) would contain a `node_modules` directory as
well. This made sizebot size change messages very big since it would try
to compare every single file in `build/node_modules`.

The fix is to ensure we always clean the `build` directory before doing
anything with it. We can also delete that one problematic cache but this
PR is a little more resilient to other weird behavior with that
directory.
2024-11-13 15:13:46 -05:00
Sebastian Markbåge
7ac8e61211 Only log component level profiling for components that actually performed work (#31522)
This provides less context but skips a lot of noise.

Previously we were including parent components to provide context about
what is rendering but this turns out to be:

1) Very expensive due to the overhead of `performance.measure()` while
profiling.
2) Unactionable noise in the profile that hurt more than it added in
real apps with large trees.

This approach instead just add performance.measure calls for each
component that was marked as PerformedWork (which was used for this
purpose by React Profiler) or had any Effects.

Not everything gets marked with PerformedWork though. E.g. DOM nodes do
not but they can have significant render times since creating them takes
time. We might consider including them if a self-time threshold is met.

Because there is little to no context about the component anymore it
becomes really essential to get a feature from Chrome DevTools that can
link to something with more context like React DevTools.
2024-11-13 10:57:15 -05:00
mofeiZ
3770c11011 [compiler] repro for reactive ref.current accesses (#31519)
See test fixture
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31519).
* #31521
* __->__ #31519
2024-11-12 14:04:54 -05:00
Sebastian Markbåge
d9b3841ca6 Revert "Performance tracks are sorted by start time" hack (#31518)
This reverts commit d3bf32a95806b6d583ef041b8d83781cd686cfd8 which was
part of #30983

When you have very deep trees this trick can cause the top levels to
skew way too much from the real numbers. Creating unbalanced trees.

The bug should have been fixed in Chrome Canary now so that entries
added later are sorted to go first which should've addressed this issue.
2024-11-12 12:46:55 -05:00
mofeiZ
2ec26bc432 [compiler] Repro for mutable range edge case (#31479)
See test fixtures
2024-11-11 18:04:29 -05:00
Ricky
b836de613d Fix continuation bug (#31434)
## Overview

In `scheduleTaskForRootDuringMicrotask` we clear `root.callbackNode` if
the work loop is [suspended waiting on
data](ac3ca097ae/packages/react-reconciler/src/ReactFiberRootScheduler.js (L338)).

But we don't null check `root.callbackNode` before returning a
continuation in `performWorkOnRootViaSchedulerTask` where
`scheduleTaskForRootDuringMicrotask` is synchronously called, causing an
infinite loop when the only thing in the queue is something suspended
waiting on data.

This essentially restores the behavior from here:
https://github.com/facebook/react/pull/26328/files#diff-72ff2175ae3569037f0b16802a41b0cda2b2d66bb97f2bda78ed8445ed487b58L1168

Found by investigating the failures for
https://github.com/facebook/react/pull/31417

## TODO
- add a test

---------

Co-authored-by: Joe Savona <joesavona@fb.com>
2024-11-11 17:25:37 -05:00
Josh Story
ed15d5007c update flight readme wording (#31466) 2024-11-10 11:58:52 -08:00
Henry Q. Dineen
6e29479bff [devtools] allow non-coercible objects in formatConsoleArgumentsToSingleString (#31444)
## Summary

We have been getting unhandled `TypeError: Cannot convert object to
primitive value` errors in development that only occur when using
devtools. I tracked it down to `console.error()` calls coming from
Apollo Client where one of the arguments is an object without a
prototype (created with `Object.create(null)`). This causes
`formatConsoleArgumentsToSingleString()` in React's devtools to error as
the function does not defend against `String()` throwing an error.

My attempted fix is to introduce a `safeToString` function (naming
suggestions appreciated) which expects `String()` to throw on certain
object and in that case falls back to returning `[object Object]`, which
is what `String({})` would return.

## How did you test this change?

Added a new unit test.
2024-11-10 19:24:15 +00:00
Josh Story
ff595de29a [Flight] Add initial readme to react-server package (#31464)
This readme documents React Server Components from `react-server`
package enough to get an implementer started. It's not comprehensive but
it's a beginning point and crucially adds documentation for the
`prerender` API for Flight.
2024-11-08 16:07:37 -08:00
Jack Pope
989af12f72 Make prerendering always non-blocking with fix (#31452)
We've previously failed to land this change due to some internal apps
seeing infinite render loops due to external store state updates during
render. It turns out that since the `renderWasConcurrent` var was moved
into the do block, the sync render triggered from the external store
check was stuck with a `RootSuspended` `exitStatus`. So this is not
unique to sibling prerendering but more generally related to how we
handle update to a sync external store during render.

We've tested this build against local repros which now render without
crashes. We will try to add a unit test to cover the scenario as well.

---------

Co-authored-by: Andrew Clark <git@andrewclark.io>
Co-authored-by: Rick Hanlon <rickhanlonii@fb.com>
2024-11-08 12:38:41 -05:00
Alex Hunt
5c56b873ef Update React Native shims to use export syntax (#31426)
## Summary

I'm working to get the main `react-native` package parsable by modern
Flow tooling (both `flow-bundler`, `flow-api-translator`), and one
blocker is legacy `module.exports` syntax. This diff updates files which
are [synced to
`react-native`](https://github.com/facebook/react-native/tree/main/packages/react-native/Libraries/Renderer/shims)
from this repo.

## How did you test this change?

Files were pasted into `react-native-github` under fbsource, where Flow
validates .
2024-11-07 14:53:44 +00:00
Jan Kassens
682a103cde [www] set disableLegacyMode to true (#31439) 2024-11-07 09:05:31 -05:00
Jan Kassens
e1378902bb [string-refs] cleanup string ref code (#31443) 2024-11-06 14:00:10 -05:00
Sathya Gunasekaran
a88b9e5f68 [compiler] Outline JSX with non-jsx children (#31442)
Previously, we bailed out on outlining jsx that had children that were
not part of the outlined jsx.

Now, we add support for children by treating as attributes.
2024-11-06 17:54:44 +00:00
Sathya Gunasekaran
09197bb786 [compiler] Outline jsx with duplicate attributes (#31441)
Previously, we would skip outlining jsx expressions that had duplicate
jsx attributes as we would not rename them causing incorrect
compilation.

In this PR, we add outlining support for duplicate jsx attributes by
renaming them.
2024-11-06 17:50:13 +00:00
Sathya Gunasekaran
2df8f61885 [compiler] Store original and new prop names (#31440)
Previously, we'd directly store the original attributes from the jsx
expressions. But this isn't enough as we want to rename duplicate
attributes.

This PR refactors the prop collection logic to store both the original
and new names for jsx attributes in the newly outlined jsx expression.

For now, both the new and old names are the same. In the future, they
will be different when we add support for outlining expressions with
duplicate attribute names.
2024-11-06 17:44:52 +00:00
Jan Kassens
a7b83e7ceb [www] set disableStringRefs to true (#31438) 2024-11-06 12:13:43 -05:00
Sophie Alpert
66855b9637 Remove unused lastFullyObservedContext (#31435) 2024-11-06 07:35:23 -08:00
Jan Kassens
314968561b Back out "[bundles] stop building legacy Paper renderer (#31429)" (#31437)
Backs out the 2 related commits:
-
f8f6e1a21a
-
6c0f37f94b

Since I only realized when syncing that we need the version of `react`
and the legacy renderer to match.

While I investigate if there's anything we can do to work around that
while preserving the legacy renderer, this unblocks the sync.
2024-11-06 09:41:18 -05:00
Jan Kassens
d1f04722d6 [string-refs] remove enableLogStringRefsProd flag (#31414)
We no longer need this production logging.
2024-11-06 09:00:49 -05:00
Jan Kassens
3dc1e4820e Followup: remove dead test code from #30346 (#31415)
I missed that this was a constant false check when making the broader
cleanup changes in https://github.com/facebook/react/pull/30346
2024-11-06 08:58:36 -05:00
mofeiZ
c3570b158d [compiler] Collect temporaries and optional chains from inner functions (#31346)
Recursively collect identifier / property loads and optional chains from
inner functions. This PR is in preparation for #31200

Previously, we only did this in `collectHoistablePropertyLoads` to
understand hoistable property loads from inner functions.
1. collectTemporariesSidemap
2. collectOptionalChainSidemap
3. collectHoistablePropertyLoads
- ^ this recursively calls `collectTemporariesSidemap`,
`collectOptionalChainSidemap`, and `collectOptionalChainSidemap` on
inner functions
4. collectDependencies

Now, we have
1. collectTemporariesSidemap
- recursively record identifiers in inner functions. Note that we track
all temporaries in the same map as `IdentifierIds` are currently unique
across functions
2. collectOptionalChainSidemap
    - recursively records optional chain sidemaps in inner functions
3. collectHoistablePropertyLoads
    - (unchanged, except to remove recursive collection of temporaries)
4. collectDependencies
- unchanged: to be modified to recursively collect dependencies in next
PR

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31346).
* #31202
* #31203
* #31201
* #31200
* __->__ #31346
* #31199
2024-11-05 19:25:05 -05:00
mofeiZ
fd018af617 [compiler] Delete propagateScopeDeps (non-hir) (#31199)
`enablePropagateScopeDepsHIR` is now used extensively in Meta. This has
been tested for over two weeks in our e2e tests and production.

The rest of this stack deletes `LoweredFunction.dependencies`, which the
non-hir version of `PropagateScopeDeps` depends on. To avoid a more
forked HIR (non-hir with dependencies and hir with no dependencies),
let's go ahead and clean up the non-hir version of
PropagateScopeDepsHIR.

Note that all fixture changes in this PR were previously reviewed when
they were copied to `propagate-scope-deps-hir-fork`. Will clean up /
merge these duplicate fixtures in a later PR

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31199).
* #31202
* #31203
* #31201
* #31200
* #31346
* __->__ #31199
2024-11-05 19:22:04 -05:00
mofeiZ
f2f002c7c1 [compiler][be] Stabilize compiler output: sort deps and decls by name (#31362)
All dependencies and declarations of a reactive scope can be reordered
to scope start/end. i.e. generated code does not depend on conditional
short-circuiting logic as dependencies are inferred to have no side
effects.

Sorting these by name helps us get higher signal compilation snapshot
diffs when upgrading the compiler and testing PRs
2024-11-05 18:26:50 -05:00
mofeiZ
792fa065ca [compiler][ez] Clean up pragma parsing for tests + playground (#31347)
Move environment config parsing for `inlineJsxTransform`,
`lowerContextAccess`, and some dev-only options out of snap (test
fixture). These should now be available for playground via
`@inlineJsxTransform` and `lowerContextAccess`.

Other small change:
Changed zod fields from `nullish()` -> `nullable().default(null)`.
[`nullish`](https://zod.dev/?id=nullish) fields accept `null |
undefined` and default to `undefined`. We don't distinguish between null
and undefined for any of these options, so let's only accept null +
default to null. This also makes EnvironmentConfig in the playground
more accurate. Previously, some fields just didn't show up as
`prettyFormat({field: undefined})` does not print `field`.
2024-11-05 18:19:44 -05:00
mofeiZ
33195602ea [compiler][ez] tsconfig: treat all snap fixtures as modules (#31350)
Qol improvement. Currently, typescript lints treat test fixtures without
an export as a 'global script' (see
[docs](https://www.typescriptlang.org/docs/handbook/2/modules.html#how-javascript-modules-are-defined)).
This gives confusing lints for duplicate declarations (in the global
scope)
2024-11-05 17:57:18 -05:00
mofeiZ
5ca2bc6d63 [compiler][ez] Fixture repro for function hoisting bug (#31349)
Repro for bug reported by @alexmckenley
2024-11-05 17:56:53 -05:00
mofeiZ
bddb7c9b5c [compiler] Add fixture for objectexpr computed key bug (#31348)
We were bailing out on complex computed-key syntax (prior to #31344) as
we assumed that this caused bugs (due to inferring computed key rvalues
to have `freeze` effects).

This fixture shows that this bailout is unrelated to the underlying bug
2024-11-05 17:56:36 -05:00
Jan Kassens
f8f6e1a21a [bundles] update GitHub actions for commit build branch (#31432)
This is a followup to
6c0f37f94b
to unblock the job.
2024-11-05 16:04:02 -05:00
Jan Kassens
6c0f37f94b [bundles] stop building legacy Paper renderer (#31429) 2024-11-05 15:49:20 -05:00
mofeiZ
527bcaa83d [compiler] patch: rewrite scope dep/decl in inlineJsxTransform (#31431)
This bugfix is needed to land #31199 PropagateScopeDepsHIR infers scope
declarations for the `inline-jsx-transform` test fixture (the non-hir
version does not).

These declarations must get the rewritten phi identifiers
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31431).
* #31204
* #31202
* #31203
* #31201
* #31200
* #31346
* #31199
* __->__ #31431
* #31345
* #31197
2024-11-05 15:27:39 -05:00
mofeiZ
e7e269b726 [compiler] bugfix for hoistable deps for nested functions (#31345)
`PropertyPathRegistry` is responsible for uniqueing identifier and
property paths. This is necessary for the hoistability CFG merging logic
which takes unions and intersections of these nodes to determine a basic
block's hoistable reads, as a function of its neighbors. We also depend
on this to merge optional chained and non-optional chained property
paths

This fixes a small bug in #31066 in which we create a new registry for
nested functions. Now, we use the same registry for a component / hook
and all its inner functions

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31345).
* #31204
* #31202
* #31203
* #31201
* #31200
* #31346
* #31199
* #31431
* __->__ #31345
* #31197
2024-11-05 15:25:54 -05:00
mofeiZ
dd1a021bad [compiler][ez] Patch hoistability for ObjectMethods (#31197)
Extends #31066 to ObjectMethods (somehow missed this before).

'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31197).
* #31204
* #31202
* #31203
* #31201
* #31200
* #31346
* #31199
* #31431
* #31345
* __->__ #31197
2024-11-05 15:25:39 -05:00
Sebastian Markbåge
156eab2f7b Fork the "empty" prepareStackTrace case for Server builds (#31427)
We don't actually want the source mapped version of `.stack` from errors
because that would cause us to not be able to associate it with a source
map in the UIs that need it. The strategy in browsers is more correct
where the display is responsible for source maps.

That's why we disable any custom `prepareStackTrace` like the ones added
by `source-map`. We reset it to `undefined`.

However, when running node with `--enable-source-maps` the default for
`prepareStackTrace` which is a V8 feature (but may exist elsewhere too
like Bun) is a source mapped version of the stack. In those environments
we need to reset it to a default implementation that doesn't apply
source maps.

We already did this in Flight using the `ReactFlightStackConfigV8.js`
config. However, we need this more generally in the
`shared/ReactComponentStackFrame` implementation.

We could always set it to the default implementation instead of
`undefined` but that's unnecessary code in browser builds and it might
lead to slightly different results. For safety and code size, this PR
does it with a fork instead.

All builds specific to `node` or `edge` (or `markup` which is a server
feature) gets the default implementation where as everything else (e.g.
browsers) get `undefined` since it's expected that this is not source
mapped. We don't have to do anything about the equivalent in React
DevTools since React DevTools doesn't run on the server.
2024-11-05 15:05:04 -05:00
lauren
b81e6dd2da [cleanup] Remove compiler runtime-compat fixture library (#31430)
There's no real reason to keep this around anymore now that the compiler
beta is released and we have validated that react-compiler-runtime is
[usable by
libraries](https://www.npmjs.com/package/react-compiler-runtime?activeTab=dependents).

Let's clean this up for now.
2024-11-05 14:14:39 -05:00
Ricky
33c7bd9ae3 Remove trim_trailing_whitespace from editorconfig (#31413)
This setting breaks `toMatchInlineSnapshot` by removing whitespace in
snapshots.
2024-11-04 15:30:02 -05:00
Jan Kassens
07aa494432 Remove enableRefAsProp feature flag (#30346)
The flag is fully rolled out.
2024-11-04 14:30:58 -05:00
Jack Pope
543eb09321 [compiler] Wrap inline jsx transform codegen in conditional (#31267)
JSX inlining is a prod-only optimization. We want to enforce this while
maintaining the same compiler output in DEV and PROD.

Here we add a conditional to the transform that only replaces JSX with
object literals outside of DEV. Then a later build step can handle DCE
based on the value of `__DEV__`
2024-11-04 13:19:05 -05:00
Timothy Yung
4d577fd216 More Unit Tests for Refs in Hidden Subtrees (#31404)
## Summary

While fixing ref lifecycles in hidden subtrees in
https://github.com/facebook/react/pull/31379, @rickhanlonii noticed that
we could also add more unit tests for other types of tags to prevent
future regressions during code refactors.

This PR adds more unit tests in the same vein as those added in
https://github.com/facebook/react/pull/31379.

## How did you test this change?

Verified unit tests pass:

```
$ yarn
$ yarn test ReactFreshIntegration-test.js
```
2024-11-04 10:46:28 -05:00
Josh Story
7c8e5e7ab8 Reenable lazy context propagation (#31405)
Reverts facebook/react#31403 to reenable lazy context propagation

The disabling was to produce a build that could help track down whether
this flag is causing a possibly related bug in transitions but we don't
intend to disable it just fix forward once we figure out what the
problem is
2024-11-01 15:17:51 -07:00
Josh Story
16409d0560 temporarily disable lazy context propagation (#31403)
disables lazy context propagation in oss to help determine if it is
causing bugs in startTransition. Will reenable after cutting a canary
release with this flag disabled
2024-11-01 15:16:26 -07:00
Sebastian Markbåge
b7e2157922 [Flight] Handle errors during JSON stringify of console values (#31391)
When we serialize debug info we should never error even though we don't
currently support everything being serialized. Since it's non-essential
dev information.

We already handle errors in the replacer but not when errors happen in
the JSON algorithm itself - such as cyclic errors.

We should ideally support cyclic objects but regardless we should
gracefully handle the errors.
2024-10-31 16:47:51 -04:00
Timothy Yung
ea3ac58669 Fix Ref Lifecycles in Hidden Subtrees (#31379)
## Summary

We're seeing certain situations in React Native development where ref
callbacks in `<Activity mode="hidden">` are sometimes invoked exactly
once with `null` without ever being called with a "current" value.

This violates the contract for refs because refs are expected to always
attach before detach (and to always eventually detach after attach).
This is *particularly* bad for refs that return cleanup functions,
because refs that return cleanup functions expect to never be invoked
with `null`. This bug causes such refs to be invoked with `null`
(because since `safelyAttachRef` was never called, `safelyDetachRef`
thinks the ref does not return a cleanup function and invokes it with
`null`).

This fix makes use of `offscreenSubtreeWasHidden` in
`commitDeletionEffectsOnFiber`, similar to how
ec52a5698e
did this for `commitDeletionEffectsOnFiber`.

## How did you test this change?

We were able to isolate the repro steps to isolate the React Native
experimental changes. However, the repro steps depend on Fast Refresh.

```
function callbackRef(current) {
  // Called once with `current` as null, upon triggering Fast Refresh.
}

<Activity mode="hidden">
  <View ref={callbackRef} />;
</Activity>
```

Ideally, we would have a unit test that verifies this behavior without
Fast Refresh. (We have evidence that this bug occurs without Fast
Refresh in real product implementations. However, we have not
successfully deduced the root cause, yet.)

This PR currently includes a unit test that reproduces the Fast Refresh
scenario, which is also demonstrated in this CodeSandbox:
https://codesandbox.io/p/sandbox/hungry-darkness-33wxy7

Verified unit tests pass:

```
$ yarn
$ yarn test
# Run with `-r=www-classic` for `enableScopeAPI` tests.
$ yarn test -r=www-classic
```

Verified on the internal React Native development branch that the bug no
longer repros.

---------

Co-authored-by: Rick Hanlon <rickhanlonii@fb.com>
2024-10-31 13:24:45 -07:00
lauren
603e6108f3 [compiler] Update react deps to experimental instead of beta (#31385)
Some tests rely on experimental APIs so let's just use
react@experimental instead of beta
2024-10-29 21:51:41 -04:00
lauren
4abe4b5821 [compiler] Check if local identifier is a hook when resolving globals (#31384)
When resolving import specifiers from the react namespace (`import
{imported as local} from 'react'`), we were previously only checking if
the `imported` identifier was a hook if we didn't already have its
definition in the global registry. We also need to check if `local` is a
hook in the case of aliasing since there may be hook-like APIs in react
that don't start with `use` (eg they are experimental or unstable).

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31384).
* #31385
* __->__ #31384
* #31383
2024-10-29 21:36:48 -04:00
lauren
3928cb00db [compiler] Ref validation repro for ImportSpecifier with renamed local (#31383)
This was originally reported in
https://github.com/reactwg/react-compiler/discussions/27.

Adding a failing repro to capture this case.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31383).
* #31385
* #31384
* __->__ #31383
2024-10-29 21:36:36 -04:00
Sebastian Markbåge
0bc3074873 Capture the source and not just the stack on first seen error (#31367)
Otherwise we can't capture the owner stack at the right location when
there's a rethrow.
2024-10-28 13:59:38 -07:00
Sathya Gunasekaran
02c0e824e4 [compiler][ez] Remove unused param (#31376) 2024-10-28 15:08:27 +00:00
Sathya Gunasekaran
aded0ef831 [compiler] Handle member expr as computed property (#31344)
This PR loosens the restriction on the types of computed properties we
can handle.

Previously, we would disallow anything that is not an identifier because
non-identifiers could be mutating. But member expressions are not
mutating so we can treat them similar to identifiers.
2024-10-28 13:10:01 +00:00
Mike Vitousek
fe04dbcbc4 [compiler] Fix to ref access check to ban ref?.current
ghstack-source-id: ea417a468e
Pull Request resolved: https://github.com/facebook/react/pull/31360
2024-10-25 16:51:36 -07:00
Jack Pope
cae764ce81 Revert "[Re-land] Make prerendering always non-blocking (#31268)" (#31355)
This reverts commit 6c4bbc7832.

It looked like the bug we found on the original land was related to
broken product code. But through landing #31268 we found additional bugs
internally. Since disabling the feature flag does not fix the bugs, we
have to revert again to unblock the sync. We can continue to debug with
our internal build.
2024-10-25 09:17:07 -07:00
Pascal Birchler
d19ba8ecdd [react-compiler-runtime] Support React 17 peer dependency (#31336)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

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

The recent blog post and
[documentation](https://react.dev/learn/react-compiler#using-react-compiler-with-react-17-or-18)
say that `react-compiler-runtime` supports React 17, yet it currently
requires React 18 or 19 as a peer dependency, making it unusable for
installing on a project still using React 17.

## How did you test this change?

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->

Manually installing the package on a React 17 codebase.

---------

Co-authored-by: lauren <poteto@users.noreply.github.com>
2024-10-24 14:08:57 -04:00
Sebastian Markbåge
1631855f43 [Flight] encodeURI filenames parsed from stack traces (#31340)
When parsing stacks from third parties they may include invalid url
characters. So we need to encode them. Since these are expected to be
urls though we use just encodeURI instead of encodeURIComponent.
2024-10-23 16:29:20 -07:00
lauren
28668d39be [playground] Upgrade to Next 15 stable (#31333) 2024-10-23 12:13:22 -04:00
Sebastian Markbåge
b3e0a11e8f [Flight] Allow <anonymous> stack frames to be serialized if opt-in (#31329)
Normally we filter out stack frames with missing `filename` because they
can be noisy and not ignore listed. However, it's up to the
filterStackFrame function to determine whether to do it. This lets us
match `<anonymous>` stack frames in V8 parsing (they don't have line
numbers).
2024-10-23 08:38:33 -07:00
Hendrik Liebau
2dc5bebd46 Fix error handling in resolveClientReference (#31332)
When a React Server Consumer Manifest does not include an entry for a
client reference ID, we must not try to look up the export name (or
`'*'`) for the client reference. Otherwise this will fail with
`TypeError: Cannot read properties of undefined (reading '...')` instead
of the custom error we intended to throw.
2024-10-23 08:36:40 -07:00
Henry Q. Dineen
b4cbdc5a7c remove terser from react-compiler-runtime build (#31326)
## Summary

This fixes a minor nit I have about the `react-compiler-runtime` package
in that the published code is minified. I assume most consumers will
minify their own bundles so there's no real advantage to minifying it as
part of the build.

For my purposes it makes it more difficult to read the code, use
`patch-package` (if needed), or diff two versions without referencing
the source code on github or mapping it back to original source using
the source maps.

## How did you test this change?

I ran the build locally and looked at the result but did not run the
code. It's a lot more readable except for the commonjs
compatibility-related stuff that Rollup inserts.
2024-10-22 19:49:10 -04:00
Marin Atanasov
9daabc0bf9 react-hooks/rules-of-hooks: Add support for do/while loops (#28714)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

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

Currently, `react-hooks/rules-of-hooks` does not support `do/while`
loops - I've also reported this in
https://github.com/facebook/react/issues/28713.

This PR takes a stab at adding support for `do/while` by following the
same logic we already have for detecting `while` loops.

After this PR, any hooks called inside a `do/while` loop will be
considered invalid.

We're also adding some unit tests to confirm that the behavior is
working as expected.

Fixes #28713.

## How did you test this change?

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->

I've added unit tests that cover the case and verified that they pass by
running:

```
yarn test packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js --watch
```

I've also verified that the rest of the tests continue to pass by
running:

```
yarn test
```

and

```
yarn test --prod
```
2024-10-22 13:07:10 -07:00
Simen Bekkhus
ae90522bc6 chore: remove unsued deps from React Compiler Babel plugin (#31315)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

Since the Babel plugin is bundled into a single file (except for
`@babel/types`
45804af18d/compiler/packages/babel-plugin-react-compiler/rollup.config.js (L18))
we can move these deps to `devDependencies`.

Main motivation is e.g. not installing ancient version of
`pretty-format` (asked in https://github.com/facebook/react/issues/29062
without getting a reason, but if consumers can just skip the deps
entirely that's even better).

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

## How did you test this change?

I tested by installing the plugin into an empty project, deleting
everything in `node_modules` _except_ for `babel-plugin-react-compiler`
and doing `require('babel-plugin-react-compiler')`. It still worked
fine, so it should work in other cases as well 😀

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->
2024-10-22 13:51:58 -04:00
Sam Zhou
45804af18d [flow] Eliminate usage of more than 1-arg React.AbstractComponent in React codebase (#31314)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

In order to adopt react 19's ref-as-prop model, Flow needs to eliminate
all the places where they are treated differently.
`React.AbstractComponent` is the worst example of this, and we need to
eliminate it.

This PR eliminates them from the react repo, and only keeps the one that
has 1 argument of props.

## How did you test this change?

yarn flow
2024-10-21 16:17:41 -07:00
Sebastian Markbåge
69d4b800a6 [Flight] Support Async Modules in Server References (#31313)
This is required to support for example top level await in a "use
server" module or dependency of a "use server".
2024-10-21 14:52:10 -04:00
Sebastian Markbåge
65a56d0e99 Fix timing issue with fake promise resolving sync (#31304) 2024-10-20 02:35:15 -04:00
Sebastian Markbåge
f11bd3439c Fix types (#31303) 2024-10-20 02:23:31 -04:00
Sebastian Markbåge
251b666ded [Flight] Handle bound arguments for loaded server references (#31302)
Follow up to #31300.

I forgot to pass the bound arguments to the loaded function.
2024-10-20 02:12:06 -04:00
Sebastian Markbåge
c1e1358b2d [Flight] Align turbopack option name with webpack name (#31301)
This was renamed in #31300.
2024-10-20 00:02:48 -04:00
Sebastian Markbåge
d49123f73f Expose prerender() for SSG in stable (#31298)
When we added `renderToReadableStream` we added the `allReady` helper to
make it easier to do SSG rendering but it's kind of awkward to wire up
that way. Since we're also discouraging `renderToString` in React 19 the
cliff is kind of awkward. ([As noted by
Docusaurus.](https://github.com/facebook/react/pull/24752#issuecomment-2178309299))

The idea of the `react-dom/static` `prerender` API was that this would
be the replacement for SSG rendering. Awkwardly this entry point
actually already exists in stable but it has only `undefined` exports.

Since then we've also added other useful heuristics into the `prerender`
branch that makes this really the favored and easiest to use API for the
prerender (SSG/ISR) use case.

`prerender` is also used for Partial Prerendering but that part is still
experimental.

However, we can expose only the `prerender` API on `react-dom/static`
without it returning the `postponeState`. Instead the stream is on
`prelude`. The naming is a bit awkward if you don't consider resuming
but it's the same thing.

It's really just `renderToReadable` stream with automatic `allReady` and
better heuristics for prerendering.
2024-10-19 22:33:28 -04:00
Sebastian Markbåge
22b2b1a05a [Flight] Add serverModuleMap option for mapping ServerReferences (#31300)
Stacked on #31299.

We already have an option for resolving Client References to other
Client References when consuming an RSC payload on the server.

This lets you resolve Server References on the consuming side when the
environment where you're consuming the RSC payload also has access to
those Server References. Basically they becomes like Client References
for this consumer but for another consumer they wouldn't be.
2024-10-19 21:10:25 -04:00
Sebastian Markbåge
39a7730b13 Rename SSRManifest to ServerConsumerManifest (#31299)
This config is more generally applicable to all server-side Flight
Clients and not just SSR.
2024-10-19 20:45:20 -04:00
lauren
1839e1437f [ez] Update compiler issue template (#31297)
Add a field to specify which version of React Compiler is being used.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31297).
* __->__ #31297
* #31296
2024-10-18 15:02:26 -04:00
lauren
54c0edc019 [ci] Don't use branch name for concurrency (#31296)
I happened to notice some jobs on main get canceled if another PR landed
before the prior commit on main had finished running CI. This is not
great for difftrain because the commit artifacts job relies on the CI
jobs on main finishing before it triggers. This would lead to commits
being skipped on DiffTrain which is not great for provenance since we
want it to be a 1:1 sync.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31296).
* #31297
* __->__ #31296
2024-10-18 15:02:14 -04:00
lauren
9deb36748d [ci] Publish compiler weekly prereleases (#31294)
Adds a new weekly job for the compiler
2024-10-18 14:43:43 -04:00
Joseph Savona
cdde15efe1 [compiler] InlineJSXTransform transforms jsx inside function expressions (#31282)
InlineJSXTransform wasn't traversing into function expressions or object
methods, so any JSX inside such functions wouldn't have gotten inlined.
This PR updates to traverse nested functions to transform all JSX within
a hook or component.

Note that this still doesn't transform JSX outside of components or
hooks, ie in standalone render helpers.
2024-10-18 11:27:48 -07:00
lauren
915be0ef78 [playground] Upgrade various packages (#31293)
Just some housekeeping
2024-10-18 14:25:36 -04:00
lauren
61383303d3 [playground] Remove unnecessary fs package (#31292)
Seems like this was accidentally added.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31292).
* #31293
* __->__ #31292
* #31291
2024-10-18 14:09:30 -04:00
lauren
ee6ca23b24 [playground] Upgrade to Next 15 (#31291)
This was previously blocked because the playground was a part of the
compiler's yarn workspace and there was some funky hoisting going on.
Now that we are decoupled we can upgrade to Next 15, which hopefully
should improve build times.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31291).
* #31293
* #31292
* __->__ #31291
2024-10-18 14:09:07 -04:00
lauren
d57217544a [fixture] Update compiler to use latest package (#31289)
Pins the compiler to the latest version in our fixture app.
2024-10-18 13:32:26 -04:00
lauren
35b63ca90d [ci:compiler] Only add latest tag to non-experimental (#31288)
It turns out npm sets the latest tag by default so simply removing it
didn't change the previous behavior.

The `latest` tag is typically used for stable release versions, and
other tags for unstable versions such as prereleases. Since the compiler
is still in prerelease, let's set the latest tag only for
non-experimental releases to help signal which version is the safest to
try out.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31288).
* #31289
* __->__ #31288
2024-10-18 13:24:30 -04:00
Sebastian Markbåge
b8ae38f88b Audit try/finally around console patching (#31286)
Otherwise if something errors they can be left patched.

[Review without
whitespace](https://github.com/facebook/react/pull/31286/files?w=1)
2024-10-18 12:05:20 -04:00
lauren
1ce58ddd67 [ci] Don't auto push to latest tag (#31284)
By default let's stop pushing to the latest tag now that we have a
non-experimental release.
2024-10-18 00:20:14 -04:00
lauren
25cac220d6 [ci] Allow passing various params to compiler publish script (#31283)
Allow passing in a few more inputs when manually publishing.
2024-10-17 18:12:58 -04:00
lauren
9c60cbe3d1 [compiler] Clean up publish script (#31278)
Few small tweaks to make it easier to run adhoc publishes
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31278).
* #31283
* __->__ #31278
2024-10-17 18:02:41 -04:00
Sathya Gunasekaran
c91b3b090a JSX Outlining (#30956)
Currently, the react compiler can not compile within callbacks which can
potentially cause over rendering. Consider this example:
```jsx
function Component(countries, onDelete) {
  const name = useFoo();
  return countries.map(() => {
    return (
      <Foo>
        <Bar name={name}/>
        <Baz onclick={onDelete} />
      </Foo>
    );
  });
}
```

In this case, there's no memoization of the nested jsx elements. But
instead if we were to manually refactor the nested jsx into separate
component like this:
```jsx
function Component(countries, onDelete) {
  const name = useFoo();
  return countries.map(() => {
    return <Temp name={name} onDelete={onDelete} />;
  });
}

function Temp({ name, onDelete }) {
  return (
    <Foo>
      <Bar name={name} />
      <Baz onclick={onDelete} />
    </Foo>
  );
}

```

The compiler can now optimise both these components:
```jsx
function Component(countries, onDelete) {
  const $ = _c(4);
  const name = useFoo();
  let t0;
  if ($[0] !== name || $[1] !== onDelete || $[2] !== countries) {
    t0 = countries.map(() => <Temp name={name} onDelete={onDelete} />);
    $[0] = name;
    $[1] = onDelete;
    $[2] = countries;
    $[3] = t0;
  } else {
    t0 = $[3];
  }
  return t0;
}

function Temp(t0) {
  const $ = _c(7);
  const { name, onDelete } = t0;
  let t1;
  if ($[0] !== name) {
    t1 = <Bar name={name} />;
    $[0] = name;
    $[1] = t1;
  } else {
    t1 = $[1];
  }
  let t2;
  if ($[2] !== onDelete) {
    t2 = <Baz onclick={onDelete} />;
    $[2] = onDelete;
    $[3] = t2;
  } else {
    t2 = $[3];
  }
  let t3;
  if ($[4] !== t1 || $[5] !== t2) {
    t3 = (
      <Foo>
        {t1}
        {t2}
      </Foo>
    );
    $[4] = t1;
    $[5] = t2;
    $[6] = t3;
  } else {
    t3 = $[6];
  }
  return t3;
}
```

Now, when `countries` is updated by adding one single value, only the
newly added value is re-rendered and not the entire list. Rather than
having to do this manually, this PR teaches the react compiler to do
this transformation.

This PR adds a new pass (`OutlineJsx`) to capture nested jsx statements
and outline them in a separate component. This newly outlined component
can then by memoized by the compiler, giving us more fine grained
rendering.
2024-10-17 18:15:32 +01:00
Ruslan Lesiutin
bf7e210cb5 tests[react-devtools]: added tests for Compiler integration (#31241)
Adds tests for Compiler integration.

This includes:
- Tests against Compiler from source.
- Versioned (18.2 - <19) tests against Compiler from npm.

For tests against React 18.2, I had to download `react-compiler-runtime`
from npm and put it to `react/compiler-runtime.js`.
2024-10-17 09:02:41 +01:00
lauren
3ed64f8232 [ez] Update references to 'forget' in react-compiler-runtime (#31277)
Updates the runtime to reference React Compiler instead of Forget.
2024-10-16 18:44:50 -04:00
Timothy Yung
a3d9ea05bf Delete __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED from React Native Renderer (#31276)
## Summary

The React Native Renderer exports a
`__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED` property with a
single method that has no remaining call sites:
`computeComponentStackForErrorReporting`

This PR cleans up this unused export.

## How did you test this change?

```
$ yarn
$ yarn flow fabric
$ yarn test
```
2024-10-16 11:19:01 -07:00
Edmond Chui
77b637d612 Add Bridge types for Fusebox (#31274)
New types used by Fusebox
https://github.com/facebookexperimental/rn-chrome-devtools-frontend/pull/117
2024-10-16 16:45:24 +01:00
Sebastian Markbåge
be94b10826 [Flight] Enable sync stack traces for errors and console replay (#31270)
This was gated behind `enableOwnerStacks` since they share some code
paths but it's really part of `enableServerComponentLogs`.

This just includes the server-side regular stack on Error/replayed logs
but doesn't use console.createTask and doesn't include owner stacks.
2024-10-16 10:57:08 -04:00
Jack Pope
6c4bbc7832 [Re-land] Make prerendering always non-blocking (#31268)
Follows https://github.com/facebook/react/pull/31238

___

This is a partial re-land of
https://github.com/facebook/react/pull/31056. We saw breakages surface
after the original land and had to revert. Now that they've been fixed,
let's try this again. This time we'll split up the commits to give us
more control of testing and rollout internally.

Original PR: https://github.com/facebook/react/pull/31056
Original Commit:
4c71025d8d
Revert PR: https://github.com/facebook/react/pull/31080

Commit description:

> When a synchronous update suspends, and we prerender the siblings, the
prerendering should be non-blocking so that we can immediately restart
once the data arrives.
>
> This happens automatically when there's a Suspense boundary, because
we immediately commit the boundary and then proceed to a Retry render,
which are always concurrent. When there's not a Suspense boundary, there
is no Retry, so we need to take care to switch from the synchronous work
loop to the concurrent one, to enable time slicing.

Co-authored-by: Andrew Clark <git@andrewclark.io>
2024-10-15 16:47:02 -04:00
Sebastian "Sebbie" Silbermann
8382581446 [ESLint] Add test for rejected useId in async Components (#31208) 2024-10-15 17:17:41 +02:00
Ruslan Lesiutin
c7c68ef842 React DevTools 6.0.0 -> 6.0.1 (#31263)
Changes in this release:

* Fix React Compiler badging ([poteto](https://github.com/poteto) in
[#31196](https://github.com/facebook/react/pull/31196))
* fix[react-devtools]: fixed timeline profiler tests
([hoxyq](https://github.com/hoxyq) in
[#31261](https://github.com/facebook/react/pull/31261))
* fix[react-devtools]: record timeline data only when supported
([hoxyq](https://github.com/hoxyq) in
[#31154](https://github.com/facebook/react/pull/31154))
* refactor[react-devtools]: flatten reload and profile config
([hoxyq](https://github.com/hoxyq) in
[#31132](https://github.com/facebook/react/pull/31132))
* fix[react-devtools]: remove all listeners when Agent is shutdown
([hoxyq](https://github.com/hoxyq) in
[#31151](https://github.com/facebook/react/pull/31151))
* fix[react-devtools]: removed redundant startProfiling call
([hoxyq](https://github.com/hoxyq) in
[#31131](https://github.com/facebook/react/pull/31131))
* refactor[react-devtools/fiber/renderer]: optimize durations resolution
([hoxyq](https://github.com/hoxyq) in
[#31118](https://github.com/facebook/react/pull/31118))
* fix[react-devtools]: update profiling status before receiving response
from backend ([hoxyq](https://github.com/hoxyq) in
[#31117](https://github.com/facebook/react/pull/31117))
* fix[react-devtools]: wrap key string in preformatted text html element
([hoxyq](https://github.com/hoxyq) in
[#31153](https://github.com/facebook/react/pull/31153))
* chore[react-devtools]: drop legacy context tests
([hoxyq](https://github.com/hoxyq) in
[#31059](https://github.com/facebook/react/pull/31059))
* chore[react-devtools]: add legacy mode error message to the ignore
list for tests ([hoxyq](https://github.com/hoxyq) in
[#31060](https://github.com/facebook/react/pull/31060))
* fix[react-devtools]: request hook initialization inside http server
response ([hoxyq](https://github.com/hoxyq) in
[#31102](https://github.com/facebook/react/pull/31102))
* [Flight] Serialize Server Components Props in DEV
([sebmarkbage](https://github.com/sebmarkbage) in
[#31105](https://github.com/facebook/react/pull/31105))
* Add: reload to profile for Fusebox
([EdmondChuiHW](https://github.com/EdmondChuiHW) in
[#31021](https://github.com/facebook/react/pull/31021))
* refactor: allow custom impl of backend realod-to-profile support check
([EdmondChuiHW](https://github.com/EdmondChuiHW) in
[#31048](https://github.com/facebook/react/pull/31048))
* fix: use public instance in Fiber renderer and expose it from
getInspectorDataForViewAtPoint ([hoxyq](https://github.com/hoxyq) in
[#31068](https://github.com/facebook/react/pull/31068))
2024-10-15 14:15:26 +01:00
lauren
9806a4b0d4 [DevTools] Fix React Compiler badging (#31196)
In #31140 we switched over the uMC polyfill to use memo instead of state
since memo would FastRefresh properly. However this busted devtools'
badging of compiled components; this PR fixes it.

TODO: tests
Co-authored-by: Ruslan Lesiutin <rdlesyutin@gmail.com>

---------

Co-authored-by: Ruslan Lesiutin <rdlesyutin@gmail.com>
2024-10-15 12:53:45 +01:00
Ruslan Lesiutin
ec2bf02245 fix[react-devtools]: fixed timeline profiler tests (#31261)
Fixes tests against React 18 after
https://github.com/facebook/react/pull/31154:
- Set `supportsTimeline` to true for `Store`.
- Execute `store.profilerStore.startProfiling` after `legacyRender`
import, because this is where `react-dom` is imported and renderer is
registered. We don't yet propagate `isProfiling` flag to newly
registered renderers, when profiling already started see:

d5bba18b5d/packages/react-devtools-shared/src/hook.js (L203-L204)
2024-10-15 12:46:05 +01:00
Jack Pope
13411e4589 [Re-land] Make prerendering always non-blocking: Add missing feature flag checks (#31238)
This is a partial re-land of
https://github.com/facebook/react/pull/31056. We saw breakages surface
after the original land and had to revert. Now that they've been fixed,
let's try this again. This time we'll split up the commits to give us
more control of testing and rollout internally.

Original PR: https://github.com/facebook/react/pull/31056
Original Commit:
2a9fb445d9
Revert PR: https://github.com/facebook/react/pull/31080

Commit description:
```
Neglected to wrap some places in the enableSiblingPrerendering flag.
```

Co-authored-by: Andrew Clark <git@andrewclark.io>
2024-10-14 14:12:23 -04:00
lauren
6cf8518505 [ci] Specify limited concurrency for PR jobs (#31240)
There was a concurrency setting we hadn't enabled on jobs that are
primarily triggered for PRs. This meant that every update to the PR
would trigger new CI jobs without canceling any ones already in flight,
which can greatly slow down CI due to the number of jobs that need to
run.

This PR adds concurrency [based on the workflow name and PR number or
head
ref.](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/control-the-concurrency-of-workflows-and-jobs)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31240).
* __->__ #31240
* #31239
2024-10-14 12:07:24 -04:00
lauren
cbcc1d2027 [ci] Consistent cache names (#31239)
Makes cache names more descriptive and consistent for CI, so it's easier
to tell which cache is used for what purpose.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31239).
* #31240
* __->__ #31239
2024-10-14 12:07:08 -04:00
lauren
b60286b834 [compiler] Use consistent version hash for npm (#31177)
Modifies our release script to use the same version hash (the hashed
`compiler` directory) for all compiler packages to keep them consistent.
2024-10-14 11:20:47 -04:00
Jan Kassens
75dd053b5e [string-refs] make disableStringRefs a dynamic www flag (#31175) 2024-10-14 10:13:40 -04:00
Sebastian Markbåge
cd22717c27 [Flight] Also don't cut off type and key (#31209) 2024-10-13 18:57:50 +02:00
Mike Vitousek
147374d71a [compiler] Kill markReactiveIdentifier and friends
Summary:
With the previous PR we no longer need to mark identifiers as reactive in contexts where we don't have places. We already deleted most uses of markReactiveId; the last case was to track identifiers through loadlocals etc -- but we already use a disjoint alias map that accounts for loadlocals when setting reactivity.

ghstack-source-id: 69ce0a78b0
Pull Request resolved: https://github.com/facebook/react/pull/31178
2024-10-11 17:26:57 -07:00
Mike Vitousek
6cf5bd9013 [compiler] Allow refs to be lazily initialized during render
Summary:
The official guidance for useRef notes an exception to the rule that refs cannot be accessed during render: to avoid recreating the ref's contents, you can test that the ref is uninitialized and then initialize it using an if statement:

```
if (ref.current == null) {
  ref.current = SomeExpensiveOperation()
}
```

The compiler didn't recognize this exception, however, leading to code that obeyed all the official guidance for refs being rejected by the compiler. This PR fixes that, by extending the ref validation machinery with an awareness of guard operations that allow lazy initialization. We now understand `== null` and similar operations, when applied to a ref and consumed by an if terminal, as marking the consequent of the if as a block in which the ref can be safely written to. In order to do so we need to create a notion of ref ids, which link different usages of the same ref via both the ref and the ref value.

ghstack-source-id: d2729274f351e1eb0268f28f629fa4c2568ebc4d
Pull Request resolved: https://github.com/facebook/react/pull/31188
2024-10-11 16:14:33 -07:00
dependabot[bot]
9c525ea44a Bump micromatch from 4.0.5 to 4.0.8 in /compiler (#31186)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5
to 4.0.8.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/micromatch/micromatch/releases">micromatch's
releases</a>.</em></p>
<blockquote>
<h2>4.0.8</h2>
<p>Ultimate release that fixes both CVE-2024-4067 and CVE-2024-4068. We
consider the issues low-priority, so even if you see automated scanners
saying otherwise, don't be scared.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md">micromatch's
changelog</a>.</em></p>
<blockquote>
<h2>[4.0.8] - 2024-08-22</h2>
<ul>
<li>backported CVE-2024-4067 fix (from v4.0.6) over to 4.x branch</li>
</ul>
<h2>[4.0.7] - 2024-05-22</h2>
<ul>
<li>this is basically v4.0.5, with some README updates</li>
<li><strong>it is vulnerable to CVE-2024-4067</strong></li>
<li>Updated braces to v3.0.3 to avoid CVE-2024-4068</li>
<li>does NOT break API compatibility</li>
</ul>
<h2>[4.0.6] - 2024-05-21</h2>
<ul>
<li>Added <code>hasBraces</code> to check if a pattern contains
braces.</li>
<li>Fixes CVE-2024-4067</li>
<li><strong>BREAKS API COMPATIBILITY</strong></li>
<li>Should be labeled as a major release, but it's not.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8bd704ec0d"><code>8bd704e</code></a>
4.0.8</li>
<li><a
href="a0e68416a4"><code>a0e6841</code></a>
run verb to generate README documentation</li>
<li><a
href="4ec288484f"><code>4ec2884</code></a>
Merge branch 'v4' into hauserkristof-feature/v4.0.8</li>
<li><a
href="03aa805217"><code>03aa805</code></a>
Merge pull request <a
href="https://redirect.github.com/micromatch/micromatch/issues/266">#266</a>
from hauserkristof/feature/v4.0.8</li>
<li><a
href="814f5f70ef"><code>814f5f7</code></a>
lint</li>
<li><a
href="67fcce6a10"><code>67fcce6</code></a>
fix: CHANGELOG about braces &amp; CVE-2024-4068, v4.0.5</li>
<li><a
href="113f2e3fa7"><code>113f2e3</code></a>
fix: CVE numbers in CHANGELOG</li>
<li><a
href="d9dbd9a266"><code>d9dbd9a</code></a>
feat: updated CHANGELOG</li>
<li><a
href="2ab13157f4"><code>2ab1315</code></a>
fix: use actions/setup-node@v4</li>
<li><a
href="1406ea38f3"><code>1406ea3</code></a>
feat: rework test to work on macos with node 10,12 and 14</li>
<li>Additional commits viewable in <a
href="https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=micromatch&package-manager=npm_and_yarn&previous-version=4.0.5&new-version=4.0.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/facebook/react/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-11 16:29:34 -04:00
dependabot[bot]
2011074ab8 Bump json5 from 2.2.1 to 2.2.3 in /compiler (#31185)
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/json5/json5/releases">json5's
releases</a>.</em></p>
<blockquote>
<h2>v2.2.3</h2>
<ul>
<li>Fix: json5@2.2.3 is now the 'latest' release according to npm
instead of v1.0.2. (<a
href="https://redirect.github.com/json5/json5/issues/299">#299</a>)</li>
</ul>
<h2>v2.2.2</h2>
<ul>
<li>Fix: Properties with the name <code>__proto__</code> are added to
objects and arrays.
(<a href="https://redirect.github.com/json5/json5/issues/199">#199</a>)
This also fixes a prototype pollution vulnerability reported by
Jonathan Gregson! (<a
href="https://redirect.github.com/json5/json5/issues/295">#295</a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/json5/json5/blob/main/CHANGELOG.md">json5's
changelog</a>.</em></p>
<blockquote>
<h3>v2.2.3 [<a
href="https://github.com/json5/json5/tree/v2.2.3">code</a>, <a
href="https://github.com/json5/json5/compare/v2.2.2...v2.2.3">diff</a>]</h3>
<ul>
<li>Fix: json5@2.2.3 is now the 'latest' release according to npm
instead of
v1.0.2. (<a
href="https://redirect.github.com/json5/json5/issues/299">#299</a>)</li>
</ul>
<h3>v2.2.2 [<a
href="https://github.com/json5/json5/tree/v2.2.2">code</a>, <a
href="https://github.com/json5/json5/compare/v2.2.1...v2.2.2">diff</a>]</h3>
<ul>
<li>Fix: Properties with the name <code>__proto__</code> are added to
objects and arrays.
(<a href="https://redirect.github.com/json5/json5/issues/199">#199</a>)
This also fixes a prototype pollution vulnerability reported by
Jonathan Gregson! (<a
href="https://redirect.github.com/json5/json5/issues/295">#295</a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c3a7524277"><code>c3a7524</code></a>
2.2.3</li>
<li><a
href="94fd06d82e"><code>94fd06d</code></a>
docs: update CHANGELOG for v2.2.3</li>
<li><a
href="3b8cebf0c4"><code>3b8cebf</code></a>
docs(security): use GitHub security advisories</li>
<li><a
href="f0fd9e194d"><code>f0fd9e1</code></a>
docs: publish a security policy</li>
<li><a
href="6a91a05fff"><code>6a91a05</code></a>
docs(template): bug -&gt; bug report</li>
<li><a
href="14f8cb186e"><code>14f8cb1</code></a>
2.2.2</li>
<li><a
href="10cc7ca916"><code>10cc7ca</code></a>
docs: update CHANGELOG for v2.2.2</li>
<li><a
href="7774c10979"><code>7774c10</code></a>
fix: add <strong>proto</strong> to objects and arrays</li>
<li><a
href="edde30abd8"><code>edde30a</code></a>
Readme: slight tweak to intro</li>
<li><a
href="97286f8bd5"><code>97286f8</code></a>
Improve example in readme</li>
<li>Additional commits viewable in <a
href="https://github.com/json5/json5/compare/v2.2.1...v2.2.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=json5&package-manager=npm_and_yarn&previous-version=2.2.1&new-version=2.2.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/facebook/react/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-11 16:29:18 -04:00
Sebastian "Sebbie" Silbermann
09111202d6 eslint-plugin-react-hooks: Release 5.0.0 (#31176)
Co-authored-by: lauren <poteto@users.noreply.github.com>
2024-10-11 14:51:59 +02:00
Jan Kassens
5636fad840 [string-refs] log string ref from prod (#31161)
If passed as a feature flag, this calls the configured function when a
string ref is used even from prod code to find the last usages.
2024-10-10 18:12:47 -04:00
Mike Vitousek
7b7fac073d [compiler] Represent phis with places rather than identifiers
Summary:
The fact that phis are identifiers rather than places is unfortunate in a few cases. In some later analyses, we might wish to know whether a phi is reactive, but we don't have an easy way to do that currently.

Most of the changes here is just replacing phi.id with phi.place.identifier and such. Interesting bits are EnterSSA (several functions now take places rather than identifiers, and InferReactivePlaces now needs to mark places as reactive explicitly.

ghstack-source-id: 5f4fb396cd86b421008c37832a5735ac40f8806e
Pull Request resolved: https://github.com/facebook/react/pull/31171
2024-10-10 12:41:03 -07:00
Sebastian Markbåge
70fb136391 Fix limit without owner stacks (#31179)
Move out of gate.
2024-10-10 14:52:33 -04:00
mofeiZ
fbfe37ee40 [compiler] Test fixture: non-reactive phi creates 'dangling ref' scope (#31103) 2024-10-10 12:23:44 -04:00
lauren
77f438931f [compiler] Consume compiled lib in react 19 app (#31167)
it works
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31167).
* __->__ #31167
* #31166
* #31165
2024-10-10 11:40:06 -04:00
lauren
0a1fdeee9e [compiler] Consume compiled lib in react 18 app (#31166)
`yarn dev` doesn't work quite correctly because of an outdated
assumption in vite-plugin-react, I have a [PR
open](https://github.com/vitejs/vite-plugin-react/pull/374) to address.

However `yarn build` and `yarn preview` does work as expected.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31166).
* #31167
* __->__ #31166
* #31165
2024-10-10 11:39:54 -04:00
lauren
eb0e265cd9 [compiler] Compile lib (#31165)
Add and compile a simple hook with rollup and babel.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31165).
* #31167
* #31166
* __->__ #31165
2024-10-10 11:39:42 -04:00
lauren
2ef407937b [compiler] Scaffold fixture apps (#31164)
Scaffold empty apps to consume the fixture lib.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31164).
* #31167
* #31166
* #31165
* __->__ #31164
* #31148
* #31168
2024-10-10 11:33:02 -04:00
Sebastian "Sebbie" Silbermann
16ac71a650 Fix prepare-prerelease script (#31159) 2024-10-10 17:24:51 +02:00
lauren
b781c9f564 [compiler] Scaffold fixture library (#31148)
Scaffolds an empty library to test backwards compatibility with the
compiler enabled.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31148).
* #31167
* #31166
* #31165
* #31164
* __->__ #31148
* #31168
2024-10-10 10:53:58 -04:00
lauren
0f1127b552 [prettier] Ignore compiler/target (#31168)
Add missing directory to prettierignore.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31168).
* #31167
* #31166
* #31165
* #31164
* #31148
* __->__ #31168
2024-10-10 10:53:27 -04:00
Josh Story
38af456a49 [Flight] don't emit chunks for rejected thenables after abort (#31169)
When aborting we emit chunks for each pending task. However there was a
bug where a thenable could also reject before we could flush and we end
up with an extra chunk throwing off the pendingChunks bookeeping. When a
task is retried we skip it if is is not in PENDING status because we
understand it was completed some other way. We need to replciate this
for the reject pathway on serialized thenables since aborting if
effectively completing all pending tasks and not something we need to
continue to do once the thenable rejects later.
2024-10-10 06:47:32 -07:00
Sebastian Markbåge
566b0b0f14 [Flight] Don't limit objects that are children of special types (#31160)
We can't make a special getter to mark the boundary of deep
serialization (which can be used for lazy loading in the future) when
the parent object is a special object that we parse with
getOutlinedModel. Such as Map/Set and JSX.

This marks the objects that are direct children of those as not possible
to limit.

I don't love this solution since ideally it would maybe be more local to
the serialization of a specific object.

It also means that very deep trees of only Map/Set never get cut off.
Maybe we should instead override the `get()` and enumeration methods on
these instead somehow.

It's important to have it be a getter though because that's the
mechanism that lets us lazy-load more depth in the future.
2024-10-10 07:36:37 -04:00
Mike Vitousek
131ae818a1 [compiler][ez] Include phi identifier in AssertValidMutableRanges
Summary:
Looks like we accidentally skipped validating this identifier.

ghstack-source-id: 05964331a8
Pull Request resolved: https://github.com/facebook/react/pull/31170
2024-10-09 22:15:41 -07:00
Josh Story
f5b8d9378b [Flight] Serialize top-level Date (#31163)
renderModelDesctructive can sometimes be called direclty on Date values.
When this happens we don't first call toJSON on the Date value so we
need to explicitly handle the case where where the rendered value is a
Date instance as well. This change updates renderModelDesctructive to
account for sometimes receiving Date instances directly.
2024-10-09 20:29:48 -07:00
Jan Kassens
de43d560a8 [cleanup] remove flag enableAddPropertiesFastPath (#31062)
The experiment was tested internally and rolled out, replacing the flag
with `true`.
2024-10-09 16:46:35 -04:00
Ruslan Lesiutin
d5bba18b5d fix[react-devtools]: record timeline data only when supported (#31154)
Stacked on https://github.com/facebook/react/pull/31132. See last
commit.

There are 2 issues:
1. We've been recording timeline events, even if Timeline Profiler was
not supported by the Host. We've been doing this for React Native, for
example, which would significantly regress perf of recording a profiling
session, but we were not even using this data.
2. Currently, we are generating component stack for every state update
event. This is extremely expensive, and we should not be doing this.

We can't currently fix the second one, because we would still need to
generate all these stacks, and this would still take quite a lot of
time. As of right now, we can't generate a component stack lazily
without relying on the fact that reference to the Fiber is not stale.
With `enableOwnerStacks` we could populate component stacks in some
collection, which would be cached at the Backend, and then returned only
once Frontend asks for it. This approach also eliminates the need for
keeping a reference to a Fiber.
2024-10-09 15:27:04 +01:00
Ruslan Lesiutin
bfe91fbecf refactor[react-devtools]: flatten reload and profile config (#31132)
Stacked on https://github.com/facebook/react/pull/31131. See last
commit.

This is a clean-up and a pre-requisite for next changes:
1. `ReloadAndProfileConfig` is now split into boolean value and settings
object. This is mainly because I will add one more setting soon, and
also because settings might be persisted for a longer time than the flag
which signals if the Backend was reloaded for profiling. Ideally, this
settings should probably be moved to the global Hook object, same as we
did for console patching.
2. Host is now responsible for reseting the cached values, Backend will
execute provided `onReloadAndProfileFlagsReset` callback.
2024-10-09 13:57:02 +01:00
Ruslan Lesiutin
1d8d12005f fix[react-devtools]: remove all listeners when Agent is shutdown (#31151)
Based on https://github.com/facebook/react/pull/31049, credits to
@EdmondChuiHW.

What is happening here:
1. Once Agent is destroyed, unsubscribe own listeners and bridge
listeners.
2. [Browser extension only] Once Agent is destroyed, unsubscribe
listeners from BackendManager.
3. [Browser extension only] I've discovered that `backendManager.js`
content script can get injected multiple times by the browser. When
Frontend is initializing, it will create Store first, and then execute a
content script for bootstraping backend manager. If Frontend was
destroyed somewhere between these 2 steps, Backend won't be notified,
because it is not initialized yet, so it will not unsubscribe listeners
correctly. We might end up duplicating listeners, and the next time
Frontend is launched, it will report an issues "Cannot add / remove node
...", because same operations are emitted twice.

To reproduce 3 you can do the following:
1. Click reload-to-profile
2. Right after when both app and Chrome DevTools panel are reloaded,
close Chrome DevTools.
3. Open Chrome DevTools again, open Profiler panel and observe "Cannot
add / remove node ..." error in the UI.
2024-10-09 13:34:01 +01:00
Ruslan Lesiutin
4a86ec5a66 fix[react-devtools]: removed redundant startProfiling call (#31131)
Stacked on https://github.com/facebook/react/pull/31118. See last
commit.

We don't need to call `startProfiling()` here, because we delegate this
to the Renderer itself:

830e823cd2/packages/react-devtools-shared/src/backend/fiber/renderer.js (L5227-L5232)

Since this is de-facto the constructor of Renderer, this will be called
earlier.

Validated via testing the reload-to-profile for Chrome browser
extension.
2024-10-09 13:32:04 +01:00
Ruslan Lesiutin
389a2deebc refactor[react-devtools/fiber/renderer]: optimize durations resolution (#31118)
Stacked on https://github.com/facebook/react/pull/31117. 

No need for sending long float numbers and to have resolution less than
a microsecond, we end up formatting it on a Frontend side:

6c7b41da3d/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js (L359-L360)
2024-10-09 13:26:16 +01:00
Ruslan Lesiutin
dbf80c8d7a fix[react-devtools]: update profiling status before receiving response from backend (#31117)
We can't wait for a response from Backend, because it might take some
time to actually finish profiling.

We should keep a flag on the frontend side, so user can quickly see the
feedback in the UI.
2024-10-09 13:23:23 +01:00
Ruslan Lesiutin
bf0c054649 fix[react-devtools]: wrap key string in preformatted text html element (#31153)
Fixes https://github.com/facebook/react/issues/28984.
2024-10-09 09:54:34 +01:00
lauren
ed966dac4a [compiler] Fix busted postinstall script (#31147) 2024-10-07 20:37:41 -04:00
lauren
f74f6cd945 [rcr] Publish react-compiler-runtime to npm (#31146)
Updates our publishing scripts to also publish react-compiler-runtime.
2024-10-07 18:50:07 -04:00
lauren
23cd3aca28 [rcr] Remove runtimeModule compiler option (#31145)
Now that the compiler always injects `react-compiler-runtime`, this
option is unnecessary.
2024-10-07 18:07:12 -04:00
lauren
3fd3364107 [rcr] Update default runtimeModule to react-compiler-runtime (#31144)
Updates the compiler to always import from `react-compiler-runtime` by
default. The runtime then decides whether to use the official or
userspace implementation of useMemoCache.
2024-10-07 17:59:33 -04:00
lauren
8dd4cda380 [rcr] Add target flag to compiler (#31143) 2024-10-07 17:50:53 -04:00
lauren
68d5288359 [snap] Add react-compiler-runtime as a dependency (#31142)
We need `react-compiler-runtime` to use the same version of React as
snap
2024-10-07 16:38:06 -04:00
lauren
0e43aa7f7a [snap] Remove unnecessary React.c override (#31141) 2024-10-07 16:31:59 -04:00
lauren
d2367f17d9 [rcr] Reexport React.__COMPILER_RUNTIME.c or fallback to polyfill (#31140)
This PR updates the standalone `react-compiler-runtime` package to
either re-export `React.__COMPILER_RUNTIME.c` or to use a userspace
polyfill.
2024-10-07 16:31:20 -04:00
lauren
b78a7f2f35 [rcr] Re-export useMemoCache in top level React namespace (#31139)
In order to support using the compiler on versions of React prior to 19,
we need the ability to statically import `c` (aka useMemoCache) or
fallback to a polyfill supplied by `react-compiler-runtime` (note: this
is a separate npm package, not to be confused with
`react/compiler-runtime`, which is currently a part of react).

To do this we first need to re-export `useMemoCache` under the top level
React namespace again, which is additive and thus non-breaking. Doing so
allows `react-compiler-runtime` to statically either re-export
`React.__COMPILER_RUNTIME.c` or supply a polyfill, without the need for
a dynamic import which is finicky to support due to returning a promise.

In later PRs I will remove `react/compiler-runtime` and update the
compiler to emit imports to `react-compiler-runtime` instead.
2024-10-07 16:25:04 -04:00
mofeiZ
68d59d43d5 [compiler][ez] Fix reanimated custom type defs for imports (#31137)
When we added support for Reanimated, we didn't distinguish between true
globals (i.e. identifiers with no static resolutions), module types, and
imports #29188. For the past 3-4 months, Reanimated imports were not
being matched to the correct hook / function shape we match globals and
module imports against two different registries.

This PR fixes our support for Reanimated library functions imported
under `react-native-reanimated`. See test fixtures for details
2024-10-07 13:09:39 -04:00
lauren
91c42a14c7 [rcr][ez] Clean up unused $read from rcr (#31136) 2024-10-07 12:39:14 -04:00
mofeiZ
1460d67c5b [compiler][hir] Only hoist always-accessed PropertyLoads from function decls (#31066)
Stack from [ghstack](https://github.com/ezyang/ghstack) (oldest at
bottom):
* __->__ #31066
* #31032

Prior to this PR, we consider all of a nested function's accessed paths
as 'hoistable' (to the basic block in which the function was defined).
Now, we traverse nested functions and find all paths hoistable to their
*entry block*.

Note that this only replaces the *hoisting* part of function
declarations, not dependencies. This realistically only affects optional
chains within functions, which always get truncated to its inner
non-optional path (see
[todo-infer-function-uncond-optionals-hoisted.tsx](576f3c0aa8/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.tsx))

See newly added test fixtures for details

Update: Note that toggling `enableTreatFunctionDepsAsConditional` makes
a non-trivial impact on granularity of inferred deps (i.e. we find that
function declarations uniquely identify some paths as hoistable).
Snapshot comparison of internal code shows ~2.5% of files get worse
dependencies ([internal
link](https://www.internalfb.com/phabricator/paste/view/P1625792186))
2024-10-03 14:41:32 -04:00
mofeiZ
edacbde73f [compiler][hir-rewrite] Check mutability of base identifier when hoisting (#31032)
Stack from [ghstack](https://github.com/ezyang/ghstack) (oldest at
bottom):
* #31066
* __->__ #31032

Prior to this PR, we check whether the property load source (e.g. the
evaluation of `<base>` in `<base>.property`) is mutable + scoped to
determine whether the property load itself is eligible for hoisting.
This changes to check the base identifier of the load.
- This is needed for the next PR #31066. We want to evaluate whether the
base identifier is mutable within the context of the *outermost
function*. This is because all LoadLocals and PropertyLoads within a
nested function declaration have mutable-ranges within the context of
the function, but the base identifier is a context variable.
- A side effect is that we no longer infer loads from props / other
function arguments as mutable in edge cases (e.g. props escaping out of
try-blocks or being assigned to context variables)
2024-10-03 13:55:59 -04:00
Mofei Zhang
0751fac747 [compiler] Optional chaining for dependencies (HIR rewrite)
Adds HIR version of `PropagateScopeDeps` to handle optional chaining.

Internally, this improves memoization on ~4% of compiled files (internal links: [1](https://www.internalfb.com/intern/paste/P1610406497/))

Summarizing the changes in this PR.
1. `CollectOptionalChainDependencies` recursively traverses optional blocks down to the base. From the base, we build up a set of `baseIdentifier.propertyA?.propertyB` mappings.
The tricky bit here is that optional blocks sometimes reference other optional blocks that are *not* part of the same chain e.g. a(c?.d)?.d. See code + comments in `traverseOptionalBlock` for how we avoid concatenating unrelated blocks.

2. Adding optional chains into non-null object calculation.
(Note that marking `a?.b` as 'non-null' means that `a?.b.c` is safe to evaluate, *not* `(a?.b).c`. Happy to rename this / reword comments accordingly if there's a better term)
This pass is split into two stages. (1) collecting non-null objects by block and (2) propagating non-null objects across blocks. The only significant change here was to (2). We add an extra reduce step `X=Reduce(Union(X, Intersect(X_neighbors)))` to merge optional and non-optional nodes (e.g. nonNulls=`{a, a?.b}` reduces to `{a, a.b}`)

3. Adding optional chains into dependency calculation.
This was the trickiest. We need to take the "maximal" property chain as a dependency. Prior to this PR, we avoided taking subpaths e.g. `a.b` of `a.b.c` as dependencies by only visiting non-PropertyLoad/LoadLocal instructions. This effectively only recorded the property-path at site-of-use.

    Unfortunately, this *quite* doesn't work for optional chains for a few reasons:
    - We would need to skip relevant `StoreLocal`/`Branch terminal` instructions (but only those within optional blocks that have been successfully read).
    - Given an optional chain, either (1) only a subpath or (2) the entire path can be represented as a PropertyLoad. We cannot directly add the last hoistable optional-block as a dependency as MethodCalls are an edge case e.g. given a?.b.c(), we should depend on `a?.b`, not `a?.b.c`
      This means that we add its dependency at either the innermost unhoistable optional-block or when encountering it within its phi-join.

4. Handle optional chains in DeriveMinimalDependenciesHIR.
This was also a bit tricky to formulate. Ideally, we would avoid a 2^3 case join (cond | uncond cfg, optional | not optional load, access | dependency). This PR attempts to simplify by building two trees
    1. First add each hoistable path into a tree containing `Optional | NonOptional` nodes.
    2. Then add each dependency into another tree containing `Optional | NonOptional`, `Access | Dependency` nodes, truncating the dependency at the earliest non-hoistable node (i.e. non-matching pair when walking the hoistable tree)

ghstack-source-id: a2170f2628
Pull Request resolved: https://github.com/facebook/react/pull/31037
2024-10-02 13:30:55 -04:00
Timothy Yung
459fd418cf Define HostInstance type for React Native (#31101)
## Summary

Creates a new `HostInstance` type for React Native, to more accurately
capture the intent most developers have when using the `NativeMethods`
type or `React.ElementRef<HostComponent<T>>`.

Since `React.ElementRef<HostComponent<T>>` is typed as
`React.AbstractComponent<T, NativeMethods>`, that means
`React.ElementRef<HostComponent<T>>` is equivalent to `NativeMethods`
which is equivalent to `HostInstance`.


## How did you test this change?

```
$ yarn
$ yarn flow fabric
```
2024-10-01 17:25:59 -07:00
Sebastian Markbåge
99c056abb0 [Flight] Allow aborting encodeReply (#31106)
Allow aborting encoding arguments to a Server Action if a Promise
doesn't resolve. That way at least part of the arguments can be used on
the receiving side. This leaves it unresolved in the stream rather than
encoding an error.

This should error on the receiving side when the stream closes but it
doesn't right now in the Edge/Browser versions because closing happens
immediately before we've had a chance to call `.then()` so the Chunks
are still in pending state. This is an existing bug also in
FlightClient.
2024-10-01 14:28:51 -04:00
Jack Pope
d8c90fa48d Disable infinite render loop detection (#31088)
We're seeing issues with this feature internally including bugs with
sibling prerendering and errors that are difficult for developers to
action on. We'll turn off the feature for the time being until we can
improve the stability and ergonomics.

This PR does two things:
- Turn off `enableInfiniteLoopDetection` everywhere while leaving it as
a variant on www so we can do further experimentation.
- Revert https://github.com/facebook/react/pull/31061 which was a
temporary change for debugging. This brings the feature back to
baseline.
2024-10-01 11:00:57 -04:00
Ruslan Lesiutin
6e612587ec chore[react-devtools]: drop legacy context tests (#31059)
We've dropped the support for detecting changes in legacy Contexts in
https://github.com/facebook/react/pull/30896.
2024-10-01 14:26:12 +01:00
Ruslan Lesiutin
9ea5ffa9cb chore[react-devtools]: add legacy mode error message to the ignore list for tests (#31060)
Without this, the console gets spammy whenever we run React DevTools
tests against React 18.x, where this deprecation message was added.
2024-10-01 14:04:05 +01:00
Ruslan Lesiutin
40357fe630 fix[react-devtools]: request hook initialization inside http server response (#31102)
Fixes https://github.com/facebook/react/issues/31100.

There are 2 things:
1. In https://github.com/facebook/react/pull/30987, we've introduced a
breaking change: importing `react-devtools-core` is no longer enough for
installing React DevTools global Hook. You need to call `initialize`, in
which you may provide initial settings. I am not adding settings here,
because it is not implemented, and there are no plans for supporting
this.
2. Calling `installHook` is not necessary inside `standalone.js`,
because this script is running inside Electron wrapper (which is just a
UI, not the app that we are debugging). We will loose the ability to use
React DevTools on this React application, but I guess thats fine.
2024-10-01 14:03:48 +01:00
Sebastian Markbåge
654e387d7e [Flight] Serialize Server Components Props in DEV (#31105)
This allows us to show props in React DevTools when inspecting a Server
Component.

I currently drastically limit the object depth that's serialized since
this is very implicit and you can have heavy objects on the server.

We previously was using the general outlineModel to outline
ReactComponentInfo but we weren't consistently using it everywhere which
could cause some bugs with the parsing when it got deduped on the
client. It also lead to the weird feature detect of `isReactComponent`.
It also meant that this serialization was using the plain serialization
instead of `renderConsoleValue` which means we couldn't safely serialize
arbitrary debug info that isn't serializable there.

So the main change here is to call `outlineComponentInfo` and have that
always write every "Server Component" instance as outlined and in a way
that lets its props be serialized using `renderConsoleValue`.

<img width="1150" alt="Screenshot 2024-10-01 at 1 25 05 AM"
src="https://github.com/user-attachments/assets/f6e7811d-51a3-46b9-bbe0-1b8276849ed4">
2024-10-01 01:39:20 -04:00
Sebastian Markbåge
326832a56d [Flight] Serialize Error Values (#31104)
The idea is that the RSC protocol is a superset of Structured Clone.
#25687 One exception that we left out was serializing Error objects as
values. We serialize "throws" or "rejections" as Error (regardless of
their type) but not Error values.

This fixes that by serializing `Error` objects. We don't include digest
in this case since we don't call `onError` and it's not really expected
that you'd log it on the server with some way to look it up.

In general this is not super useful outside throws. Especially since we
hide their values in prod. However, there is one case where it is quite
useful. When you replay console logs in DEV you might often log an Error
object within the scope of a Server Component. E.g. the default RSC
error handling just console.error and error object.

Before this would just be an empty object due to our lax console log
serialization:
<img width="1355" alt="Screenshot 2024-09-30 at 2 24 03 PM"
src="https://github.com/user-attachments/assets/694b3fd3-f95f-4863-9321-bcea3f5c5db4">
After:
<img width="1348" alt="Screenshot 2024-09-30 at 2 36 48 PM"
src="https://github.com/user-attachments/assets/834b129d-220d-43a2-a2f4-2eb06921747d">

TODO for a follow up: Flight Reply direction. This direction doesn't
actually serialize thrown errors because they always reject the
serialization.
2024-09-30 15:45:13 -04:00
Mofei Zhang
c67e241c16 [compiler] Renames and no-op refactor for next PR
Rename for clarity:
- `CollectHoistablePropertyLoads:Tree` -> `CollectHoistablePropertyLoads:PropertyPathRegistry`
    - `getPropertyLoadNode` -> `getOrCreateProperty`
    - `getOrCreateRoot` -> `getOrCreateIdentifier`
- `PropertyLoadNode` -> `PropertyPathNode`

Refactor to CFG joining logic for `CollectHoistablePropertyLoads`. We now write to the same set of inferredNonNullObjects when traversing from entry and exit blocks. This is more correct, as non-nulls inferred from a forward traversal should be included when computing the backward traversal (and vice versa). This fix is needed by an edge case in #31036

Added invariant into fixed-point iteration to terminate (instead of infinite looping).

ghstack-source-id: 1e8eb2d566b649ede93de9a9c13dad09b96416a5
Pull Request resolved: https://github.com/facebook/react/pull/31036
2024-09-30 12:35:16 -04:00
Mofei Zhang
2cbea245cc [compiler][fixtures] Patch error-handling edge case in snap evaluator
Fix edge case in which we incorrectly returned a cached exception instead of trying to rerender with new props.
ghstack-source-id: 843fb85df4a2ae7a88f296104fb16b5f9a34c76e
Pull Request resolved: https://github.com/facebook/react/pull/31082
2024-09-30 12:35:16 -04:00
Mofei Zhang
5d12e9e10b [compiler] repro for dep merging edge case (non-hir)
Found when writing #31037, summary copied from comments:

This is an extreme edge case and not code we'd expect any reasonable developer to write. In most cases e.g. `(a?.b != null ? a.b : DEFAULT)`, we do want to take a dependency on `a?.b`.

I found this trying to come up with edge cases that break the current dependency + CFG merging logic. I think it makes sense to error on the side of correctness. After all, we still take `a` as a dependency if users write `a != null ? a.b : DEFAULT`, and the same fix (understanding the `<hoistable> != null` test expression) works for both. Can be convinced otherwise though!

ghstack-source-id: cc06afda59f7681e228495f5e35a596c20f875f5
Pull Request resolved: https://github.com/facebook/react/pull/31035
2024-09-30 12:35:16 -04:00
Mofei Zhang
58a3ca3b47 [compiler][hir-rewrite] Cleanup Identifier -> IdentifierId
Since removing ExitSSA, Identifier and IdentifierId should mean the same thing

ghstack-source-id: 076cacbe8360e716b0555088043502823f9ee72e
Pull Request resolved: https://github.com/facebook/react/pull/31034
2024-09-30 12:35:16 -04:00
Mofei Zhang
8c89fa7643 [compiler][hir-rewrite] Infer non-null props, destructure source
Followup from #30894.
This adds a new flagged mode `enablePropagateScopeDepsInHIR: "enabled_with_optimizations"`, under which we infer more hoistable loads:
- it's always safe to evaluate loads from `props` (i.e. first parameter of a `component`)
- destructuring sources are safe to evaluate loads from (e.g. given `{x} = obj`, we infer that it's safe to evaluate obj.y)
- computed load sources are safe to evaluate loads from (e.g. given `arr[0]`, we can infer that it's safe to evaluate arr.length)

ghstack-source-id: 32f3bb72e9f85922825579bd785d636f4ccf724d
Pull Request resolved: https://github.com/facebook/react/pull/31033
2024-09-30 12:35:16 -04:00
Mofei Zhang
1a779207a7 [compiler][test fixtures] Add enablePropagateDepsInHIR to forked tests
Annotates fixtures added in #31030 with `@enablePropagateDepsInHIR` to fork behavior (and commit snapshot differences)
ghstack-source-id: e423e8c42db62f1bb87562b770761be09fc8ffc6
Pull Request resolved: https://github.com/facebook/react/pull/31031
2024-09-30 12:35:15 -04:00
Mofei Zhang
943e45e910 [compiler][test fixtures] Fork more fixtures for hir-rewrite
Followup from #30894 , not sure how these got missed. Note that this PR just copies the fixtures without adding `@enablePropagateDepsInHIR`. #31032 follows and actually enables the HIR-version of propagateScopeDeps to run. I split this out into two PRs to make snapshot differences easier to review, but also happy to merge

Fixtures found from locally setting snap test runner to default to `enablePropagateDepsInHIR: 'enabled_baseline'` and forking fixtures files with different output.

ghstack-source-id: 7d7cf41aa923d83ad49f89079171b0411923ce6b
Pull Request resolved: https://github.com/facebook/react/pull/31030
2024-09-30 12:35:15 -04:00
Ruslan Lesiutin
2d16326d9a fix[scripts/devtools/publish-release]: parse version list instead of handling 404 (#31087)
Discovered yesterday while was publishing a new release.

NPM `10.x.x` changed the text for 404 errors, so this check was failing.
Instead of handling 404 as a signal, I think its better to just parse
the whole list of versions and check if the new one is already there.
2024-09-30 17:07:54 +01:00
Lauren Tan
db240980a3 [playground] Decouple playground from compiler
Currently the playground is setup as a linked workspace for the
compiler which complicates our yarn workspace setup and means that snap
can sometimes pull in a different version of react than was otherwise
specified.

There's no real reason to have these workspaces combined so let's split
them up.

ghstack-source-id: 56ab064b2f
Pull Request resolved: https://github.com/facebook/react/pull/31081
2024-09-27 15:25:07 -04:00
Lauren Tan
3edc000d77 [compiler] Fix broken tests
ghstack-source-id: 000a37ae1f819eef676dcd52410d5231cd2d50fe
Pull Request resolved: https://github.com/facebook/react/pull/31078
2024-09-26 17:44:28 -04:00
Lauren Tan
d34da5cdb9 [ci] Fix incorrect sha / commit messages in manual Meta builds
ghstack-source-id: 0790b32d29
Pull Request resolved: https://github.com/facebook/react/pull/31083
2024-09-26 17:43:07 -04:00
Josh Story
67fee58b1f [Fizz] Start initial work immediately (#31079)
In a recent update we make Flight start working immediately rather than
waitin for a new task. This commit updates fizz to have similar
mechanics. We start the render in the currently running task but we do
so in a microtask to avoid reentrancy. This aligns Fizz with Flight.

ref: https://github.com/facebook/react/pull/30961
2024-09-26 13:51:45 -07:00
Ricky
76aee6f39d Revert "Make prerendering always non-blocking" (#31080)
Reverts facebook/react#31056
2024-09-26 16:48:57 -04:00
Jack Pope
60b1420f18 Turn on lazy context propagation for RN and RTR (#31076)
Following https://github.com/facebook/react/pull/30935 let's turn this
on across the board so we can clean up experiments in RN.
2024-09-26 12:55:46 -04:00
Edmond Chui
204a551eae Add: reload to profile for Fusebox (#31021)
## Summary

Add reload to profile for Fusebox 

Stacked on #31048. See
6be1977112

## How did you test this change?

Test E2E in [D63233256](https://www.internalfb.com/diff/D63233256)
2024-09-26 16:39:51 +01:00
Ruslan Lesiutin
b091ef7e0b fix: update release scripts for react devtools (#31069)
This has been broken since the migration to GitHub actions.

Previously, we've been using `buildId` as an identifier from CircleCI.
I've decided to use a commit hash as an identifier, because I don't know
if there is a better option, and
`scripts/release/download_build_artifacts.js` allows us to download them
for a specific commit.
2024-09-26 15:42:59 +01:00
Lauren Tan
b90e440231 [dependabot] Ignore all fixture directories
Seems like we can specify a wildcard dependency name to ignore all
dependencies from being updated. As I understand it dependabot will
still run monthly but no PRs will be generated.

ghstack-source-id: 64b76bd532663cdc4db10ba6299e791b5908d5b1
Pull Request resolved: https://github.com/facebook/react/pull/31074
2024-09-26 10:34:35 -04:00
Lauren Tan
0e9ccde185 [dependabot] Remove stale directories from config
ghstack-source-id: 570399bc77529bf9fb005149cfd20ba59405b2bc
Pull Request resolved: https://github.com/facebook/react/pull/31073
2024-09-26 10:34:35 -04:00
Lauren Tan
b7f74f39d8 [ez] Update deprecated action
ghstack-source-id: 991b314fd610bcbca68df52149866b2c6d8e6799
Pull Request resolved: https://github.com/facebook/react/pull/31047
2024-09-26 10:20:10 -04:00
Lauren Tan
c900ee0cce [ez] Make commit sha clickable for fbsource
ghstack-source-id: 1307b9a83a8613c08f11be5397ab45719df00992
Pull Request resolved: https://github.com/facebook/react/pull/31046
2024-09-26 10:20:10 -04:00
Lauren Tan
64be7b4dcc [ci] Fix missing values during manual workflow dispatch
It seems like the github.event.workflow_run payload is only populated
for non manual runs of the workflow, so this would crash the manual
dispatch

Test plan: https://github.com/facebook/react/actions/runs/11017512571
completes

ghstack-source-id: fce02b17f85ac4762de0561764785882e767c872
Pull Request resolved: https://github.com/facebook/react/pull/31045
2024-09-26 10:20:10 -04:00
dependabot[bot]
db320ea4d9 Bump rollup from 3.20.0 to 3.29.5 (#31072)
Bumps [rollup](https://github.com/rollup/rollup) from 3.20.0 to 3.29.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rollup/rollup/releases">rollup's
releases</a>.</em></p>
<blockquote>
<h2>v3.29.4</h2>
<h2>3.29.4</h2>
<p><em>2023-09-28</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Fix static analysis when an exported function uses callbacks (<a
href="https://redirect.github.com/rollup/rollup/issues/5158">#5158</a>)</li>
</ul>
<h3>Pull Requests</h3>
<ul>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5158">#5158</a>:
Deoptimize all parameters when losing track of a function (<a
href="https://github.com/lukastaegert"><code>@​lukastaegert</code></a>)</li>
</ul>
<h2>v3.29.3</h2>
<h2>3.29.3</h2>
<p><em>2023-09-24</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Fix a bug where code was wrongly tree-shaken after mutating function
parameters (<a
href="https://redirect.github.com/rollup/rollup/issues/5153">#5153</a>)</li>
</ul>
<h3>Pull Requests</h3>
<ul>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5145">#5145</a>:
docs: improve the docs repl appearance in the light mode (<a
href="https://github.com/TrickyPi"><code>@​TrickyPi</code></a>)</li>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5148">#5148</a>:
chore(deps): update dependency
<code>@​vue/eslint-config-typescript</code> to v12 (<a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot])</li>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5149">#5149</a>:
chore(deps): lock file maintenance minor/patch updates (<a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot])</li>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5153">#5153</a>:
Fully deoptimize first level path when deoptimizing nested parameter
paths (<a
href="https://github.com/lukastaegert"><code>@​lukastaegert</code></a>)</li>
</ul>
<h2>v3.29.2</h2>
<h2>3.29.2</h2>
<p><em>2023-09-15</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Export <code>TreeshakingPreset</code> type (<a
href="https://redirect.github.com/rollup/rollup/issues/5131">#5131</a>)</li>
</ul>
<h3>Pull Requests</h3>
<ul>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5131">#5131</a>:
fix: exports <code>TreeshakingPreset</code> (<a
href="https://github.com/moltar"><code>@​moltar</code></a>)</li>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5134">#5134</a>:
docs: steps to enable symlinks on windows (<a
href="https://github.com/thebanjomatic"><code>@​thebanjomatic</code></a>)</li>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5137">#5137</a>:
chore(deps): lock file maintenance minor/patch updates (<a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot])</li>
</ul>
<h2>v3.29.1</h2>
<h2>3.29.1</h2>
<p><em>2023-09-10</em></p>
<h3>Bug Fixes</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rollup/rollup/blob/master/CHANGELOG.md">rollup's
changelog</a>.</em></p>
<blockquote>
<h1>rollup changelog</h1>
<h2>4.22.4</h2>
<p><em>2024-09-21</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Fix a vulnerability in generated code that affects IIFE, UMD and CJS
bundles when run in a browser context (<a
href="https://redirect.github.com/rollup/rollup/issues/5671">#5671</a>)</li>
</ul>
<h3>Pull Requests</h3>
<ul>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5670">#5670</a>:
refactor: Use object.prototype to check for reserved properties (<a
href="https://github.com/YuHyeonWook"><code>@​YuHyeonWook</code></a>)</li>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5671">#5671</a>:
Fix DOM Clobbering CVE (<a
href="https://github.com/lukastaegert"><code>@​lukastaegert</code></a>)</li>
</ul>
<h2>4.22.3</h2>
<p><em>2024-09-21</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Ensure that mutations in modules without side effects are observed
while properly handling transitive dependencies (<a
href="https://redirect.github.com/rollup/rollup/issues/5669">#5669</a>)</li>
</ul>
<h3>Pull Requests</h3>
<ul>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5669">#5669</a>:
Ensure impure dependencies of pure modules are added (<a
href="https://github.com/lukastaegert"><code>@​lukastaegert</code></a>)</li>
</ul>
<h2>4.22.2</h2>
<p><em>2024-09-20</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Revert fix for side effect free modules until other issues are
investigated (<a
href="https://redirect.github.com/rollup/rollup/issues/5667">#5667</a>)</li>
</ul>
<h3>Pull Requests</h3>
<ul>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5667">#5667</a>:
Partially revert <a
href="https://redirect.github.com/rollup/rollup/issues/5658">#5658</a>
and re-apply <a
href="https://redirect.github.com/rollup/rollup/issues/5644">#5644</a>
(<a
href="https://github.com/lukastaegert"><code>@​lukastaegert</code></a>)</li>
</ul>
<h2>4.22.1</h2>
<p><em>2024-09-20</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Revert <a
href="https://redirect.github.com/rollup/rollup/issues/5644">#5644</a>
&quot;stable chunk hashes&quot; while issues are being investigated</li>
</ul>
<h3>Pull Requests</h3>
<ul>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5663">#5663</a>:
chore(deps): update dependency inquirer to v11 (<a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot], <a
href="https://github.com/lukastaegert"><code>@​lukastaegert</code></a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="dfd233d3a9"><code>dfd233d</code></a>
3.29.5</li>
<li><a
href="2ef77c00ec"><code>2ef77c0</code></a>
Fix DOM Clobbering CVE</li>
<li><a
href="a6448b99f7"><code>a6448b9</code></a>
3.29.4</li>
<li><a
href="4e92d60fa9"><code>4e92d60</code></a>
Deoptimize all parameters when losing track of a function (<a
href="https://redirect.github.com/rollup/rollup/issues/5158">#5158</a>)</li>
<li><a
href="801ffd1a9b"><code>801ffd1</code></a>
3.29.3</li>
<li><a
href="353e462828"><code>353e462</code></a>
Fully deoptimize first level path when deoptimizing nested parameter
paths (#...</li>
<li><a
href="a1a89e77fb"><code>a1a89e7</code></a>
chore(deps): update dependency
<code>@​vue/eslint-config-typescript</code> to v12 (<a
href="https://redirect.github.com/rollup/rollup/issues/5148">#5148</a>)</li>
<li><a
href="cc14f7070a"><code>cc14f70</code></a>
chore(deps): lock file maintenance minor/patch updates (<a
href="https://redirect.github.com/rollup/rollup/issues/5149">#5149</a>)</li>
<li><a
href="1e8355b2b6"><code>1e8355b</code></a>
docs: improve the docs repl appearance in the light mode (<a
href="https://redirect.github.com/rollup/rollup/issues/5145">#5145</a>)</li>
<li><a
href="5950fc8a6b"><code>5950fc8</code></a>
Adapt branches in REPL workflow</li>
<li>Additional commits viewable in <a
href="https://github.com/rollup/rollup/compare/v3.20.0...v3.29.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rollup&package-manager=npm_and_yarn&previous-version=3.20.0&new-version=3.29.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/facebook/react/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-26 09:56:33 -04:00
dependabot[bot]
f7aff7f15c Bump express from 4.18.2 to 4.21.0 (#30980)
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to
4.21.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/expressjs/express/releases">express's
releases</a>.</em></p>
<blockquote>
<h2>4.21.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Deprecate <code>&quot;back&quot;</code> magic string in redirects by
<a href="https://github.com/blakeembrey"><code>@​blakeembrey</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/5935">expressjs/express#5935</a></li>
<li>finalhandler@1.3.1 by <a
href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5954">expressjs/express#5954</a></li>
<li>fix(deps): serve-static@1.16.2 by <a
href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5951">expressjs/express#5951</a></li>
<li>Upgraded dependency qs to 6.13.0 to match qs in body-parser by <a
href="https://github.com/agadzinski93"><code>@​agadzinski93</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/5946">expressjs/express#5946</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/agadzinski93"><code>@​agadzinski93</code></a>
made their first contribution in <a
href="https://redirect.github.com/expressjs/express/pull/5946">expressjs/express#5946</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/expressjs/express/compare/4.20.0...4.21.0">https://github.com/expressjs/express/compare/4.20.0...4.21.0</a></p>
<h2>4.20.0</h2>
<h2>What's Changed</h2>
<h3>Important</h3>
<ul>
<li>IMPORTANT: The default <code>depth</code> level for parsing
URL-encoded data is now <code>32</code> (previously was
<code>Infinity</code>)</li>
<li>Remove link renderization in html while using
<code>res.redirect</code></li>
</ul>
<h3>Other Changes</h3>
<ul>
<li>4.19.2 Staging by <a
href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5561">expressjs/express#5561</a></li>
<li>remove duplicate location test for data uri by <a
href="https://github.com/wesleytodd"><code>@​wesleytodd</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5562">expressjs/express#5562</a></li>
<li>feat: document beta releases expectations by <a
href="https://github.com/marco-ippolito"><code>@​marco-ippolito</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/5565">expressjs/express#5565</a></li>
<li>Cut down on duplicated CI runs by <a
href="https://github.com/jonchurch"><code>@​jonchurch</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5564">expressjs/express#5564</a></li>
<li>Add a Threat Model by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/5526">expressjs/express#5526</a></li>
<li>Assign captain of encodeurl by <a
href="https://github.com/blakeembrey"><code>@​blakeembrey</code></a> in
<a
href="https://redirect.github.com/expressjs/express/pull/5579">expressjs/express#5579</a></li>
<li>Nominate jonchurch as repo captain for <code>http-errors</code>,
<code>expressjs.com</code>, <code>morgan</code>, <code>cors</code>,
<code>body-parser</code> by <a
href="https://github.com/jonchurch"><code>@​jonchurch</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5587">expressjs/express#5587</a></li>
<li>docs: update Security.md by <a
href="https://github.com/inigomarquinez"><code>@​inigomarquinez</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/5590">expressjs/express#5590</a></li>
<li>docs: update triage nomination policy by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/5600">expressjs/express#5600</a></li>
<li>Add CodeQL (SAST) by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/5433">expressjs/express#5433</a></li>
<li>docs: add UlisesGascon as triage initiative captain by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/5605">expressjs/express#5605</a></li>
<li>deps: encodeurl@~2.0.0 by <a
href="https://github.com/blakeembrey"><code>@​blakeembrey</code></a> in
<a
href="https://redirect.github.com/expressjs/express/pull/5569">expressjs/express#5569</a></li>
<li>skip QUERY method test by <a
href="https://github.com/jonchurch"><code>@​jonchurch</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5628">expressjs/express#5628</a></li>
<li>ignore ETAG query test on 21 and 22, reuse skip util by <a
href="https://github.com/jonchurch"><code>@​jonchurch</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5639">expressjs/express#5639</a></li>
<li>add support Node.js@22 in the CI by <a
href="https://github.com/mertcanaltin"><code>@​mertcanaltin</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/5627">expressjs/express#5627</a></li>
<li>doc: add table of contents, tc/triager lists to readme by <a
href="https://github.com/mertcanaltin"><code>@​mertcanaltin</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/5619">expressjs/express#5619</a></li>
<li>List and sort all projects, add captains by <a
href="https://github.com/blakeembrey"><code>@​blakeembrey</code></a> in
<a
href="https://redirect.github.com/expressjs/express/pull/5653">expressjs/express#5653</a></li>
<li>docs: add <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
as captain for cookie-parser by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/5666">expressjs/express#5666</a></li>
<li> bring back query tests for node 21 by <a
href="https://github.com/ctcpip"><code>@​ctcpip</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5690">expressjs/express#5690</a></li>
<li>[v4] Deprecate <code>res.clearCookie</code> accepting
<code>options.maxAge</code> and <code>options.expires</code> by <a
href="https://github.com/jonchurch"><code>@​jonchurch</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5672">expressjs/express#5672</a></li>
<li>skip QUERY tests for Node 21 only, still not supported by <a
href="https://github.com/jonchurch"><code>@​jonchurch</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5695">expressjs/express#5695</a></li>
<li>📝 update people, add ctcpip to TC by <a
href="https://github.com/ctcpip"><code>@​ctcpip</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5683">expressjs/express#5683</a></li>
<li>remove minor version pinning from ci by <a
href="https://github.com/jonchurch"><code>@​jonchurch</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5722">expressjs/express#5722</a></li>
<li>Fix link variable use in attribution section of CODE OF CONDUCT by
<a href="https://github.com/IamLizu"><code>@​IamLizu</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5762">expressjs/express#5762</a></li>
<li>Replace Appveyor windows testing with GHA by <a
href="https://github.com/jonchurch"><code>@​jonchurch</code></a> in <a
href="https://redirect.github.com/expressjs/express/pull/5599">expressjs/express#5599</a></li>
<li>Add OSSF Scorecard badge by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/5436">expressjs/express#5436</a></li>
<li>update scorecard link by <a
href="https://github.com/bjohansebas"><code>@​bjohansebas</code></a> in
<a
href="https://redirect.github.com/expressjs/express/pull/5814">expressjs/express#5814</a></li>
<li>Nominate <a
href="https://github.com/IamLizu"><code>@​IamLizu</code></a> to the
triage team by <a
href="https://github.com/UlisesGascon"><code>@​UlisesGascon</code></a>
in <a
href="https://redirect.github.com/expressjs/express/pull/5836">expressjs/express#5836</a></li>
<li>deps: path-to-regexp@0.1.8 by <a
href="https://github.com/blakeembrey"><code>@​blakeembrey</code></a> in
<a
href="https://redirect.github.com/expressjs/express/pull/5603">expressjs/express#5603</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/expressjs/express/blob/4.21.0/History.md">express's
changelog</a>.</em></p>
<blockquote>
<h1>4.21.0 / 2024-09-11</h1>
<ul>
<li>Deprecate <code>res.location(&quot;back&quot;)</code> and
<code>res.redirect(&quot;back&quot;)</code> magic string</li>
<li>deps: serve-static@1.16.2
<ul>
<li>includes send@0.19.0</li>
</ul>
</li>
<li>deps: finalhandler@1.3.1</li>
<li>deps: qs@6.13.0</li>
</ul>
<h1>4.20.0 / 2024-09-10</h1>
<ul>
<li>deps: serve-static@0.16.0
<ul>
<li>Remove link renderization in html while redirecting</li>
</ul>
</li>
<li>deps: send@0.19.0
<ul>
<li>Remove link renderization in html while redirecting</li>
</ul>
</li>
<li>deps: body-parser@0.6.0
<ul>
<li>add <code>depth</code> option to customize the depth level in the
parser</li>
<li>IMPORTANT: The default <code>depth</code> level for parsing
URL-encoded data is now <code>32</code> (previously was
<code>Infinity</code>)</li>
</ul>
</li>
<li>Remove link renderization in html while using
<code>res.redirect</code></li>
<li>deps: path-to-regexp@0.1.10
<ul>
<li>Adds support for named matching groups in the routes using a
regex</li>
<li>Adds backtracking protection to parameters without regexes
defined</li>
</ul>
</li>
<li>deps: encodeurl@~2.0.0
<ul>
<li>Removes encoding of <code>\</code>, <code>|</code>, and
<code>^</code> to align better with URL spec</li>
</ul>
</li>
<li>Deprecate passing <code>options.maxAge</code> and
<code>options.expires</code> to <code>res.clearCookie</code>
<ul>
<li>Will be ignored in v5, clearCookie will set a cookie with an expires
in the past to instruct clients to delete the cookie</li>
</ul>
</li>
</ul>
<h1>4.19.2 / 2024-03-25</h1>
<ul>
<li>Improved fix for open redirect allow list bypass</li>
</ul>
<h1>4.19.1 / 2024-03-20</h1>
<ul>
<li>Allow passing non-strings to res.location with new encoding handling
checks</li>
</ul>
<h1>4.19.0 / 2024-03-20</h1>
<ul>
<li>Prevent open redirect allow list bypass due to encodeurl</li>
<li>deps: cookie@0.6.0</li>
</ul>
<h1>4.18.3 / 2024-02-29</h1>
<ul>
<li>Fix routing requests without method</li>
<li>deps: body-parser@1.20.2
<ul>
<li>Fix strict json error message on Node.js 19+</li>
<li>deps: content-type@~1.0.5</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="7e562c6d8d"><code>7e562c6</code></a>
4.21.0</li>
<li><a
href="1bcde96bc8"><code>1bcde96</code></a>
fix(deps): qs@6.13.0 (<a
href="https://redirect.github.com/expressjs/express/issues/5946">#5946</a>)</li>
<li><a
href="7d36477568"><code>7d36477</code></a>
fix(deps): serve-static@1.16.2 (<a
href="https://redirect.github.com/expressjs/express/issues/5951">#5951</a>)</li>
<li><a
href="40d2d8f2c8"><code>40d2d8f</code></a>
fix(deps): finalhandler@1.3.1</li>
<li><a
href="77ada906db"><code>77ada90</code></a>
Deprecate <code>&quot;back&quot;</code> magic string in redirects (<a
href="https://redirect.github.com/expressjs/express/issues/5935">#5935</a>)</li>
<li><a
href="21df421ebc"><code>21df421</code></a>
4.20.0</li>
<li><a
href="4c9ddc1c47"><code>4c9ddc1</code></a>
feat: upgrade to serve-static@0.16.0</li>
<li><a
href="9ebe5d500d"><code>9ebe5d5</code></a>
feat: upgrade to send@0.19.0 (<a
href="https://redirect.github.com/expressjs/express/issues/5928">#5928</a>)</li>
<li><a
href="ec4a01b6b8"><code>ec4a01b</code></a>
feat: upgrade to body-parser@1.20.3 (<a
href="https://redirect.github.com/expressjs/express/issues/5926">#5926</a>)</li>
<li><a
href="54271f69b5"><code>54271f6</code></a>
fix: don't render redirect values in anchor href</li>
<li>Additional commits viewable in <a
href="https://github.com/expressjs/express/compare/4.18.2...4.21.0">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~wesleytodd">wesleytodd</a>, a new releaser
for express since your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=express&package-manager=npm_and_yarn&previous-version=4.18.2&new-version=4.21.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/facebook/react/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-26 09:56:24 -04:00
dependabot[bot]
f04b8fc88e Bump ws from 6.2.2 to 6.2.3 in /scripts/release (#30463)
Bumps [ws](https://github.com/websockets/ws) from 6.2.2 to 6.2.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/websockets/ws/releases">ws's
releases</a>.</em></p>
<blockquote>
<h2>6.2.3</h2>
<h1>Bug fixes</h1>
<ul>
<li>Backported e55e5106 to the 6.x release line (eeb76d31).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="d87f3b6d3a"><code>d87f3b6</code></a>
[dist] 6.2.3</li>
<li><a
href="eeb76d313e"><code>eeb76d3</code></a>
[security] Fix crash when the Upgrade header cannot be read (<a
href="https://redirect.github.com/websockets/ws/issues/2231">#2231</a>)</li>
<li>See full diff in <a
href="https://github.com/websockets/ws/compare/6.2.2...6.2.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ws&package-manager=npm_and_yarn&previous-version=6.2.2&new-version=6.2.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/facebook/react/network/alerts).

</details>

> **Note**
> Automatic rebases have been disabled on this pull request as it has
been open for over 30 days.

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-26 09:56:10 -04:00
dependabot[bot]
d0772d5866 Bump axios from 1.7.1 to 1.7.4 in /compiler (#30694)
Bumps [axios](https://github.com/axios/axios) from 1.7.1 to 1.7.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/axios/axios/releases">axios's
releases</a>.</em></p>
<blockquote>
<h2>Release v1.7.4</h2>
<h2>Release notes:</h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>sec:</strong> CVE-2024-39338 (<a
href="https://redirect.github.com/axios/axios/issues/6539">#6539</a>)
(<a
href="https://redirect.github.com/axios/axios/issues/6543">#6543</a>)
(<a
href="6b6b605eaf">6b6b605</a>)</li>
<li><strong>sec:</strong> disregard protocol-relative URL to remediate
SSRF (<a
href="https://redirect.github.com/axios/axios/issues/6539">#6539</a>)
(<a
href="07a661a2a6">07a661a</a>)</li>
</ul>
<h3>Contributors to this release</h3>
<ul>
<li><!-- raw HTML omitted --> <a href="https://github.com/levpachmanov"
title="+47/-11 ([#6543](https://github.com/axios/axios/issues/6543)
)">Lev Pachmanov</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/hainenber"
title="+49/-4 ([#6539](https://github.com/axios/axios/issues/6539) )">Đỗ
Trọng Hải</a></li>
</ul>
<h2>Release v1.7.3</h2>
<h2>Release notes:</h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>adapter:</strong> fix progress event emitting; (<a
href="https://redirect.github.com/axios/axios/issues/6518">#6518</a>)
(<a
href="e3c76fc9bd">e3c76fc</a>)</li>
<li><strong>fetch:</strong> fix withCredentials request config (<a
href="https://redirect.github.com/axios/axios/issues/6505">#6505</a>)
(<a
href="85d4d0ea0a">85d4d0e</a>)</li>
<li><strong>xhr:</strong> return original config on errors from XHR
adapter (<a
href="https://redirect.github.com/axios/axios/issues/6515">#6515</a>)
(<a
href="8966ee7ea6">8966ee7</a>)</li>
</ul>
<h3>Contributors to this release</h3>
<ul>
<li><!-- raw HTML omitted --> <a
href="https://github.com/DigitalBrainJS" title="+211/-159
([#6518](https://github.com/axios/axios/issues/6518)
[#6519](https://github.com/axios/axios/issues/6519) )">Dmitriy
Mozgovoy</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/ValeraS"
title="+3/-3 ([#6515](https://github.com/axios/axios/issues/6515)
)">Valerii Sidorenko</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/prianyu"
title="+2/-2 ([#6505](https://github.com/axios/axios/issues/6505)
)">prianYu</a></li>
</ul>
<h2>Release v1.7.2</h2>
<h2>Release notes:</h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>fetch:</strong> enhance fetch API detection; (<a
href="https://redirect.github.com/axios/axios/issues/6413">#6413</a>)
(<a
href="4f79aef81b">4f79aef</a>)</li>
</ul>
<h3>Contributors to this release</h3>
<ul>
<li><!-- raw HTML omitted --> <a
href="https://github.com/DigitalBrainJS" title="+3/-3
([#6413](https://github.com/axios/axios/issues/6413) )">Dmitriy
Mozgovoy</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/axios/axios/blob/v1.x/CHANGELOG.md">axios's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/axios/axios/compare/v1.7.3...v1.7.4">1.7.4</a>
(2024-08-13)</h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>sec:</strong> CVE-2024-39338 (<a
href="https://redirect.github.com/axios/axios/issues/6539">#6539</a>)
(<a
href="https://redirect.github.com/axios/axios/issues/6543">#6543</a>)
(<a
href="6b6b605eaf">6b6b605</a>)</li>
<li><strong>sec:</strong> disregard protocol-relative URL to remediate
SSRF (<a
href="https://redirect.github.com/axios/axios/issues/6539">#6539</a>)
(<a
href="07a661a2a6">07a661a</a>)</li>
</ul>
<h3>Contributors to this release</h3>
<ul>
<li><!-- raw HTML omitted --> <a href="https://github.com/levpachmanov"
title="+47/-11 ([#6543](https://github.com/axios/axios/issues/6543)
)">Lev Pachmanov</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/hainenber"
title="+49/-4 ([#6539](https://github.com/axios/axios/issues/6539) )">Đỗ
Trọng Hải</a></li>
</ul>
<h2><a
href="https://github.com/axios/axios/compare/v1.7.2...v1.7.3">1.7.3</a>
(2024-08-01)</h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>adapter:</strong> fix progress event emitting; (<a
href="https://redirect.github.com/axios/axios/issues/6518">#6518</a>)
(<a
href="e3c76fc9bd">e3c76fc</a>)</li>
<li><strong>fetch:</strong> fix withCredentials request config (<a
href="https://redirect.github.com/axios/axios/issues/6505">#6505</a>)
(<a
href="85d4d0ea0a">85d4d0e</a>)</li>
<li><strong>xhr:</strong> return original config on errors from XHR
adapter (<a
href="https://redirect.github.com/axios/axios/issues/6515">#6515</a>)
(<a
href="8966ee7ea6">8966ee7</a>)</li>
</ul>
<h3>Contributors to this release</h3>
<ul>
<li><!-- raw HTML omitted --> <a
href="https://github.com/DigitalBrainJS" title="+211/-159
([#6518](https://github.com/axios/axios/issues/6518)
[#6519](https://github.com/axios/axios/issues/6519) )">Dmitriy
Mozgovoy</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/ValeraS"
title="+3/-3 ([#6515](https://github.com/axios/axios/issues/6515)
)">Valerii Sidorenko</a></li>
<li><!-- raw HTML omitted --> <a href="https://github.com/prianyu"
title="+2/-2 ([#6505](https://github.com/axios/axios/issues/6505)
)">prianYu</a></li>
</ul>
<h2><a
href="https://github.com/axios/axios/compare/v1.7.1...v1.7.2">1.7.2</a>
(2024-05-21)</h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>fetch:</strong> enhance fetch API detection; (<a
href="https://redirect.github.com/axios/axios/issues/6413">#6413</a>)
(<a
href="4f79aef81b">4f79aef</a>)</li>
</ul>
<h3>Contributors to this release</h3>
<ul>
<li><!-- raw HTML omitted --> <a
href="https://github.com/DigitalBrainJS" title="+3/-3
([#6413](https://github.com/axios/axios/issues/6413) )">Dmitriy
Mozgovoy</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="abd24a7367"><code>abd24a7</code></a>
chore(release): v1.7.4 (<a
href="https://redirect.github.com/axios/axios/issues/6544">#6544</a>)</li>
<li><a
href="6b6b605eaf"><code>6b6b605</code></a>
fix(sec): CVE-2024-39338 (<a
href="https://redirect.github.com/axios/axios/issues/6539">#6539</a>)
(<a
href="https://redirect.github.com/axios/axios/issues/6543">#6543</a>)</li>
<li><a
href="07a661a2a6"><code>07a661a</code></a>
fix(sec): disregard protocol-relative URL to remediate SSRF (<a
href="https://redirect.github.com/axios/axios/issues/6539">#6539</a>)</li>
<li><a
href="c6cce43cd9"><code>c6cce43</code></a>
chore(release): v1.7.3 (<a
href="https://redirect.github.com/axios/axios/issues/6521">#6521</a>)</li>
<li><a
href="e3c76fc9bd"><code>e3c76fc</code></a>
fix(adapter): fix progress event emitting; (<a
href="https://redirect.github.com/axios/axios/issues/6518">#6518</a>)</li>
<li><a
href="85d4d0ea0a"><code>85d4d0e</code></a>
fix(fetch): fix withCredentials request config (<a
href="https://redirect.github.com/axios/axios/issues/6505">#6505</a>)</li>
<li><a
href="92cd8ed943"><code>92cd8ed</code></a>
chore(github): update ISSUE_TEMPLATE.md (<a
href="https://redirect.github.com/axios/axios/issues/6519">#6519</a>)</li>
<li><a
href="8966ee7ea6"><code>8966ee7</code></a>
fix(xhr): return original config on errors from XHR adapter (<a
href="https://redirect.github.com/axios/axios/issues/6515">#6515</a>)</li>
<li><a
href="0e4f9fa290"><code>0e4f9fa</code></a>
chore(release): v1.7.2 (<a
href="https://redirect.github.com/axios/axios/issues/6414">#6414</a>)</li>
<li><a
href="4f79aef81b"><code>4f79aef</code></a>
fix(fetch): enhance fetch API detection; (<a
href="https://redirect.github.com/axios/axios/issues/6413">#6413</a>)</li>
<li>See full diff in <a
href="https://github.com/axios/axios/compare/v1.7.1...v1.7.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=axios&package-manager=npm_and_yarn&previous-version=1.7.1&new-version=1.7.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/facebook/react/network/alerts).

</details>

> **Note**
> Automatic rebases have been disabled on this pull request as it has
been open for over 30 days.

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-26 09:51:12 -04:00
dependabot[bot]
9927ab238b Bump rollup from 4.13.2 to 4.22.4 in /compiler (#31039)
Bumps [rollup](https://github.com/rollup/rollup) from 4.13.2 to 4.22.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/rollup/rollup/releases">rollup's
releases</a>.</em></p>
<blockquote>
<h2>v4.22.4</h2>
<h2>4.22.4</h2>
<p><em>2024-09-21</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Fix a vulnerability in generated code that affects IIFE, UMD and CJS
bundles when run in a browser context (<a
href="https://redirect.github.com/rollup/rollup/issues/5671">#5671</a>)</li>
</ul>
<h3>Pull Requests</h3>
<ul>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5670">#5670</a>:
refactor: Use object.prototype to check for reserved properties (<a
href="https://github.com/YuHyeonWook"><code>@​YuHyeonWook</code></a>)</li>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5671">#5671</a>:
Fix DOM Clobbering CVE (<a
href="https://github.com/lukastaegert"><code>@​lukastaegert</code></a>)</li>
</ul>
<h2>v4.22.3</h2>
<h2>4.22.3</h2>
<p><em>2024-09-21</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Ensure that mutations in modules without side effects are observed
while properly handling transitive dependencies (<a
href="https://redirect.github.com/rollup/rollup/issues/5669">#5669</a>)</li>
</ul>
<h3>Pull Requests</h3>
<ul>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5669">#5669</a>:
Ensure impure dependencies of pure modules are added (<a
href="https://github.com/lukastaegert"><code>@​lukastaegert</code></a>)</li>
</ul>
<h2>v4.22.2</h2>
<h2>4.22.2</h2>
<p><em>2024-09-20</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Revert fix for side effect free modules until other issues are
investigated (<a
href="https://redirect.github.com/rollup/rollup/issues/5667">#5667</a>)</li>
</ul>
<h3>Pull Requests</h3>
<ul>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5667">#5667</a>:
Partially revert <a
href="https://redirect.github.com/rollup/rollup/issues/5658">#5658</a>
and re-apply <a
href="https://redirect.github.com/rollup/rollup/issues/5644">#5644</a>
(<a
href="https://github.com/lukastaegert"><code>@​lukastaegert</code></a>)</li>
</ul>
<h2>v4.22.1</h2>
<h2>4.22.1</h2>
<p><em>2024-09-20</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Revert <a
href="https://redirect.github.com/rollup/rollup/issues/5644">#5644</a>
&quot;stable chunk hashes&quot; while issues are being investigated</li>
</ul>
<h3>Pull Requests</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rollup/rollup/blob/master/CHANGELOG.md">rollup's
changelog</a>.</em></p>
<blockquote>
<h2>4.22.4</h2>
<p><em>2024-09-21</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Fix a vulnerability in generated code that affects IIFE, UMD and CJS
bundles when run in a browser context (<a
href="https://redirect.github.com/rollup/rollup/issues/5671">#5671</a>)</li>
</ul>
<h3>Pull Requests</h3>
<ul>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5670">#5670</a>:
refactor: Use object.prototype to check for reserved properties (<a
href="https://github.com/YuHyeonWook"><code>@​YuHyeonWook</code></a>)</li>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5671">#5671</a>:
Fix DOM Clobbering CVE (<a
href="https://github.com/lukastaegert"><code>@​lukastaegert</code></a>)</li>
</ul>
<h2>4.22.3</h2>
<p><em>2024-09-21</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Ensure that mutations in modules without side effects are observed
while properly handling transitive dependencies (<a
href="https://redirect.github.com/rollup/rollup/issues/5669">#5669</a>)</li>
</ul>
<h3>Pull Requests</h3>
<ul>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5669">#5669</a>:
Ensure impure dependencies of pure modules are added (<a
href="https://github.com/lukastaegert"><code>@​lukastaegert</code></a>)</li>
</ul>
<h2>4.22.2</h2>
<p><em>2024-09-20</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Revert fix for side effect free modules until other issues are
investigated (<a
href="https://redirect.github.com/rollup/rollup/issues/5667">#5667</a>)</li>
</ul>
<h3>Pull Requests</h3>
<ul>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5667">#5667</a>:
Partially revert <a
href="https://redirect.github.com/rollup/rollup/issues/5658">#5658</a>
and re-apply <a
href="https://redirect.github.com/rollup/rollup/issues/5644">#5644</a>
(<a
href="https://github.com/lukastaegert"><code>@​lukastaegert</code></a>)</li>
</ul>
<h2>4.22.1</h2>
<p><em>2024-09-20</em></p>
<h3>Bug Fixes</h3>
<ul>
<li>Revert <a
href="https://redirect.github.com/rollup/rollup/issues/5644">#5644</a>
&quot;stable chunk hashes&quot; while issues are being investigated</li>
</ul>
<h3>Pull Requests</h3>
<ul>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5663">#5663</a>:
chore(deps): update dependency inquirer to v11 (<a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot], <a
href="https://github.com/lukastaegert"><code>@​lukastaegert</code></a>)</li>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5664">#5664</a>:
chore(deps): lock file maintenance minor/patch updates (<a
href="https://github.com/renovate"><code>@​renovate</code></a>[bot])</li>
<li><a
href="https://redirect.github.com/rollup/rollup/pull/5665">#5665</a>:
fix: type in CI file (<a
href="https://github.com/YuHyeonWook"><code>@​YuHyeonWook</code></a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="79c0aba353"><code>79c0aba</code></a>
4.22.4</li>
<li><a
href="e2552c9e95"><code>e2552c9</code></a>
Fix DOM Clobbering CVE (<a
href="https://redirect.github.com/rollup/rollup/issues/5671">#5671</a>)</li>
<li><a
href="10ab90ea61"><code>10ab90e</code></a>
refactor: Use object.prototype to check for reserved properties (<a
href="https://redirect.github.com/rollup/rollup/issues/5670">#5670</a>)</li>
<li><a
href="e1cba8e84a"><code>e1cba8e</code></a>
4.22.3</li>
<li><a
href="59cec3e867"><code>59cec3e</code></a>
Ensure impure dependencies of pure modules are added (<a
href="https://redirect.github.com/rollup/rollup/issues/5669">#5669</a>)</li>
<li><a
href="b86ffd776c"><code>b86ffd7</code></a>
4.22.2</li>
<li><a
href="d5ff63de9e"><code>d5ff63d</code></a>
Partially revert <a
href="https://redirect.github.com/rollup/rollup/issues/5658">#5658</a>
and re-apply <a
href="https://redirect.github.com/rollup/rollup/issues/5644">#5644</a>
(<a
href="https://redirect.github.com/rollup/rollup/issues/5667">#5667</a>)</li>
<li><a
href="0a821d9318"><code>0a821d9</code></a>
Create SECURITY.md</li>
<li><a
href="76e962daca"><code>76e962d</code></a>
4.22.1</li>
<li><a
href="68c23da882"><code>68c23da</code></a>
Partially revert <a
href="https://redirect.github.com/rollup/rollup/issues/5644">#5644</a></li>
<li>Additional commits viewable in <a
href="https://github.com/rollup/rollup/compare/v4.13.2...v4.22.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rollup&package-manager=npm_and_yarn&previous-version=4.13.2&new-version=4.22.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/facebook/react/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-26 09:45:02 -04:00
Edmond Chui
f8024b0686 refactor: allow custom impl of backend realod-to-profile support check (#31048)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

In preparation to support reload-to-profile in Fusebox (#31021), we need
a way to check capability of different backends, e.g. web vs React
Native.

## How did you test this change?

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->

* Default, e.g. existing web impl = no-op
* Custom impl: is called
2024-09-26 12:39:28 +01:00
Ruslan Lesiutin
d66fa02a30 fix: use public instance in Fiber renderer and expose it from getInspectorDataForViewAtPoint (#31068)
React DevTools no longer operates with just Fibers, it now builds its
own Shadow Tree, which represents the tree on the Host (Fabric on
Native, DOM on Web).

We have to keep track of public instances for a select-to-inspect
feature. We've recently changed this logic in
https://github.com/facebook/react/pull/30831, and looks like we've been
incorrectly getting a public instance for Fabric case.

Not only this, turns out that all `getInspectorData...` APIs are
returning Fibers, and not public instances. I have to expose it, so that
React DevTools can correctly identify the element, which was selected.

Changes for React Native are in
[D63421463](https://www.internalfb.com/diff/D63421463)
2024-09-26 10:17:16 +01:00
Sebastian "Sebbie" Silbermann
778e1ed2e5 [Fiber] Fix missing render times when we cancel a pending commit (#31065) 2024-09-26 00:20:03 +02:00
Andrew Clark
0f1856c49f Make prerendering always non-blocking (#31056)
When a synchronous update suspends, and we prerender the siblings, the
prerendering should be non-blocking so that we can immediately restart
once the data arrives.

This happens automatically when there's a Suspense boundary, because we
immediately commit the boundary and then proceed to a Retry render,
which are always concurrent. When there's not a Suspense boundary, there
is no Retry, so we need to take care to switch from the synchronous work
loop to the concurrent one, to enable time slicing.
2024-09-25 16:31:44 -04:00
Andrew Clark
3c7667a694 Unify perform{Sync,Concurrent}WorkOnRoot implementation (#31029)
Over time the behavior of these two paths has converged to be
essentially the same. So this merges them back into one function. This
should save some code size and also make it harder for the behavior to
accidentally diverge. (For the same reason, rolling out this change
might expose some areas where we had already accidentally diverged.)
2024-09-25 14:33:11 -04:00
Jack Pope
f9ebd85a19 Increase nested update limit to 100 (#31061)
We're seeing the limit hit in some tests after enabling sibling
prerendering. Let's bump the limit so we can run more tests and gather
more signal on the changes. When we understand the scope of the problem
we can determine whether we need to change how the updates are counted
in prerenders and/or fix specific areas of product code.
2024-09-25 11:50:41 -04:00
Ruslan Lesiutin
d2e9b9b4dc React DevTools 5.3.1 -> 6.0.0 (#31058)
Full list of changes:
* refactor: data source for errors and warnings tracking is now in Store
([hoxyq](https://github.com/hoxyq) in
[#31010](https://github.com/facebook/react/pull/31010))
* fix: consider alternate as a key for componentLogsEntry when
inspecting raw fiber instance ([hoxyq](https://github.com/hoxyq) in
[#31009](https://github.com/facebook/react/pull/31009))
* Fix: profiling crashes #30661 #28838
([EdmondChuiHW](https://github.com/EdmondChuiHW) in
[#31024](https://github.com/facebook/react/pull/31024))
* chore: remove using local storage for persisting console settings on
the frontend ([hoxyq](https://github.com/hoxyq) in
[#31002](https://github.com/facebook/react/pull/31002))
* feat: display message if user ended up opening hook script
([hoxyq](https://github.com/hoxyq) in
[#31000](https://github.com/facebook/react/pull/31000))
* feat: expose installHook with settings argument from
react-devtools-core/backend ([hoxyq](https://github.com/hoxyq) in
[#30987](https://github.com/facebook/react/pull/30987))
* chore: remove settings manager from react-devtools-core
([hoxyq](https://github.com/hoxyq) in
[#30986](https://github.com/facebook/react/pull/30986))
* feat[react-devtools/extension]: use chrome.storage to persist settings
across sessions ([hoxyq](https://github.com/hoxyq) in
[#30636](https://github.com/facebook/react/pull/30636))
* refactor[react-devtools]: propagate settings from global hook object
to frontend ([hoxyq](https://github.com/hoxyq) in
[#30610](https://github.com/facebook/react/pull/30610))
* chore[react-devtools]: extract some utils into separate modules to
unify implementations ([hoxyq](https://github.com/hoxyq) in
[#30597](https://github.com/facebook/react/pull/30597))
* refactor[react-devtools]: move console patching to global hook
([hoxyq](https://github.com/hoxyq) in
[#30596](https://github.com/facebook/react/pull/30596))
* refactor[react-devtools]: remove browserTheme from
ConsolePatchSettings ([hoxyq](https://github.com/hoxyq) in
[#30566](https://github.com/facebook/react/pull/30566))
* feat[react-devtools]: add settings to global hook object
([hoxyq](https://github.com/hoxyq) in
[#30564](https://github.com/facebook/react/pull/30564))
* fix: add Error prefix to Error objects names
([hoxyq](https://github.com/hoxyq) in
[#30969](https://github.com/facebook/react/pull/30969))
* Add enableComponentPerformanceTrack Flag
([sebmarkbage](https://github.com/sebmarkbage) in
[#30960](https://github.com/facebook/react/pull/30960))
* fix[rdt/fiber/renderer.js]: getCurrentFiber can be injected as null
([hoxyq](https://github.com/hoxyq) in
[#30968](https://github.com/facebook/react/pull/30968))
* disable `enableSiblingPrerendering` in experimental channel
([gnoff](https://github.com/gnoff) in
[#30952](https://github.com/facebook/react/pull/30952))
* refactor[react-devtools]: initialize renderer interface early
([hoxyq](https://github.com/hoxyq) in
[#30946](https://github.com/facebook/react/pull/30946))
* Start prerendering Suspense retries immediately
([acdlite](https://github.com/acdlite) in
[#30934](https://github.com/facebook/react/pull/30934))
* refactor[Agent/Store]: Store to send messages only after Agent is
initialized ([hoxyq](https://github.com/hoxyq) in
[#30945](https://github.com/facebook/react/pull/30945))
* refactor[RendererInterface]: expose onErrorOrWarning and
getComponentStack ([hoxyq](https://github.com/hoxyq) in
[#30931](https://github.com/facebook/react/pull/30931))
* Implement getComponentStack and onErrorOrWarning for replayed Flight
logs ([sebmarkbage](https://github.com/sebmarkbage) in
[#30930](https://github.com/facebook/react/pull/30930))
* Use Unicode Atom Symbol instead of Atom Emoji
([sebmarkbage](https://github.com/sebmarkbage) in
[#30832](https://github.com/facebook/react/pull/30832))
* Improve Layering Between Console and Renderer
([sebmarkbage](https://github.com/sebmarkbage) in
[#30925](https://github.com/facebook/react/pull/30925))
* Add Map for Server Component Logs
([sebmarkbage](https://github.com/sebmarkbage) in
[#30905](https://github.com/facebook/react/pull/30905))
* Delete fiberToFiberInstanceMap
([sebmarkbage](https://github.com/sebmarkbage) in
[#30900](https://github.com/facebook/react/pull/30900))
* Add Flight Renderer ([sebmarkbage](https://github.com/sebmarkbage) in
[#30906](https://github.com/facebook/react/pull/30906))
* Refactor Error / Warning Count Tracking
([sebmarkbage](https://github.com/sebmarkbage) in
[#30899](https://github.com/facebook/react/pull/30899))
* [flow] Upgrade Flow to 0.245.2
([SamChou19815](https://github.com/SamChou19815) in
[#30919](https://github.com/facebook/react/pull/30919))
* Separate RDT Fusebox into single-panel entry points
([huntie](https://github.com/huntie) in
[#30708](https://github.com/facebook/react/pull/30708))
* Build Updater List from the Commit instead of Map
([sebmarkbage](https://github.com/sebmarkbage) in
[#30897](https://github.com/facebook/react/pull/30897))
* Simplify Context Change Tracking in Profiler
([sebmarkbage](https://github.com/sebmarkbage) in
[#30896](https://github.com/facebook/react/pull/30896))
* Remove use of .alternate in root and recordProfilingDurations
([sebmarkbage](https://github.com/sebmarkbage) in
[#30895](https://github.com/facebook/react/pull/30895))
* Handle reordered contexts in Profiler
([sebmarkbage](https://github.com/sebmarkbage) in
[#30887](https://github.com/facebook/react/pull/30887))
* Refactor Forcing Fallback / Error of Suspense / Error Boundaries
([sebmarkbage](https://github.com/sebmarkbage) in
[#30870](https://github.com/facebook/react/pull/30870))
* Avoid getFiberIDUnsafe in debug() Helper
([sebmarkbage](https://github.com/sebmarkbage) in
[#30878](https://github.com/facebook/react/pull/30878))
* Include some Filtered Fiber Instances
([sebmarkbage](https://github.com/sebmarkbage) in
[#30865](https://github.com/facebook/react/pull/30865))
* Track root instances in a root Map
([sebmarkbage](https://github.com/sebmarkbage) in
[#30875](https://github.com/facebook/react/pull/30875))
* Track all public HostInstances in a Map
([sebmarkbage](https://github.com/sebmarkbage) in
[#30831](https://github.com/facebook/react/pull/30831))
* Support VirtualInstances in findAllCurrentHostInstances
([sebmarkbage](https://github.com/sebmarkbage) in
[#30853](https://github.com/facebook/react/pull/30853))
* Add Filtering of Environment Names
([sebmarkbage](https://github.com/sebmarkbage) in
[#30850](https://github.com/facebook/react/pull/30850))
* Support secondary environment name when it changes
([sebmarkbage](https://github.com/sebmarkbage) in
[#30842](https://github.com/facebook/react/pull/30842))
* Increase max payload for websocket in standalone app
([runeb](https://github.com/runeb) in
[#30848](https://github.com/facebook/react/pull/30848))
* Filter Server Components
([sebmarkbage](https://github.com/sebmarkbage) in
[#30839](https://github.com/facebook/react/pull/30839))
* Track virtual instances on the tracked path for selections
([sebmarkbage](https://github.com/sebmarkbage) in
[#30802](https://github.com/facebook/react/pull/30802))
* Remove displayName from inspected data
([sebmarkbage](https://github.com/sebmarkbage) in
[#30841](https://github.com/facebook/react/pull/30841))
* chore[react-devtools/hook]: remove unused native values
([hoxyq](https://github.com/hoxyq) in
[#30827](https://github.com/facebook/react/pull/30827))
* chore[react-devtools/extensions]: remove unused storage permission
([hoxyq](https://github.com/hoxyq) in
[#30826](https://github.com/facebook/react/pull/30826))
* fix[react-devtools/extensions]: fixed tabs API calls and displaying
restricted access popup ([hoxyq](https://github.com/hoxyq) in
[#30825](https://github.com/facebook/react/pull/30825))
* feat[react-devtools]: support Manifest v3 for Firefox extension
([hoxyq](https://github.com/hoxyq) in
[#30824](https://github.com/facebook/react/pull/30824))
* Reconcile Fibers Against Previous Children Instances
([sebmarkbage](https://github.com/sebmarkbage) in
[#30822](https://github.com/facebook/react/pull/30822))
* Remove findCurrentFiberUsingSlowPathByFiberInstance
([sebmarkbage](https://github.com/sebmarkbage) in
[#30818](https://github.com/facebook/react/pull/30818))
* Track Tree Base Duration of Virtual Instances
([sebmarkbage](https://github.com/sebmarkbage) in
[#30817](https://github.com/facebook/react/pull/30817))
* Use Owner Stacks to Implement View Source of a Server Component
([sebmarkbage](https://github.com/sebmarkbage) in
[#30798](https://github.com/facebook/react/pull/30798))
* Make function inspection instant
([sebmarkbage](https://github.com/sebmarkbage) in
[#30786](https://github.com/facebook/react/pull/30786))
* Make Functions Clickable to Jump to Definition
([sebmarkbage](https://github.com/sebmarkbage) in
[#30769](https://github.com/facebook/react/pull/30769))
* Support REACT_LEGACY_ELEMENT_TYPE for formatting JSX
([sebmarkbage](https://github.com/sebmarkbage) in
[#30779](https://github.com/facebook/react/pull/30779))
* Find owners from the parent path that matches the Fiber or
ReactComponentInfo ([sebmarkbage](https://github.com/sebmarkbage) in
[#30717](https://github.com/facebook/react/pull/30717))
* [Flight/DevTools] Pass the Server Component's "key" as Part of the
ReactComponentInfo ([sebmarkbage](https://github.com/sebmarkbage) in
[#30703](https://github.com/facebook/react/pull/30703))
* Hide props section if it is null
([sebmarkbage](https://github.com/sebmarkbage) in
[#30696](https://github.com/facebook/react/pull/30696))
* Support Server Components in Tree
([sebmarkbage](https://github.com/sebmarkbage) in
[#30684](https://github.com/facebook/react/pull/30684))
* fix[react-devtools/InspectedElement]: fixed border stylings when some
of the panels are not rendered ([hoxyq](https://github.com/hoxyq) in
[#30676](https://github.com/facebook/react/pull/30676))
* Compute new reordered child set from the instance tree
([sebmarkbage](https://github.com/sebmarkbage) in
[#30668](https://github.com/facebook/react/pull/30668))
* Unmount instance by walking the instance tree instead of the fiber
tree ([sebmarkbage](https://github.com/sebmarkbage) in
[#30665](https://github.com/facebook/react/pull/30665))
* Further Refactoring of Unmounts
([sebmarkbage](https://github.com/sebmarkbage) in
[#30658](https://github.com/facebook/react/pull/30658))
* Remove lodash.throttle ([sebmarkbage](https://github.com/sebmarkbage)
in [#30657](https://github.com/facebook/react/pull/30657))
* Unmount by walking previous nodes no longer in the new tree
([sebmarkbage](https://github.com/sebmarkbage) in
[#30644](https://github.com/facebook/react/pull/30644))
* Build up DevTools Instance Shadow Tree
([sebmarkbage](https://github.com/sebmarkbage) in
[#30625](https://github.com/facebook/react/pull/30625))
* chore[packages/react-devtools]: remove unused index.js
([hoxyq](https://github.com/hoxyq) in
[#30579](https://github.com/facebook/react/pull/30579))
* Track DOM nodes to Fiber map for HostHoistable Resources
([sebmarkbage](https://github.com/sebmarkbage) in
[#30590](https://github.com/facebook/react/pull/30590))
* Rename mountFiberRecursively/updateFiberRecursively
([sebmarkbage](https://github.com/sebmarkbage) in
[#30586](https://github.com/facebook/react/pull/30586))
* Allow Highlighting/Inspect HostSingletons/Hoistables and Resources
([sebmarkbage](https://github.com/sebmarkbage) in
[#30584](https://github.com/facebook/react/pull/30584))
* chore[react-devtools]: add global for native and use it to fork
backend implementation ([hoxyq](https://github.com/hoxyq) in
[#30533](https://github.com/facebook/react/pull/30533))
* Enable pointEvents while scrolling
([sebmarkbage](https://github.com/sebmarkbage) in
[#30560](https://github.com/facebook/react/pull/30560))
* Make Element Inspection Feel Snappy
([sebmarkbage](https://github.com/sebmarkbage) in
[#30555](https://github.com/facebook/react/pull/30555))
* Track the parent DevToolsInstance while mounting a tree
([sebmarkbage](https://github.com/sebmarkbage) in
[#30542](https://github.com/facebook/react/pull/30542))
* Add DevToolsInstance to Store Stateful Information
([sebmarkbage](https://github.com/sebmarkbage) in
[#30517](https://github.com/facebook/react/pull/30517))
* Implement "best renderer" by taking the inner most matched node
([sebmarkbage](https://github.com/sebmarkbage) in
[#30494](https://github.com/facebook/react/pull/30494))
* Rename NativeElement to HostInstance in the Bridge
([sebmarkbage](https://github.com/sebmarkbage) in
[#30491](https://github.com/facebook/react/pull/30491))
* Rename Fiber to Element in the Bridge Protocol and RendererInterface
([sebmarkbage](https://github.com/sebmarkbage) in
[#30490](https://github.com/facebook/react/pull/30490))
* Stop filtering owner stacks
([sebmarkbage](https://github.com/sebmarkbage) in
[#30438](https://github.com/facebook/react/pull/30438))
* [Fiber] Call life-cycles with a react-stack-bottom-frame stack frame
([sebmarkbage](https://github.com/sebmarkbage) in
[#30429](https://github.com/facebook/react/pull/30429))
* [Flight] Prefix owner stacks added to the console.log with the current
stack ([sebmarkbage](https://github.com/sebmarkbage) in
[#30427](https://github.com/facebook/react/pull/30427))
* [BE] switch to hermes parser for prettier
([kassens](https://github.com/kassens) in
[#30421](https://github.com/facebook/react/pull/30421))
* Implement Owner Stacks ([sebmarkbage](https://github.com/sebmarkbage)
in [#30417](https://github.com/facebook/react/pull/30417))
* [BE] upgrade prettier to 3.3.3 ([kassens](https://github.com/kassens)
in [#30420](https://github.com/facebook/react/pull/30420))
* [ci] Add yarn_test_build job to gh actions
* [Fizz] Refactor Component Stack Nodes
([sebmarkbage](https://github.com/sebmarkbage) in
[#30298](https://github.com/facebook/react/pull/30298))
* Print component stacks as error objects to get source mapping
([sebmarkbage](https://github.com/sebmarkbage) in
[#30289](https://github.com/facebook/react/pull/30289))
* Upgrade flow to 0.235.0 ([kassens](https://github.com/kassens) in
[#30118](https://github.com/facebook/react/pull/30118))
* fix: path handling in react devtools
([Jack-Works](https://github.com/Jack-Works) in
[#29199](https://github.com/facebook/react/pull/29199))
2024-09-25 14:38:34 +01:00
Ruslan Lesiutin
a15bbe1475 refactor: data source for errors and warnings tracking is now in Store (#31010)
Stacked on https://github.com/facebook/react/pull/31009.

1. Instead of keeping `showInlineWarningsAndErrors` in `Settings`
context (which was removed in
https://github.com/facebook/react/pull/30610), `Store` will now have a
boolean flag, which controls if the UI should be displaying information
about errors and warnings.
2. The errors and warnings counters in the Tree view are now counting
only unique errors. This makes more sense, because it is part of the
Elements Tree view, so ideally it should be showing number of components
with errors and number of components of warnings. Consider this example:
2.1. Warning for element `A` was emitted once and warning for element
`B` was emitted twice.
2.2. With previous implementation, we would show `3 ⚠️`, because in
total there were 3 warnings in total. If user tries to iterate through
these, it will only take 2 steps to do the full cycle, because there are
only 2 elements with warnings (with one having same warning, which was
emitted twice).
2.3 With current implementation, we would show `2 ⚠️`. Inspecting the
element with doubled warning will still show the warning counter (2)
before the warning message.

With these changes, the feature correctly works.
https://fburl.com/a7fw92m4
2024-09-24 19:51:21 +01:00
Ruslan Lesiutin
fc4a33eaa9 fix: consider alternate as a key for componentLogsEntry when inspecting raw fiber instance (#31009)
Related - https://github.com/facebook/react/pull/30899.

Looks like this was missed. We actually do this when we record errors
and warnings before sending them via Bridge:

e4953922a9/packages/react-devtools-shared/src/backend/fiber/renderer.js (L2169-L2173)

So, what is happening in the end, errors or warnings are displayed in
the Tree, but when user clicks on the component, nothing is shown,
because `fiberToComponentLogsMap` has only `alternate` as a key.
2024-09-24 17:49:19 +01:00
Hendrik Liebau
04bd67a490 Resolve references to deduped owner objects (#30549)
This is a follow-up from #30528 to not only handle props (the critical
change), but also the owner ~and stack~ of a referenced element.

~Handling stacks here is rather academic because the Flight Server
currently does not deduplicate owner stacks. And if they are really
identical, we should probably just dedupe the whole element.~ EDIT:
Removed from the PR.

Handling owner objects on the other hand is an actual requirement as
reported in https://github.com/vercel/next.js/issues/69545. This problem
only affects the stable release channel, as the absence of owner stacks
allows for the specific kind of shared owner deduping as demonstrated in
the unit test.
2024-09-24 02:34:53 -04:00
Lauren Tan
4708fb92c2 Fix runtime_commit_artifacts workflow
I messed up the yml syntax and also realized that our script doesn't
currently handle renames or deletes, so I fixed that

ghstack-source-id: 7d481a951a
Pull Request resolved: https://github.com/facebook/react/pull/31028
2024-09-23 17:39:53 -04:00
Lauren Tan
5b19dc0f06 Allow forcing a build in artifacts workflow dispatch
Sometimes it is useful to bypass the revision check when we need to make
changes to the runtime_commit_artifacts script. The `force` input can be
passed via the GitHub UI for manual runs of the workflow.

ghstack-source-id: cf9e32c01a
Pull Request resolved: https://github.com/facebook/react/pull/31027
2024-09-23 15:38:05 -04:00
Lauren Tan
79bcf6eb23 Fix missing trailing / in commit artifacts workflow
The trailing / was being omitted, so instead of moving the cjs
directory itself, it would move only its contents instead. This broke
some internal path assumptions.

Additionally, updates the step to create the react-dom directory prior
to moving.

ghstack-source-id: b6eedb0c88cd3aa3a786a3d3d280ede5ee81a063
Pull Request resolved: https://github.com/facebook/react/pull/31026
2024-09-23 14:58:19 -04:00
Sebastian Markbåge
4e9540e3c2 [Fiber] Log the Render/Commit phases and the gaps in between (#31016)
A slight behavior change here too is that I now mark the start of the
commit phase before the BeforeMutationEffect phase. This affects
`<Profiler>` too.

The named sequences are as follows:

Render -> Suspended or Throttled -> Commit -> Waiting for Paint ->
Remaining Effects

The Suspended phase is only logged if we delay the Commit due to CSS /
images.

The Throttled phase is only logged if we delay the commit due to the
Suspense throttling timer.

<img width="1246" alt="Screenshot 2024-09-20 at 9 14 23 PM"
src="https://github.com/user-attachments/assets/8d01f444-bb85-472b-9b42-6157d92c81b4">

I don't yet log render phases that don't complete. I think I also need
to special case renders that or don't commit after being suspended.
2024-09-23 14:09:48 -04:00
Edmond Chui
5d19e1c8d1 Fix: profiling crashes #30661 #28838 (#31024)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

Profiling fails sometimes because `onProfilingStatus` is called
repeatedly on some occasions, e.g. multiple calls to
`getProfilingStatus`.

Subsequent calls should be a no-op if the profiling status hasn't
changed.

Reported via #30661 #28838.

> [!TIP]
> Hide whitespace changes on this PR

<img width="328" alt="screenshot showing the UI controls for hiding
whitespace changes on GitHub"
src="https://github.com/user-attachments/assets/036385cf-2610-4e69-a717-17c05d7ef047">


## How did you test this change?

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->

Tested as part of Fusebox implementation of reload-to-profile.

https://github.com/facebook/react/pull/31021?#discussion_r1770589753
2024-09-23 13:25:44 +01:00
Sebastian Markbåge
d4688dfaaf [Fiber] Track Event Time, startTransition Time and setState Time (#31008)
This tracks the current window.event.timeStamp the first time we
setState or call startTransition. For either the blocking track or
transition track. We can use this to show how long we were blocked by
other events or overhead from when the user interacted until we got
called into React.

Then we track the time we start awaiting a Promise returned from
startTransition. We can use this track how long we waited on an Action
to complete before setState was called.

Then finally we track when setState was called so we can track how long
we were blocked by other word before we could actually start rendering.
For a Transition this might be blocked by Blocking React render work.

We only log these once a subsequent render actually happened. If no
render was actually scheduled, then we don't log these. E.g. if an
isomorphic Action doesn't call startTransition there's no render so we
don't log it.

We only log the first event/update/transition even if multiple are
batched into it later. If multiple Actions are entangled they're all
treated as one until an update happens. If no update happens and all
entangled actions finish, we clear the transition so that the next time
a new sequence starts we can log it.

We also clamp these (start the track later) if they were scheduled
within a render/commit. Since we share a single track we don't want to
create overlapping tracks.

The purpose of this is not to show every event/action that happens but
to show a prelude to how long we were blocked before a render started.
So you can follow the first event to commit.

<img width="674" alt="Screenshot 2024-09-20 at 1 59 58 AM"
src="https://github.com/user-attachments/assets/151ba9e8-6b3c-4fa1-9f8d-e3602745eeb7">

I still need to add the rendering/suspended phases to the timeline which
why this screenshot has a gap.

<img width="993" alt="Screenshot 2024-09-20 at 12 50 27 AM"
src="https://github.com/user-attachments/assets/155b6675-b78a-4a22-a32b-212c15051074">

In this case it's a Form Action which started a render into the form
which then suspended on the action. The action then caused a refresh,
which interrupts with its own update that's blocked before rendering.
Suspended roots like this is interesting because we could in theory
start working on a different root in the meantime which makes this
timeline less linear.
2024-09-20 14:27:12 -04:00
Josh Story
ae75d5a3f5 [Fizz] Include componentStack at the root when aborting (#31011)
When aborting we currently don't produce a componentStack when aborting
the shell. This is likely just an oversight and this change updates this
behavior to be consistent with what we do when there is a boundary
2024-09-20 10:00:02 -07:00
Jack Pope
e4953922a9 Update react-native/react-dom build directory (#31006)
Commit artifact actions are breaking after
https://github.com/facebook/react/pull/30711

See:
https://github.com/facebook/react/actions/runs/10930658977/job/30344033974

> mv: cannot stat 'build/facebook-react-native/react/dom/': No such file
or directory

After build, the new artifacts are in `/react-dom/cjs`, not
`/react/dom/`
```
$> yarn build
$> ls build/facebook-react-native/react/
# ... no dom
$> ls build/facebook-react-native/react-dom/cjs
```
2024-09-19 18:04:06 -04:00
Ricky
babde5d182 [lint] Add no-optional-chaining (#31003)
## Overview

Adds a lint rule to prevent optional chaining to catch issues like
https://github.com/facebook/react/pull/30982 until we support optional
chaining without a bundle impact.
2024-09-19 13:42:49 -04:00
Ruslan Lesiutin
e740d4b14b chore: remove using local storage for persisting console settings on the frontend (#31002)
After https://github.com/facebook/react/pull/30636 and
https://github.com/facebook/react/pull/30986 we no longer store settings
on the Frontend side via `localStorage`.

This PR removes all occurrences of it from
`react-devtools-core/standalone` and `react-devtools-inline`.
2024-09-19 15:47:25 +01:00
Ruslan Lesiutin
c21ce4a396 feat: display message if user ended up opening hook script (#31000)
In https://github.com/facebook/react/pull/30596 we've moved console
patching to the global hook. Generally speaking, the patching happens
even before React is loaded on the page.

If browser DevTools were opened after when `console.error` or
`console.warn` were called, the source script will be `hook.js`, because
of the patching.

![devtools-opened-after-the-message](https://github.com/user-attachments/assets/3d3dbc16-96b8-4234-b061-57b21b60cf2e)

This is because ignore listing is not applied retroactively by Chrome
DevTools.
If you had it open before console calls, Hook script would be correctly
filtered out from the stack:

![devtools-opened-before-the-message](https://github.com/user-attachments/assets/3e99cb22-97b0-4b49-9a76-f7bc948e6452)

I had hopes that the fix for
https://issues.chromium.org/issues/345248263 will also apply ignore
listing retroactively, but looks like we need to open a separate feature
request for the Chrome DevTools team.

With these changes, if user attempts to open `hook.js` script, they are
going to see this message:
![Screenshot 2024-09-19 at 11 30
59](https://github.com/user-attachments/assets/5850b74c-329f-4fbe-a3dd-33f9ac717ee9)
2024-09-19 15:44:34 +01:00
Jack Pope
632f88df11 [compiler] Allow ReactElement symbol to be configured when inlining jsx (#30996)
Based on https://github.com/facebook/react/pull/30995 ([rendered
diff](https://github.com/jackpope/react/compare/inline-jsx-2...jackpope:react:inline-jsx-3?expand=1))

____

Some apps still use `react.element` symbols. Not only do we want to test
there but we also want to be able to upgrade those sites to
`react.transitional.element` without blocking on the compiler (we can
change the symbol feature flag and compiler config at the same time).

The compiler runtime uses `react.transitional.element`, so the snap
fixture will fail if we change the default here. However I confirmed
that commenting out the fixture entrypoint and running snap with
`react.element` will update the fixture symbols as expected.
2024-09-19 10:34:24 -04:00
Jack Pope
d5e955d3c0 [compiler] Pass through unmodified props spread when inlining jsx (#30995)
If JSX receives a props spread without additional attributes (besides
`ref` and `key`), we can pass the spread object as a property directly
to avoid the extra object copy.

```
<Test {...propsToSpread} />
// {props: propsToSpread}
<Test {...propsToSpread} a="z" />
// {props: {...propsToSpread, a: "z"}}
```
2024-09-19 10:07:29 -04:00
Ruslan Lesiutin
a86afe8e56 feat: expose installHook with settings argument from react-devtools-core/backend (#30987)
Stacked on https://github.com/facebook/react/pull/30986. 

Previously, we would call `installHook` at a top level of the JavaScript
module. Because of this, having `require` statement for
`react-devtools-core` package was enough to initialize the React
DevTools global hook on the `window`.

Now, the Hook can actually receive an argument - initial user settings
for console patching. We expose this as a function `initialize`, which
can be used by third parties (including React Native) to provide the
persisted settings.

The README was also updated to reflect the changes.
2024-09-19 13:55:08 +01:00
Timothy Yung
e72127a4ec Build react-dom in builds/facebook-fbsource (#30711)
## Summary

Builds `react-dom` for React Native so that it also populates the
`builds/facebook-fbsource` branch.

**NOTE:** For Meta employees, D61354219 is the internal integration.

## How did you test this change?

```
$ yarn build
…
$ ls build/facebook-react-native/react-dom/cjs
ReactDOM-dev.js       ReactDOM-prod.js      ReactDOM-profiling.js
```
2024-09-18 14:44:55 -07:00
mofeiZ
09d8283599 [ez] Rewrite optional chaining and nullish coalescing syntax (#30982)
Rewrite `containerInfo?.ownerDocument?.defaultView ?? window` to instead
use a ternary.

This changes the compilation output (see [bundle changes from
#30951](d65fb06955)).
```js
// compilation of containerInfo?.ownerDocument?.defaultView ?? window
var $jscomp$optchain$tmpm1756096108$1, $jscomp$nullish$tmp0;
containerInfo =
  null !=
  ($jscomp$nullish$tmp0 =
    null == containerInfo
      ? void 0
      : null ==
          ($jscomp$optchain$tmpm1756096108$1 = containerInfo.ownerDocument)
        ? void 0
        : $jscomp$optchain$tmpm1756096108$1.defaultView)
    ? $jscomp$nullish$tmp0
    : window;

// compilation of ternary expression
containerInfo =
  null != containerInfo &&
  null != containerInfo.ownerDocument &&
  null != containerInfo.ownerDocument.defaultView
    ? containerInfo.ownerDocument.defaultView
    : window;
```

This also reduces the number of no-op bundle syncs for Meta. Note that
Closure compiler's `jscomp$optchain$tmp<HASH>` identifiers change when
we rebuild (likely due to version number changes). See
[workflow](https://github.com/facebook/react/actions/runs/10891164281/job/30221518374)
for a PR that was synced despite making no changes to the runtime.
2024-09-18 14:39:04 -04:00
Ruslan Lesiutin
f2c57a31e9 chore: remove settings manager from react-devtools-core (#30986)
Stacked on https://github.com/facebook/react/pull/30636. See [this
commit](20cec76c44).

This has been only used for React Native and will be replaced by another
approach (initialization via `installHook` call) in the next PR.
2024-09-18 18:30:32 +01:00
Ruslan Lesiutin
f37c7bc653 feat[react-devtools/extension]: use chrome.storage to persist settings across sessions (#30636)
Stacked on https://github.com/facebook/react/pull/30610 and whats under
it. See [last
commit](248ddba186).

Now, we are using
[`chrome.storage`](https://developer.chrome.com/docs/extensions/reference/api/storage)
to persist settings for the browser extension across different sessions.
Once settings are updated from the UI, the `Store` will emit
`settingsUpdated` event, and we are going to persist them via
`chrome.storage.local.set` in `main/index.js`.

When hook is being injected, we are going to pass a `Promise`, which is
going to be resolved after the settings are read from the storage via
`chrome.storage.local.get` in `hookSettingsInjector.js`.
2024-09-18 18:26:39 +01:00
Ruslan Lesiutin
e33acfd67f refactor[react-devtools]: propagate settings from global hook object to frontend (#30610)
Stacked on https://github.com/facebook/react/pull/30597 and whats under
it. See [this
commit](59b4efa723).

With this change, the initial values for console patching settings are
propagated from hook (which is the source of truth now, because of
https://github.com/facebook/react/pull/30596) to the UI. Instead of
reading from `localStorage` the frontend is now requesting it from the
hook. This happens when settings modal is rendered, and wrapped in a
transition. Also, this is happening even if settings modal is not opened
yet, so we have enough time to fetch this data without displaying loader
or similar UI.
2024-09-18 18:19:01 +01:00
Ruslan Lesiutin
fce4606657 chore[react-devtools]: extract some utils into separate modules to unify implementations (#30597)
Stacked on https://github.com/facebook/react/pull/30596. See [this
commit](4ba5e784bb).

Moving `formatWithStyles` and `formatConsoleArguments` to its own
modules, so that we can finally have a single implementation for these
and stop inlining them in RDT global hook object.
2024-09-18 18:16:20 +01:00
Ruslan Lesiutin
3cac0875dc refactor[react-devtools]: move console patching to global hook (#30596)
Stacked on https://github.com/facebook/react/pull/30566 and whats under
it. See [this
commit](374fd737e4).

It is mostly copying code from one place to another and updating tests.
With these changes, for every console method that we patch, there is
going to be a single applied patch:
- For `error`, `warn`, and `trace` we are patching when hook is
installed. This guarantees that component stacks are going to be
appended even if browser DevTools are not opened. We pay some price for
it, though: if user has browser DevTools closed and if at this point
some warning or error is emitted (logged), the next time user opens
browser DevTools, they are going to see `hook.js` as the source frame.
Unfortunately, ignore listing from source maps is not applied
retroactively, and I don't know if its a bug or just a design
limitations. Once browser DevTools are opened, source maps will be
loaded and ignore listing will be applied for all emitted logs in the
future.
- For `log`, `info`, `group`, `groupCollapsed` we are only patching when
React notifies React DevTools about running in StrictMode. We unpatch
the methods right after it.
2024-09-18 18:12:18 +01:00
Ruslan Lesiutin
b521ef8a2a refactor[react-devtools]: remove browserTheme from ConsolePatchSettings (#30566)
Stacked on https://github.com/facebook/react/pull/30564.

We are no longer using browser theme in our console patching, this was
removed in unification of console patching for strict mode, we started
using ansi escape symbols and forking based on browser theme is no
longer required - https://github.com/facebook/react/pull/29869

The real browser theme initialization for frontend is happening at the
other place and is not affected:

40be968257/packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js (L117-L120)
2024-09-18 18:02:13 +01:00
Ruslan Lesiutin
5e83d9ab3b feat[react-devtools]: add settings to global hook object (#30564)
Right now we are patching console 2 times: when hook is installed
(before page is loaded) and when backend is connected. Because of this,
even if user had `appendComponentStack` setting enabled, all emitted
error and warning logs are not going to have component stacks appended.
They also won't have component stacks appended retroactively when user
opens browser DevTools (this is when frontend is initialized and
connects to backend).

This behavior adds potential race conditions with LogBox in React
Native, and also unpredictable to the user, because in order to get
component stacks logged you have to open browser DevTools, but by the
time you do it, error or warning log was already emitted.

To solve this, we are going to only patch console in the hook object,
because it is guaranteed to load even before React. Settings are going
to be synchronized with the hook via Bridge, and React DevTools Backend
Host (React Native or browser extension shell) will be responsible for
persisting these settings across the session, this is going to be
implemented in a separate PR.
2024-09-18 17:37:00 +01:00
Jack Pope
5dcb009760 [compiler] Add JSX inlining optimization (#30867)
This adds an `InlineJsxTransform` optimization pass, toggled by the
`enableInlineJsxTransform` flag. When enabled, JSX will be transformed
into React Element object literals, preventing runtime overhead during
element creation.

TODO:
- [ ] Add conditionals to make transform PROD-only
- [ ] Make the React element symbol configurable so this works with
runtimes that support `react.element` or `react.transitional.element`
- [ ] Look into additional optimization to pass props spread through
directly if none of the properties are mutated
2024-09-18 11:51:36 -04:00
Sebastian Markbåge
8dfbd16fce [Fiber] Color Performance Track Entries by Self Time (#30984)
Stacked on #30983.

This colors each component entry by its self time from light to dark
depending on how long it took. If it took longer than a cut off we color
it red (the error color).

<img width="435" alt="Screenshot 2024-09-16 at 11 48 15 PM"
src="https://github.com/user-attachments/assets/5d0bda83-6205-40e9-bec1-b81db2d48b2d">
2024-09-17 16:36:10 -04:00
Sebastian Markbåge
e1c20902c3 [Fiber] Log Component Effects to Performance Track (#30983)
Stacked on #30981. Same as #30967 but for effects.

This logs a tree of components using `performance.measure()`.

In addition to the previous render phase this logs one tree for each
commit phase:

- Mutation Phase
- Layout Effect
- Passive Unmounts
- Passive Mounts

I currently skip the Before Mutation phase since the snapshots are so
unusual it's not worth creating trees for those.

The mechanism is that I reuse the timings we track for
`enableProfilerCommitHooks`. I track first and last effect timestamp
within each component subtree. Then on the way up do we log the entry.
This means that we don't include overhead to find our way down to a
component and that we don't need to add any additional overhead by
reading timestamps.

To ensure that the entries get ordered correctly we need to ensure that
the start time of each parent is slightly before the inner one.
2024-09-17 16:14:57 -04:00
Sebastian Markbåge
15da917451 Don't read currentTransition back from internals (#30991)
This code is weird. It reads back the transition that it just set from
the shared internals. It's almost like it expects it to be a getter or
something.

This avoids that and makes it consistent with what ReactFiberHooks
already does.
2024-09-17 15:25:00 -04:00
Sebastian Markbåge
4549be0f84 [Fiber] Optimize enableProfilerCommitHooks by Collecting Elapsed Effect Duration in Module Scope (#30981)
Stacked on #30979.

The problem with the previous approach is that it recursively walked the
tree up to propagate the resulting time from recording a layout effect.

Instead, we keep a running count of the effect duration on the module
scope. Then we reset it when entering a nested Profiler and then we add
its elapsed count when we exit the Profiler.

This also fixes a bug where we weren't previously including unmount
times for some detached trees since they couldn't bubble up to find the
profiler.
2024-09-17 15:12:16 -04:00
Mike Vitousek
7b56a54298 [compiler][playground] create playground API in pipeline, and allow spaces in pass names
Summary:
1. Minor refactor to provide a stable API for calling the compiler from the playground
2. Allows spaces in pass names without breaking the appearance of the playground by replacing spaces with &nbsp; in pass tabs

ghstack-source-id: 12a43ad86c16c0e21f3e6b4086d531cdefd893eb
Pull Request resolved: https://github.com/facebook/react/pull/30988
2024-09-17 11:05:59 -07:00
mofeiZ
a99d8e8d97 [compiler][eslint] Report bailout diagnostics with correct column # (#30977)
Compiler bailout diagnostics should now highlight only the first line of
the source location span.

(Resubmission of #30423 which was reverted due to invalid column
number.)
2024-09-16 15:56:24 -04:00
Sebastian Markbåge
8152e5cd27 Remove execution context check from shouldProfile (#30971)
I don't know why this is here since all these callsites are within the
CommitWork/CommitEffects helpers.

This should help with inlining.
2024-09-16 15:00:17 -04:00
798 changed files with 31566 additions and 14500 deletions

View File

@@ -8,11 +8,9 @@ indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 80
trim_trailing_whitespace = true
[*.md]
max_line_length = 0
trim_trailing_whitespace = false
[COMMIT_EDITMSG]
max_line_length = 0

View File

@@ -23,6 +23,7 @@ module.exports = {
'babel',
'ft-flow',
'jest',
'es',
'no-for-of-loops',
'no-function-declare-after-return',
'react',
@@ -47,7 +48,7 @@ module.exports = {
'ft-flow/no-unused-expressions': ERROR,
// 'ft-flow/no-weak-types': WARNING,
// 'ft-flow/require-valid-file-annotation': ERROR,
'es/no-optional-chaining': ERROR,
'no-cond-assign': OFF,
'no-constant-condition': OFF,
'no-control-regex': OFF,
@@ -435,6 +436,7 @@ module.exports = {
'packages/react-dom/src/test-utils/*.js',
],
rules: {
'es/no-optional-chaining': OFF,
'react-internal/no-production-logging': OFF,
'react-internal/warning-args': OFF,
'react-internal/safe-string-coercion': [
@@ -567,6 +569,7 @@ module.exports = {
React$Node: 'readonly',
React$Portal: 'readonly',
React$Ref: 'readonly',
React$RefSetter: 'readonly',
ReadableStreamController: 'readonly',
ReadableStreamReader: 'readonly',
RequestInfo: 'readonly',

View File

@@ -55,3 +55,10 @@ body:
Please provide your React version in the app where this issue occurred.
validations:
required: true
- type: input
attributes:
label: What version of React Compiler are you using?
description: |
Please provide the exact React Compiler version in the app where this issue occurred.
validations:
required: true

107
.github/dependabot.yml vendored
View File

@@ -1,107 +1,10 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/fixtures/art"
directories:
- "/fixtures/*"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/attribute-behavior"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/concurrent"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/devtools"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/dom"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/eslint"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/expiration"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/fiber-debugger"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/fiber-triangle"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/fizz"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/fizz-ssr-browser"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/flight"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/flight-browser"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/flight-esm"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/legacy-jsx-runtimes"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/nesting"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/packaging"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/scheduler"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/ssr"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/ssr-2"
schedule:
interval: "weekly"
open-pull-requests-limit: 0
- package-ecosystem: "npm"
directory: "/fixtures/stacks"
schedule:
interval: "weekly"
interval: "monthly"
open-pull-requests-limit: 0
ignore:
- dependency-name: "*"

View File

@@ -6,7 +6,11 @@ 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 }}
cancel-in-progress: true
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -15,7 +19,7 @@ env:
defaults:
run:
working-directory: compiler
working-directory: compiler/apps/playground
jobs:
playground:
@@ -27,13 +31,25 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: compiler/yarn.lock
cache-dependency-path: compiler/**/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- name: yarn install compiler
run: yarn install --frozen-lockfile
working-directory: compiler
- name: yarn install playground
run: yarn install --frozen-lockfile
- run: npx playwright install --with-deps chromium
- run: yarn workspace playground 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: compiler/apps/playground/test-results

View File

@@ -13,6 +13,9 @@ on:
dist_tag:
required: true
type: string
version_name:
required: true
type: string
secrets:
NPM_TOKEN:
required: true
@@ -44,9 +47,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Publish packages to npm
run: |
cp ./scripts/release/ci-npmrc ~/.npmrc
scripts/release/publish.js --frfr --ci --tags ${{ inputs.dist_tag }}
scripts/release/publish.js --frfr --ci --versionName=${{ inputs.version_name }} --tag ${{ inputs.dist_tag }}

View File

@@ -5,6 +5,15 @@ on:
inputs:
prerelease_commit_sha:
required: false
release_channel:
required: true
type: string
dist_tag:
required: true
type: string
version_name:
required: true
type: string
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -15,7 +24,8 @@ jobs:
uses: facebook/react/.github/workflows/compiler_prereleases.yml@main
with:
commit_sha: ${{ inputs.prerelease_commit_sha || github.sha }}
release_channel: experimental
dist_tag: experimental
release_channel: ${{ inputs.release_channel }}
dist_tag: ${{ inputs.dist_tag }}
version_name: ${{ inputs.version_name }}
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -16,5 +16,6 @@ jobs:
commit_sha: ${{ github.sha }}
release_channel: experimental
dist_tag: experimental
version_name: '0.0.0'
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -0,0 +1,21 @@
name: (Compiler) Publish Prereleases Weekly
on:
schedule:
# At 10 minutes past 9:00 on Mon
- cron: 10 9 * * 1
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
publish_prerelease_beta:
name: Publish to beta channel
uses: facebook/react/.github/workflows/compiler_prereleases.yml@main
with:
commit_sha: ${{ github.sha }}
release_channel: beta
dist_tag: beta
version_name: '19.0.0'
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -15,6 +15,10 @@ on:
- compiler/Cargo.*
- compiler/*.toml
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings

View File

@@ -8,6 +8,10 @@ on:
- compiler/**
- .github/workflows/compiler_typescript.yml
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -43,7 +47,7 @@ jobs:
uses: actions/cache@v4
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace babel-plugin-react-compiler lint
@@ -63,7 +67,7 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace babel-plugin-react-compiler jest
@@ -87,6 +91,6 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace ${{ matrix.workspace_name }} test

View File

@@ -30,7 +30,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
@@ -62,7 +64,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -103,6 +107,7 @@ jobs:
- "16.8" # hooks
- "17.0"
- "18.0"
- "18.2" # compiler polyfill
continue-on-error: true
steps:
- uses: actions/checkout@v4
@@ -116,7 +121,7 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4
@@ -150,7 +155,7 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4

View File

@@ -7,6 +7,10 @@ on:
paths-ignore:
- compiler/**
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -48,7 +52,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: node ./scripts/tasks/flow-ci ${{ matrix.flow_inline_config_shortname }}
@@ -68,7 +74,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: |
yarn generate-inline-fizz-runtime
@@ -90,7 +98,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn flags
@@ -139,7 +149,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
@@ -168,7 +180,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci
env:
@@ -238,7 +252,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -266,7 +282,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -309,7 +327,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -340,7 +360,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -368,7 +390,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: v2-yarn_cache_fixtures_dom-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: fixtures_dom-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
working-directory: fixtures/dom
@@ -408,7 +432,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: v2-yarn_cache_fixtures_flight-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: fixtures_flight-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -464,7 +490,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -510,7 +538,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -543,6 +573,8 @@ jobs:
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
@@ -581,7 +613,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build for PR
uses: actions/download-artifact@v4

View File

@@ -11,6 +11,11 @@ on:
commit_sha:
required: false
type: string
force:
description: 'Force a commit to the builds/... branches'
required: true
default: false
type: boolean
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -69,7 +74,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
name: yarn install (react)
- run: yarn install --frozen-lockfile
@@ -77,7 +84,7 @@ jobs:
working-directory: scripts/release
- name: Download artifacts for base revision
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || github.event.workflow_run.head_sha }}
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}
- name: Display structure of build
run: ls -R build
- name: Strip @license from eslint plugin and react-refresh
@@ -118,13 +125,14 @@ jobs:
run: |
BASE_FOLDER='compiled-rn/facebook-fbsource/xplat/js'
mkdir -p ${BASE_FOLDER}/react-native-github/Libraries/Renderer/
mkdir -p ${BASE_FOLDER}/RKJSModules/vendor/react/{scheduler,react,react-is,react-test-renderer}/
mkdir -p ${BASE_FOLDER}/RKJSModules/vendor/react/{scheduler,react,react-dom,react-is,react-test-renderer}/
# Move React Native renderer
mv build/react-native/implementations/ $BASE_FOLDER/react-native-github/Libraries/Renderer/
mv build/react-native/shims/ $BASE_FOLDER/react-native-github/Libraries/Renderer/
mv build/facebook-react-native/scheduler/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/scheduler/
mv build/facebook-react-native/react/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react/
mv build/facebook-react-native/react-dom/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-dom/
mv build/facebook-react-native/react-is/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-is/
mv build/facebook-react-native/react-test-renderer/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-test-renderer/
@@ -139,9 +147,9 @@ jobs:
ls -R ./compiled-rn
- name: Add REVISION files
run: |
echo ${{ github.event.workflow_run.head_sha }} >> ./compiled/facebook-www/REVISION
echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION
cp ./compiled/facebook-www/REVISION ./compiled/facebook-www/REVISION_TRANSFORMS
echo ${{ github.event.workflow_run.head_sha}} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
echo ${{ github.sha}} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
- name: "Get current version string"
id: get_current_version
run: |
@@ -165,7 +173,7 @@ jobs:
commit_www_artifacts:
needs: download_artifacts
if: ${{ (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.www_branch_count == '0') || github.ref == 'refs/heads/meta-www' }}
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.www_branch_count == '0')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -195,6 +203,7 @@ jobs:
grep -rl "$CURRENT_VERSION_MODERN" ./compiled | xargs -r sed -i -e "s/$CURRENT_VERSION_MODERN/$LAST_VERSION_MODERN/g"
grep -rl "$CURRENT_VERSION_MODERN" ./compiled || echo "Modern version reverted"
- name: Check for changes
if: inputs.force != true
id: check_should_commit
run: |
echo "Full git status"
@@ -212,7 +221,7 @@ jobs:
echo "should_commit=false" >> "$GITHUB_OUTPUT"
fi
- name: Re-apply version changes
if: steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != ''
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != '')
env:
CURRENT_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.download_artifacts.outputs.current_version_modern }}
@@ -229,26 +238,26 @@ jobs:
grep -rl "$LAST_VERSION_MODERN" ./compiled | xargs -r sed -i -e "s/$LAST_VERSION_MODERN/$CURRENT_VERSION_MODERN/g"
grep -rl "$LAST_VERSION_MODERN" ./compiled || echo "Classic version re-applied"
- name: Will commit these changes
if: steps.check_should_commit.outputs.should_commit == 'true'
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
echo ":"
git status -u
- name: Commit changes to branch
if: steps.check_should_commit.outputs.should_commit == 'true'
uses: stefanzweifel/git-auto-commit-action@v4
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: |
${{ github.event.workflow_run.head_commit.message }}
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
DiffTrain build for [${{ github.event.workflow_run.head_sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha }})
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
branch: builds/facebook-www
commit_user_name: ${{ github.event.workflow_run.triggering_actor.login }}
commit_user_email: ${{ github.event.workflow_run.triggering_actor.email || format('{0}@users.noreply.github.com', github.event.workflow_run.triggering_actor.login) }}
commit_user_name: ${{ github.triggering_actor }}
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
create_branch: true
commit_fbsource_artifacts:
needs: download_artifacts
if: ${{ (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.fbsource_branch_count == '0') || github.ref == 'refs/heads/meta-fbsource' }}
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.fbsource_branch_count == '0')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -271,6 +280,7 @@ jobs:
grep -rl "$CURRENT_VERSION" ./compiled-rn | xargs -r sed -i -e "s/$CURRENT_VERSION/$LAST_VERSION/g"
grep -rl "$CURRENT_VERSION" ./compiled-rn || echo "Version reverted"
- name: Check for changes
if: inputs.force != 'true'
id: check_should_commit
run: |
echo "Full git status"
@@ -289,7 +299,7 @@ jobs:
echo "should_commit=false" >> "$GITHUB_OUTPUT"
fi
- name: Re-apply version changes
if: steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_rn != ''
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_rn != '')
env:
CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.download_artifacts.outputs.last_version_rn }}
@@ -299,12 +309,12 @@ jobs:
grep -rl "$LAST_VERSION" ./compiled-rn | xargs -r sed -i -e "s/$LAST_VERSION/$CURRENT_VERSION/g"
grep -rl "$LAST_VERSION" ./compiled-rn || echo "Version re-applied"
- name: Add files for signing
if: steps.check_should_commit.outputs.should_commit == 'true'
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
echo ":"
git add .
- name: Signing files
if: steps.check_should_commit.outputs.should_commit == 'true'
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
uses: actions/github-script@v7
with:
script: |
@@ -358,8 +368,9 @@ jobs:
console.log('Signing files in directory:', directory);
try {
const result = execSync(`git status --porcelain ${directory}`, {encoding: 'utf8'});
console.log(result);
// Parse the git status output to get file paths
// Parse the git status output to get file paths!
const files = result.split('\n').filter(file => file.endsWith('.js'));
if (files.length === 0) {
@@ -368,7 +379,14 @@ jobs:
);
} else {
files.forEach(line => {
const file = line.slice(3).trim();
let file = null;
if (line.startsWith('D ')) {
return;
} else if (line.startsWith('R ')) {
file = line.slice(line.indexOf('->') + 3);
} else {
file = line.slice(3).trim();
}
if (file) {
console.log(' Signing file:', file);
const originalContents = fs.readFileSync(file, 'utf8');
@@ -387,19 +405,19 @@ jobs:
console.error('Error signing files:', e);
}
- name: Will commit these changes
if: steps.check_should_commit.outputs.should_commit == 'true'
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
git add .
git status
- name: Commit changes to branch
if: steps.check_should_commit.outputs.should_commit == 'true'
uses: stefanzweifel/git-auto-commit-action@v4
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: |
${{ github.event.workflow_run.head_commit.message }}
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
DiffTrain build for commit https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha }}.
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
branch: builds/facebook-fbsource
commit_user_name: ${{ github.event.workflow_run.triggering_actor.login }}
commit_user_email: ${{ github.event.workflow_run.triggering_actor.email || format('{0}@users.noreply.github.com', github.event.workflow_run.triggering_actor.login) }}
commit_user_name: ${{ github.triggering_actor }}
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
create_branch: true

View File

@@ -40,7 +40,9 @@ jobs:
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release

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

@@ -5,6 +5,10 @@ on:
branches: [main]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -25,7 +29,9 @@ jobs:
uses: actions/cache@v4
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn prettier-check
@@ -43,7 +49,9 @@ jobs:
uses: actions/cache@v4
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: node ./scripts/tasks/eslint
@@ -61,7 +69,9 @@ jobs:
uses: actions/cache@v4
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: ./scripts/ci/check_license.sh
@@ -79,6 +89,8 @@ jobs:
uses: actions/cache@v4
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: ./scripts/ci/test_print_warnings.sh

View File

@@ -24,6 +24,7 @@ compiler/**/.next
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.js
compiler/crates
compiler/target
compiler/apps/playground/public
compiler/**/LICENSE

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

@@ -30,7 +30,7 @@ const canaryChannelLabel = 'rc';
// If the canaryChannelLabel is "rc", the build pipeline will use this to build
// an RC version of the packages.
const rcNumber = 0;
const rcNumber = 1;
const stablePackages = {
'eslint-plugin-react-hooks': '5.1.0',

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 =
(await page.locator('.monaco-editor').nth(2).allInnerTexts()) ?? [];
expect(concat(userInput)).toMatchSnapshot('user-input.txt');
const text =
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
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 =
(await page.locator('.monaco-editor').nth(2).allInnerTexts()) ?? [];
expect(concat(defaultInput)).toMatchSnapshot('default-input.txt');
const text =
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
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

@@ -7,7 +7,11 @@
import '../styles/globals.css';
export default function RootLayout({children}: {children: React.ReactNode}) {
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}): JSX.Element {
'use no memo';
return (
<html lang="en">

View File

@@ -11,7 +11,7 @@ import {SnackbarProvider} from 'notistack';
import {Editor, Header, StoreProvider} from '../components';
import MessageSnackbar from '../components/Message';
export default function Hoot() {
export default function Page(): JSX.Element {
return (
<StoreProvider>
<SnackbarProvider

View File

@@ -1,21 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
module.exports = function (api) {
api.cache(true);
return {
presets: ['next/babel'],
plugins: [
[
'babel-plugin-react-compiler',
{
runtimeModule: 'react-compiler-runtime',
},
],
],
};
};

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {parse as babelParse, ParserPlugin} from '@babel/parser';
import {parse as babelParse} from '@babel/parser';
import * as HermesParser from 'hermes-parser';
import traverse, {NodePath} from '@babel/traverse';
import * as t from '@babel/types';
@@ -14,12 +14,12 @@ import {
CompilerErrorDetail,
Effect,
ErrorSeverity,
parseConfigPragma,
printHIR,
printReactiveFunction,
run,
parseConfigPragmaForTests,
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';
@@ -45,7 +45,26 @@ import {
import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR';
import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction';
function parseInput(input: string, language: 'flow' | 'typescript') {
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') {
return HermesParser.parse(input, {
@@ -65,29 +84,36 @@ function parseInput(input: string, language: 'flow' | 'typescript') {
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();
},
});
@@ -100,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',
@@ -183,9 +248,9 @@ function getFunctionIdentifier(
}
function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
const results = new Map<string, PrintedCompilerPipelineValue[]>();
const results = new Map<string, Array<PrintedCompilerPipelineValue>>();
const error = new CompilerError();
const upsert = (result: PrintedCompilerPipelineValue) => {
const upsert: (result: PrintedCompilerPipelineValue) => void = result => {
const entry = results.get(result.name);
if (Array.isArray(entry)) {
entry.push(result);
@@ -210,23 +275,39 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
try {
// Extract the first line to quickly check for custom test directives
const pragma = source.substring(0, source.indexOf('\n'));
const config = parseConfigPragma(pragma);
for (const fn of parseFunctions(source, language)) {
const id = withIdentifier(getFunctionIdentifier(fn));
for (const result of run(
fn,
const config = parseConfigPragmaForTests(pragma);
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(
func.fn,
{
...config,
customHooks: new Map([...COMMON_HOOKS]),
},
getReactFunctionType(id),
'_c',
null,
null,
null,
)) {
const fnName = id.name;
switch (result.kind) {
case 'ast': {
upsert({
@@ -279,13 +360,17 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
}
}
} catch (err) {
// error might be an invariant violation or other runtime error
// (i.e. object shape that is not CompilerError)
/**
* error might be an invariant violation or other runtime error
* (i.e. object shape that is not CompilerError)
*/
if (err instanceof CompilerError && err.details.length > 0) {
error.details.push(...err.details);
} else {
// Handle unexpected failures by logging (to get a stack trace)
// and reporting
/**
* Handle unexpected failures by logging (to get a stack trace)
* and reporting
*/
console.error(err);
error.details.push(
new CompilerErrorDetail({
@@ -303,7 +388,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
return [{kind: 'ok', results}, language];
}
export default function Editor() {
export default function Editor(): JSX.Element {
const store = useStore();
const deferredStore = useDeferredValue(store);
const dispatchStore = useStoreDispatch();

View File

@@ -15,18 +15,17 @@ import {useEffect, useState} from 'react';
import {renderReactCompilerMarkers} from '../../lib/reactCompilerMonacoDiagnostics';
import {useStore, useStoreDispatch} from '../StoreContext';
import {monacoOptions} from './monacoOptions';
// TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
// @ts-ignore
// @ts-expect-error TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
import React$Types from '../../node_modules/@types/react/index.d.ts';
loader.config({monaco});
type Props = {
errors: CompilerErrorDetail[];
errors: Array<CompilerErrorDetail>;
language: 'flow' | 'typescript';
};
export default function Input({errors, language}: Props) {
export default function Input({errors, language}: Props): JSX.Element {
const [monaco, setMonaco] = useState<Monaco | null>(null);
const store = useStore();
const dispatchStore = useStoreDispatch();
@@ -38,18 +37,19 @@ export default function Input({errors, language}: Props) {
const model = monaco.editor.getModel(uri);
invariant(model, 'Model must exist for the selected input file.');
renderReactCompilerMarkers({monaco, model, details: errors});
// N.B. that `tabSize` is a model property, not an editor property.
// So, the tab size has to be set per model.
/**
* N.B. that `tabSize` is a model property, not an editor property.
* So, the tab size has to be set per model.
*/
model.updateOptions({tabSize: 2});
}, [monaco, errors]);
const flowDiagnosticDisable = [
7028 /* unused label */, 6133 /* var declared but not read */,
];
useEffect(() => {
// Ignore "can only be used in TypeScript files." errors, since
// we want to support syntax highlighting for Flow (*.js) files
// and Flow is not a built-in language.
/**
* Ignore "can only be used in TypeScript files." errors, since
* we want to support syntax highlighting for Flow (*.js) files
* and Flow is not a built-in language.
*/
if (!monaco) return;
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
diagnosticCodesToIgnore: [
@@ -64,7 +64,9 @@ export default function Input({errors, language}: Props) {
8011,
8012,
8013,
...(language === 'flow' ? flowDiagnosticDisable : []),
...(language === 'flow'
? [7028 /* unused label */, 6133 /* var declared but not read */]
: []),
],
noSemanticValidation: true,
// Monaco can't validate Flow component syntax
@@ -72,7 +74,7 @@ export default function Input({errors, language}: Props) {
});
}, [monaco, language]);
const handleChange = (value: string | undefined) => {
const handleChange: (value: string | undefined) => void = value => {
if (!value) return;
dispatchStore({
@@ -83,7 +85,10 @@ export default function Input({errors, language}: Props) {
});
};
const handleMount = (_: editor.IStandaloneCodeEditor, monaco: Monaco) => {
const handleMount: (
_: editor.IStandaloneCodeEditor,
monaco: Monaco,
) => void = (_, monaco) => {
setMonaco(monaco);
const tscOptions = {
@@ -111,10 +116,12 @@ export default function Input({errors, language}: Props) {
monaco.languages.typescript.javascriptDefaults.addExtraLib(...reactLib);
monaco.languages.typescript.typescriptDefaults.addExtraLib(...reactLib);
// Remeasure the font in case the custom font is loaded only after
// Monaco Editor is mounted.
// N.B. that this applies also to the output editor as it seems
// Monaco Editor instances share the same font config.
/**
* Remeasure the font in case the custom font is loaded only after
* Monaco Editor is mounted.
* N.B. that this applies also to the output editor as it seems
* Monaco Editor instances share the same font config.
*/
document.fonts.ready.then(() => {
monaco.editor.remeasureFonts();
});
@@ -125,14 +132,18 @@ export default function Input({errors, language}: Props) {
<Resizable
minWidth={650}
enable={{right: true}}
// Restrict MonacoEditor's height, since the config autoLayout:true
// will grow the editor to fit within parent element
/**
* Restrict MonacoEditor's height, since the config autoLayout:true
* will grow the editor to fit within parent element
*/
className="!h-[calc(100vh_-_3.5rem)]">
<MonacoEditor
path={'index.js'}
// .js and .jsx files are specified to be TS so that Monaco can actually
// check their syntax using its TS language service. They are still JS files
// due to their extensions, so TS language features don't work.
/**
* .js and .jsx files are specified to be TS so that Monaco can actually
* check their syntax using its TS language service. They are still JS files
* due to their extensions, so TS language features don't work.
*/
language={'javascript'}
value={store.source}
onMount={handleMount}

View File

@@ -17,7 +17,7 @@ import {type CompilerError} from 'babel-plugin-react-compiler/src';
import parserBabel from 'prettier/plugins/babel';
import * as prettierPluginEstree from 'prettier/plugins/estree';
import * as prettier from 'prettier/standalone';
import {memo, useEffect, useState} from 'react';
import {memo, ReactNode, useEffect, useState} from 'react';
import {type Store} from '../../lib/stores';
import TabbedWindow from '../TabbedWindow';
import {monacoOptions} from './monacoOptions';
@@ -42,10 +42,10 @@ export type PrintedCompilerPipelineValue =
| {kind: 'debug'; name: string; fnName: string | null; value: string};
export type CompilerOutput =
| {kind: 'ok'; results: Map<string, PrintedCompilerPipelineValue[]>}
| {kind: 'ok'; results: Map<string, Array<PrintedCompilerPipelineValue>>}
| {
kind: 'err';
results: Map<string, PrintedCompilerPipelineValue[]>;
results: Map<string, Array<PrintedCompilerPipelineValue>>;
error: CompilerError;
};
@@ -54,7 +54,10 @@ type Props = {
compilerOutput: CompilerOutput;
};
async function tabify(source: string, compilerOutput: CompilerOutput) {
async function tabify(
source: string,
compilerOutput: CompilerOutput,
): Promise<Map<string, ReactNode>> {
const tabs = new Map<string, React.ReactNode>();
const reorderedTabs = new Map<string, React.ReactNode>();
const concattedResults = new Map<string, string>();
@@ -112,8 +115,10 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
}
// Ensure that JS and the JS source map come first
if (topLevelFnDecls.length > 0) {
// Make a synthetic Program so we can have a single AST with all the top level
// FunctionDeclarations
/**
* Make a synthetic Program so we can have a single AST with all the top level
* FunctionDeclarations
*/
const ast = t.program(topLevelFnDecls);
const {code, sourceMapUrl} = await codegen(ast, source);
reorderedTabs.set(
@@ -175,7 +180,7 @@ function getSourceMapUrl(code: string, map: string): string | null {
)}`;
}
function Output({store, compilerOutput}: Props) {
function Output({store, compilerOutput}: Props): JSX.Element {
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set(['JS']));
const [tabs, setTabs] = useState<Map<string, React.ReactNode>>(
() => new Map(),
@@ -236,11 +241,13 @@ function TextTabContent({
output: string;
diff: string | null;
showInfoPanel: boolean;
}) {
}): JSX.Element {
const [diffMode, setDiffMode] = useState(false);
return (
// Restrict MonacoEditor's height, since the config autoLayout:true
// will grow the editor to fit within parent element
/**
* Restrict MonacoEditor's height, since the config autoLayout:true
* will grow the editor to fit within parent element
*/
<div className="w-full h-monaco_small sm:h-monaco">
{showInfoPanel ? (
<div className="flex items-center gap-1 bg-amber-50 p-2">

View File

@@ -7,8 +7,10 @@
import dynamic from 'next/dynamic';
// monaco-editor is currently not compatible with ssr
// https://github.com/vercel/next.js/issues/31692
/**
* monaco-editor is currently not compatible with ssr
* https://github.com/vercel/next.js/issues/31692
*/
const Editor = dynamic(() => import('./EditorImpl'), {
ssr: false,
});

View File

@@ -16,26 +16,26 @@ import {IconGitHub} from './Icons/IconGitHub';
import Logo from './Logo';
import {useStoreDispatch} from './StoreContext';
export default function Header() {
export default function Header(): JSX.Element {
const [showCheck, setShowCheck] = useState(false);
const dispatchStore = useStoreDispatch();
const {enqueueSnackbar, closeSnackbar} = useSnackbar();
const handleReset = () => {
const handleReset: () => void = () => {
if (confirm('Are you sure you want to reset the playground?')) {
/*
Close open snackbars if any. This is necessary because when displaying
outputs (Preview or not), we only close previous snackbars if we received
new messages, which is needed in order to display "Bad URL" or success
messages when loading Playground for the first time. Otherwise, messages
such as "Bad URL" will be closed by the outputs calling `closeSnackbar`.
*/
/**
* Close open snackbars if any. This is necessary because when displaying
* outputs (Preview or not), we only close previous snackbars if we received
* new messages, which is needed in order to display "Bad URL" or success
* messages when loading Playground for the first time. Otherwise, messages
* such as "Bad URL" will be closed by the outputs calling `closeSnackbar`.
*/
closeSnackbar();
dispatchStore({type: 'setStore', payload: {store: defaultStore}});
}
};
const handleShare = () => {
const handleShare: () => void = () => {
navigator.clipboard.writeText(location.href).then(() => {
enqueueSnackbar('URL copied to clipboard');
setShowCheck(true);

View File

@@ -7,7 +7,7 @@
// https://github.com/reactjs/reactjs.org/blob/main/beta/src/components/Logo.tsx
export default function Logo(props: JSX.IntrinsicElements['svg']) {
export default function Logo(props: JSX.IntrinsicElements['svg']): JSX.Element {
return (
<svg
viewBox="0 0 410 369"

View File

@@ -29,7 +29,7 @@ export const useStoreDispatch = StoreDispatchContext.useContext;
/**
* Make Store and dispatch function available to all sub-components in children.
*/
export function StoreProvider({children}: {children: ReactNode}) {
export function StoreProvider({children}: {children: ReactNode}): JSX.Element {
const [store, dispatch] = useReducer(storeReducer, emptyStore);
return (

View File

@@ -69,6 +69,9 @@ function TabbedWindowItem({
setTabsOpen(nextState);
}, [tabsOpen, name, setTabsOpen]);
// Replace spaces with non-breaking spaces
const displayName = name.replace(/ /g, '\u00A0');
return (
<div key={name} className="flex flex-row">
{isShow ? (
@@ -80,7 +83,7 @@ function TabbedWindowItem({
className={`p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 ${
hasChanged ? 'font-bold' : 'font-light'
} text-secondary hover:text-link`}>
- {name}
- {displayName}
</h2>
{tabs.get(name) ?? <div>No output for {name}</div>}
</Resizable>
@@ -94,7 +97,7 @@ function TabbedWindowItem({
className={`flex-grow-0 w-5 transition-colors duration-150 ease-in ${
hasChanged ? 'font-bold' : 'font-light'
} text-secondary hover:text-link`}>
{name}
{displayName}
</button>
</div>
)}

View File

@@ -23,10 +23,13 @@ import React from 'react';
* Instead, it throws an error when `useContext` is not called within a
* Provider with a value.
*/
export default function createContext<T>() {
export default function createContext<T>(): {
useContext: () => NonNullable<T>;
Provider: React.Provider<T | null>;
} {
const context = React.createContext<T | null>(null);
function useContext() {
function useContext(): NonNullable<T> {
const c = React.useContext(context);
if (!c)
throw new Error('useContext must be within a Provider with a value');

View File

@@ -46,9 +46,9 @@ function mapReactCompilerDiagnosticToMonacoMarker(
type ReactCompilerMarkerConfig = {
monaco: Monaco;
model: editor.ITextModel;
details: CompilerErrorDetail[];
details: Array<CompilerErrorDetail>;
};
let decorations: string[] = [];
let decorations: Array<string> = [];
export function renderReactCompilerMarkers({
monaco,
model,

View File

@@ -28,7 +28,7 @@ export function decodeStore(hash: string): Store {
/**
* Serialize, encode, and save @param store to localStorage and update URL.
*/
export function saveStore(store: Store) {
export function saveStore(store: Store): void {
const hash = encodeStore(store);
localStorage.setItem('playgroundStore', hash);
history.replaceState({}, '', `#${hash}`);
@@ -56,8 +56,10 @@ export function initStoreFromUrlOrLocalStorage(): Store {
const encodedSourceFromLocal = localStorage.getItem('playgroundStore');
const encodedSource = encodedSourceFromUrl || encodedSourceFromLocal;
// No data in the URL and no data in the localStorage to fallback to.
// Initialize with the default store.
/**
* No data in the URL and no data in the localStorage to fallback to.
* Initialize with the default store.
*/
if (!encodedSource) return defaultStore;
const raw = decodeStore(encodedSource);

View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

View File

@@ -9,6 +9,9 @@ const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const path = require('path');
const nextConfig = {
experimental: {
reactCompiler: true,
},
reactStrictMode: true,
webpack: (config, options) => {
// Load *.d.ts files as strings using https://webpack.js.org/guides/asset-modules/#source-assets.

View File

@@ -3,58 +3,60 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "NODE_ENV=development next dev",
"build": "next build && node ./scripts/downloadFonts.js",
"vercel-build": "yarn workspaces run build",
"dev": "cd ../.. && concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run build --watch\" \"yarn workspace react-compiler-runtime run build --watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && cd apps/playground && NODE_ENV=development next dev\"",
"build:compiler": "cd ../.. && concurrently -n compiler,runtime \"yarn workspace babel-plugin-react-compiler run build\" \"yarn workspace react-compiler-runtime run build\"",
"build": "yarn build:compiler && next build",
"postbuild": "node ./scripts/downloadFonts.js",
"postinstall": "./scripts/link-compiler.sh",
"vercel-build": "yarn build",
"start": "next start",
"lint": "next lint",
"test": "playwright test"
},
"dependencies": {
"@babel/core": "^7.19.1",
"@babel/generator": "^7.19.1",
"@babel/parser": "^7.19.1",
"@babel/plugin-syntax-typescript": "^7.18.6",
"@babel/core": "^7.18.9",
"@babel/generator": "^7.18.9",
"@babel/parser": "^7.18.9",
"@babel/plugin-syntax-typescript": "^7.18.9",
"@babel/plugin-transform-block-scoping": "^7.18.9",
"@babel/plugin-transform-modules-commonjs": "^7.18.6",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/traverse": "^7.19.1",
"@babel/types": "^7.19.0",
"@babel/plugin-transform-modules-commonjs": "^7.18.9",
"@babel/preset-react": "^7.18.9",
"@babel/preset-typescript": "^7.18.9",
"@babel/traverse": "^7.18.9",
"@babel/types": "7.18.9",
"@heroicons/react": "^1.0.6",
"@monaco-editor/react": "^4.4.6",
"@playwright/test": "^1.42.1",
"@use-gesture/react": "^10.2.22",
"fs": "^0.0.1-security",
"hermes-eslint": "^0.14.0",
"hermes-parser": "^0.22.0",
"hermes-eslint": "^0.25.0",
"hermes-parser": "^0.25.0",
"invariant": "^2.2.4",
"lz-string": "^1.5.0",
"monaco-editor": "^0.34.1",
"next": "^13.5.6",
"monaco-editor": "^0.52.0",
"next": "^15.0.1",
"notistack": "^3.0.0-alpha.7",
"prettier": "^3.3.3",
"pretty-format": "^29.3.1",
"re-resizable": "^6.9.16",
"react": "18.2.0",
"react-compiler-runtime": "*",
"react-dom": "18.2.0"
"react": "19.0.0-rc-77b637d6-20241016",
"react-dom": "19.0.0-rc-77b637d6-20241016"
},
"devDependencies": {
"@types/node": "18.11.9",
"@types/react": "18.0.25",
"@types/react-dom": "18.0.9",
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
"autoprefixer": "^10.4.13",
"clsx": "^1.2.1",
"concurrently": "^7.4.0",
"eslint": "^8.28.0",
"eslint-config-next": "^13.5.6",
"hermes-parser": "^0.22.0",
"eslint-config-next": "^15.0.1",
"monaco-editor-webpack-plugin": "^7.1.0",
"postcss": "^8.4.31",
"tailwindcss": "^3.2.4"
"tailwindcss": "^3.2.4",
"wait-on": "^7.2.0"
},
"resolutions": {
"./**/@babel/parser": "7.7.4",
"./**/@babel/types": "7.7.4"
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
}
}

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
@@ -30,13 +32,16 @@ export default defineConfig({
// Run your local dev server before starting the tests:
// https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests
webServer: {
command:
'yarn workspace babel-plugin-react-compiler build && yarn workspace react-compiler-runtime build && yarn dev',
command: 'yarn dev',
url: baseURL,
timeout: 300 * 1000,
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

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
set -eo pipefail
HERE=$(pwd)
cd ../../packages/react-compiler-runtime && yarn --silent link && cd $HERE
cd ../../packages/babel-plugin-react-compiler && yarn --silent link && cd $HERE
yarn --silent link babel-plugin-react-compiler
yarn --silent link react-compiler-runtime

View File

@@ -31,6 +31,7 @@
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
"node_modules",
"../../../**"
]
}

File diff suppressed because it is too large Load Diff

View File

View File

@@ -2,12 +2,7 @@
"private": true,
"workspaces": {
"packages": [
"packages/*",
"apps/*"
],
"nohoist": [
"**/next",
"**/next/**"
"packages/*"
]
},
"repository": {
@@ -20,15 +15,15 @@
"start": "yarn workspace playground run start",
"next": "yarn workspace playground run dev",
"build": "yarn workspaces run build",
"dev": "concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run build --watch\" \"yarn workspace react-compiler-runtime run build --watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && yarn workspace playground run dev\"",
"dev": "echo 'DEPRECATED: use `cd apps/playground && yarn dev` instead!' && sleep 5 && cd apps/playground && yarn dev",
"test": "yarn workspaces run test",
"snap": "yarn workspace babel-plugin-react-compiler run snap",
"snap:build": "yarn workspace snap run build",
"postinstall": "perl -p -i -e 's/react\\.element/react.transitional.element/' packages/snap/node_modules/fbt/lib/FbtReactUtil.js && perl -p -i -e 's/didWarnAboutUsingAct = false;/didWarnAboutUsingAct = true;/' packages/babel-plugin-react-compiler/node_modules/react-dom/cjs/react-dom-test-utils.development.js",
"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",
@@ -39,19 +34,17 @@
"@tsconfig/strictest": "^2.0.5",
"concurrently": "^7.4.0",
"folder-hash": "^4.0.4",
"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.13.2",
"rollup": "^4.22.4",
"rollup-plugin-banner2": "^1.2.3",
"rollup-plugin-prettier": "^4.1.1",
"typescript": "^5.4.3",
"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,11 +5,12 @@
"main": "dist/index.js",
"license": "MIT",
"files": [
"dist"
"dist",
"!*.tsbuildinfo"
],
"scripts": {
"build": "rimraf dist && rollup --config --bundleConfigAsCjs",
"test": "yarn snap:ci",
"test": "./scripts/link-react-compiler-runtime.sh && yarn snap:ci",
"jest": "yarn build && ts-node node_modules/.bin/jest",
"snap": "node ../snap/dist/main.js",
"snap:build": "yarn workspace snap run build",
@@ -18,16 +19,11 @@
"lint": "yarn eslint src"
},
"dependencies": {
"@babel/generator": "7.2.0",
"@babel/types": "^7.19.0",
"chalk": "4",
"invariant": "^2.2.4",
"pretty-format": "^24",
"zod": "^3.22.4",
"zod-validation-error": "^2.1.0"
"@babel/types": "^7.19.0"
},
"devDependencies": {
"@babel/core": "^7.2.0",
"@babel/generator": "7.2.0",
"@babel/parser": "^7.2.0",
"@babel/plugin-syntax-typescript": "^7.18.6",
"@babel/plugin-transform-block-scoping": "^7.18.9",
@@ -41,20 +37,25 @@
"@types/invariant": "^2.2.35",
"@types/jest": "^29.0.3",
"@types/node": "^18.7.18",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"@typescript-eslint/eslint-plugin": "^8.7.0",
"@typescript-eslint/parser": "^8.7.0",
"babel-jest": "^29.0.3",
"babel-plugin-fbt": "^1.0.0",
"babel-plugin-fbt-runtime": "^1.0.0",
"eslint": "8.27.0",
"chalk": "4",
"eslint": "^8.57.1",
"glob": "^7.1.6",
"invariant": "^2.2.4",
"jest": "^29.0.3",
"jest-environment-jsdom": "^29.0.3",
"react": "19.0.0-beta-b498834eab-20240506",
"react-dom": "19.0.0-beta-b498834eab-20240506",
"pretty-format": "^24",
"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"
"ts-node": "^10.9.2",
"zod": "^3.22.4",
"zod-validation-error": "^2.1.0"
},
"resolutions": {
"./**/@babel/parser": "7.7.4",

View File

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

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
set -eo pipefail
yarn --silent workspace react-compiler-runtime link
yarn --silent workspace babel-plugin-react-compiler link react-compiler-runtime

View File

@@ -14,6 +14,7 @@ import {
parseEnvironmentConfig,
} from '../HIR/Environment';
import {hasOwnProperty} from '../Utils/utils';
import {fromZodError} from 'zod-validation-error';
const PanicThresholdOptionsSchema = z.enum([
/*
@@ -86,17 +87,6 @@ export type PluginOptions = {
*/
compilationMode: CompilationMode;
/*
* If enabled, Forget will import `useMemoCache` from the given module
* instead of `react/compiler-runtime`.
*
* ```
* // If set to "react-compiler-runtime"
* import {c as useMemoCache} from 'react-compiler-runtime';
* ```
*/
runtimeModule?: string | null | undefined;
/**
* By default React Compiler will skip compilation of code that suppresses the default
* React ESLint rules, since this is a strong indication that the code may be breaking React rules
@@ -121,8 +111,34 @@ export type PluginOptions = {
* Set this flag (on by default) to automatically check for this library and activate the support.
*/
enableReanimatedCheck: boolean;
/**
* The minimum major version of React that the compiler should emit code for. If the target is 19
* or higher, the compiler emits direct imports of React runtime APIs needed by the compiler. On
* versions prior to 19, an extra runtime package react-compiler-runtime is necessary to provide
* a userspace approximation of runtime APIs.
*/
target: CompilerReactTarget;
};
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([
/*
* Compiles functions annotated with "use forget" or component/hook-like functions.
@@ -202,7 +218,6 @@ export const defaultOptions: PluginOptions = {
logger: null,
gating: null,
noEmit: false,
runtimeModule: null,
eslintSuppressionRules: null,
flowSuppressions: true,
ignoreUseNoForget: false,
@@ -210,6 +225,7 @@ export const defaultOptions: PluginOptions = {
return filename.indexOf('node_modules') === -1;
},
enableReanimatedCheck: true,
target: '19',
} as const;
export function parsePluginOptions(obj: unknown): PluginOptions {
@@ -222,25 +238,49 @@ export function parsePluginOptions(obj: unknown): PluginOptions {
// normalize string configs to be case insensitive
value = value.toLowerCase();
}
if (key === 'environment') {
const environmentResult = parseEnvironmentConfig(value);
if (environmentResult.isErr()) {
CompilerError.throwInvalidConfig({
reason:
'Error in validating environment config. This is an advanced setting and not meant to be used directly',
description: environmentResult.unwrapErr().toString(),
suggestions: null,
loc: null,
});
if (isCompilerFlag(key)) {
switch (key) {
case 'environment': {
const environmentResult = parseEnvironmentConfig(value);
if (environmentResult.isErr()) {
CompilerError.throwInvalidConfig({
reason:
'Error in validating environment config. This is an advanced setting and not meant to be used directly',
description: environmentResult.unwrapErr().toString(),
suggestions: null,
loc: null,
});
}
parsedOptions[key] = environmentResult.unwrap();
break;
}
case 'target': {
parsedOptions[key] = parseTargetConfig(value);
break;
}
default: {
parsedOptions[key] = value;
}
}
parsedOptions[key] = environmentResult.unwrap();
} else if (isCompilerFlag(key)) {
parsedOptions[key] = value;
}
}
return {...defaultOptions, ...parsedOptions};
}
export function parseTargetConfig(value: unknown): CompilerReactTarget {
const parsed = CompilerReactTargetSchema.safeParse(value);
if (parsed.success) {
return parsed.data;
} else {
CompilerError.throwInvalidConfig({
reason: 'Not a valid target',
description: `${fromZodError(parsed.error)}`,
suggestions: null,
loc: null,
});
}
}
function isCompilerFlag(s: string): s is keyof PluginOptions {
return hasOwnProperty(defaultOptions, s);
}

View File

@@ -36,11 +36,13 @@ import {
inferReactivePlaces,
inferReferenceEffects,
inlineImmediatelyInvokedFunctionExpressions,
inferEffectDependencies,
} from '../Inference';
import {
constantPropagation,
deadCodeElimination,
pruneMaybeThrows,
inlineJsxTransform,
} from '../Optimization';
import {instructionReordering} from '../Optimization/InstructionReordering';
import {
@@ -56,7 +58,6 @@ import {
mergeReactiveScopesThatInvalidateTogether,
promoteUsedTemporaries,
propagateEarlyReturns,
propagateScopeDependencies,
pruneHoistedContexts,
pruneNonEscapingScopes,
pruneNonReactiveDependencies,
@@ -102,6 +103,7 @@ import {lowerContextAccess} from '../Optimization/LowerContextAccess';
import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetStateInPassiveEffects';
import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement';
import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
import {outlineJSX} from '../Optimization/OutlineJsx';
export type CompilerPipelineValue =
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -277,6 +279,10 @@ function* runWithEnvironment(
value: hir,
});
if (env.config.enableJsxOutlining) {
outlineJSX(hir);
}
if (env.config.enableFunctionOutlining) {
outlineFunctions(hir, fbtOperands);
yield log({kind: 'hir', name: 'OutlineFunctions', value: hir});
@@ -342,11 +348,22 @@ function* runWithEnvironment(
});
assertTerminalSuccessorsExist(hir);
assertTerminalPredsExist(hir);
if (env.config.enablePropagateDepsInHIR) {
propagateScopeDependenciesHIR(hir);
propagateScopeDependenciesHIR(hir);
yield log({
kind: 'hir',
name: 'PropagateScopeDependenciesHIR',
value: hir,
});
if (env.config.inferEffectDependencies) {
inferEffectDependencies(hir);
}
if (env.config.inlineJsxTransform) {
inlineJsxTransform(hir, env.config.inlineJsxTransform);
yield log({
kind: 'hir',
name: 'PropagateScopeDependenciesHIR',
name: 'inlineJsxTransform',
value: hir,
});
}
@@ -368,15 +385,6 @@ function* runWithEnvironment(
});
assertScopeInstructionsWithinScopes(reactiveFunction);
if (!env.config.enablePropagateDepsInHIR) {
propagateScopeDependencies(reactiveFunction);
yield log({
kind: 'reactive',
name: 'PropagateScopeDependencies',
value: reactiveFunction,
});
}
pruneNonEscapingScopes(reactiveFunction);
yield log({
kind: 'reactive',
@@ -554,3 +562,14 @@ export function log(value: CompilerPipelineValue): CompilerPipelineValue {
}
return value;
}
export function* runPlayground(
func: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>,
config: EnvironmentConfig,
fnType: ReactFunctionType,
): Generator<CompilerPipelineValue, CodegenFunction> {
const ast = yield* run(func, config, fnType, '_c', null, null, null);
return ast;
}

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 =>
@@ -199,7 +199,7 @@ function insertNewOutlinedFunctionNode(
program: NodePath<t.Program>,
originalFn: BabelFn,
compiledFn: CodegenFunction,
): NodePath<t.Function> {
): BabelFn {
switch (originalFn.type) {
case 'FunctionDeclaration': {
return originalFn.insertAfter(
@@ -298,7 +298,6 @@ export function compileProgram(
return;
}
const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c');
const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime';
/*
* Record lint errors and critical errors as depending on Forget's config,
@@ -492,18 +491,11 @@ export function compileProgram(
fn.skip();
ALREADY_COMPILED.add(fn.node);
if (outlined.type !== null) {
CompilerError.throwTodo({
reason: `Implement support for outlining React functions (components/hooks)`,
loc: outlined.fn.loc,
queue.push({
kind: 'outlined',
fn,
fnType: outlined.type,
});
/*
* Above should be as simple as the following, but needs testing:
* queue.push({
* kind: "outlined",
* fn,
* fnType: outlined.type,
* });
*/
}
}
compiledFns.push({
@@ -605,7 +597,7 @@ export function compileProgram(
if (needsMemoCacheFunctionImport) {
updateMemoCacheFunctionImport(
program,
moduleName,
getReactCompilerRuntimeModule(pass.opts),
useMemoCacheIdentifier.name,
);
}
@@ -638,8 +630,12 @@ function shouldSkipCompilation(
}
}
const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime';
if (hasMemoCacheFunctionImport(program, moduleName)) {
if (
hasMemoCacheFunctionImport(
program,
getReactCompilerRuntimeModule(pass.opts),
)
) {
return true;
}
return false;
@@ -1126,3 +1122,24 @@ function checkFunctionReferencedBeforeDeclarationAtTopLevel(
return errors.details.length > 0 ? errors : 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;
}
}

View File

@@ -29,9 +29,9 @@ export function assertConsistentIdentifiers(fn: HIRFunction): void {
const assignments: Set<IdentifierId> = new Set();
for (const [, block] of fn.body.blocks) {
for (const phi of block.phis) {
validate(identifiers, phi.id);
validate(identifiers, phi.place.identifier);
for (const [, operand] of phi.operands) {
validate(identifiers, operand);
validate(identifiers, operand.identifier);
}
}
for (const instr of block.instructions) {

View File

@@ -20,8 +20,9 @@ import {
export function assertValidMutableRanges(fn: HIRFunction): void {
for (const [, block] of fn.body.blocks) {
for (const phi of block.phis) {
visitIdentifier(phi.place.identifier);
for (const [, operand] of phi.operands) {
visitIdentifier(operand);
visitIdentifier(operand.identifier);
}
}
for (const instr of block.instructions) {

View File

@@ -1418,7 +1418,7 @@ function lowerObjectPropertyKey(
name: key.node.value,
};
} else if (property.node.computed && key.isExpression()) {
if (!key.isIdentifier()) {
if (!key.isIdentifier() && !key.isMemberExpression()) {
/*
* NOTE: allowing complex key expressions can trigger a bug where a mutation is made conditional
* see fixture
@@ -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

@@ -14,6 +14,7 @@ import {
ScopeId,
} from './HIR';
import {
fixScopeAndIdentifierRanges,
markInstructionIds,
markPredecessors,
reversePostorderBlocks,
@@ -176,20 +177,7 @@ export function buildReactiveScopeTerminalsHIR(fn: HIRFunction): void {
* Step 5:
* Fix scope and identifier ranges to account for renumbered instructions
*/
for (const [, block] of fn.body.blocks) {
const terminal = block.terminal;
if (terminal.kind === 'scope' || terminal.kind === 'pruned-scope') {
/*
* Scope ranges should always align to start at the 'scope' terminal
* and end at the first instruction of the fallthrough block
*/
const fallthroughBlock = fn.body.blocks.get(terminal.fallthrough)!;
const firstId =
fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id;
terminal.scope.range.start = terminal.id;
terminal.scope.range.end = firstId;
}
}
fixScopeAndIdentifierRanges(fn.body);
}
type TerminalRewriteInfo =

View File

@@ -1,22 +1,33 @@
import {CompilerError} from '../CompilerError';
import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables';
import {Set_intersect, Set_union, getOrInsertDefault} from '../Utils/utils';
import {printDependency} from '../ReactiveScopes/PrintReactiveFunction';
import {
Set_equal,
Set_filter,
Set_intersect,
Set_union,
getOrInsertDefault,
} from '../Utils/utils';
import {
BasicBlock,
BlockId,
DependencyPathEntry,
GeneratedSource,
HIRFunction,
Identifier,
IdentifierId,
Place,
InstructionId,
InstructionValue,
ReactiveScopeDependency,
ScopeId,
} from './HIR';
const DEBUG_PRINT = false;
/**
* Helper function for `PropagateScopeDependencies`.
* Uses control flow graph analysis to determine which `Identifier`s can
* be assumed to be non-null objects, on a per-block basis.
* Helper function for `PropagateScopeDependencies`. Uses control flow graph
* analysis to determine which `Identifier`s can be assumed to be non-null
* objects, on a per-block basis.
*
* Here is an example:
* ```js
@@ -41,15 +52,16 @@ import {
* }
* ```
*
* Note that we currently do NOT account for mutable / declaration range
* when doing the CFG-based traversal, producing results that are technically
* Note that we currently do NOT account for mutable / declaration range when
* doing the CFG-based traversal, producing results that are technically
* incorrect but filtered by PropagateScopeDeps (which only takes dependencies
* on constructed value -- i.e. a scope's dependencies must have mutable ranges
* ending earlier than the scope start).
*
* Take this example, this function will infer x.foo.bar as non-nullable for bb0,
* via the intersection of bb1 & bb2 which in turn comes from bb3. This is technically
* incorrect bb0 is before / during x's mutable range.
* Take this example, this function will infer x.foo.bar as non-nullable for
* bb0, via the intersection of bb1 & bb2 which in turn comes from bb3. This is
* technically incorrect bb0 is before / during x's mutable range.
* ```
* bb0:
* const x = ...;
* if cond then bb1 else bb2
@@ -61,175 +73,22 @@ import {
* goto bb3:
* bb3:
* x.foo.bar
* ```
*
* @param fn
* @param temporaries sidemap of identifier -> baseObject.a.b paths. Does not
* contain optional chains.
* @param hoistableFromOptionals sidemap of optionalBlock -> baseObject?.a
* optional paths for which it's safe to evaluate non-optional loads (see
* CollectOptionalChainDependencies).
* @returns
*/
export function collectHoistablePropertyLoads(
fn: HIRFunction,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
): ReadonlyMap<ScopeId, BlockInfo> {
const nodes = collectPropertyLoadsInBlocks(fn, temporaries);
propagateNonNull(fn, nodes);
const nodesKeyedByScopeId = new Map<ScopeId, BlockInfo>();
for (const [_, block] of fn.body.blocks) {
if (block.terminal.kind === 'scope') {
nodesKeyedByScopeId.set(
block.terminal.scope.id,
nodes.get(block.terminal.block)!,
);
}
}
return nodesKeyedByScopeId;
}
export type BlockInfo = {
block: BasicBlock;
assumedNonNullObjects: ReadonlySet<PropertyLoadNode>;
};
export function getProperty(
object: Place,
propertyName: string,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
): ReactiveScopeDependency {
/*
* (1) Get the base object either from the temporary sidemap (e.g. a LoadLocal)
* or a deep copy of an existing property dependency.
* Example 1:
* $0 = LoadLocal x
* $1 = PropertyLoad $0.y
* getProperty($0, ...) -> resolvedObject = x, resolvedDependency = null
*
* Example 2:
* $0 = LoadLocal x
* $1 = PropertyLoad $0.y
* $2 = PropertyLoad $1.z
* getProperty($1, ...) -> resolvedObject = null, resolvedDependency = x.y
*
* Example 3:
* $0 = Call(...)
* $1 = PropertyLoad $0.y
* getProperty($0, ...) -> resolvedObject = null, resolvedDependency = null
*/
const resolvedDependency = temporaries.get(object.identifier.id);
/**
* (2) Push the last PropertyLoad
* TODO(mofeiZ): understand optional chaining
*/
let property: ReactiveScopeDependency;
if (resolvedDependency == null) {
property = {
identifier: object.identifier,
path: [{property: propertyName, optional: false}],
};
} else {
property = {
identifier: resolvedDependency.identifier,
path: [
...resolvedDependency.path,
{property: propertyName, optional: false},
],
};
}
return property;
}
export function resolveTemporary(
place: Place,
temporaries: ReadonlyMap<IdentifierId, Identifier>,
): Identifier {
return temporaries.get(place.identifier.id) ?? place.identifier;
}
/**
* Tree data structure to dedupe property loads (e.g. a.b.c)
* and make computing sets intersections simpler.
*/
type RootNode = {
properties: Map<string, PropertyLoadNode>;
parent: null;
// Recorded to make later computations simpler
fullPath: ReactiveScopeDependency;
root: Identifier;
};
type PropertyLoadNode =
| {
properties: Map<string, PropertyLoadNode>;
parent: PropertyLoadNode;
fullPath: ReactiveScopeDependency;
}
| RootNode;
class Tree {
roots: Map<Identifier, RootNode> = new Map();
#getOrCreateRoot(identifier: Identifier): PropertyLoadNode {
/**
* Reads from a statically scoped variable are always safe in JS,
* with the exception of TDZ (not addressed by this pass).
*/
let rootNode = this.roots.get(identifier);
if (rootNode === undefined) {
rootNode = {
root: identifier,
properties: new Map(),
fullPath: {
identifier,
path: [],
},
parent: null,
};
this.roots.set(identifier, rootNode);
}
return rootNode;
}
static #getOrCreateProperty(
node: PropertyLoadNode,
property: string,
): PropertyLoadNode {
let child = node.properties.get(property);
if (child == null) {
child = {
properties: new Map(),
parent: node,
fullPath: {
identifier: node.fullPath.identifier,
path: node.fullPath.path.concat([{property, optional: false}]),
},
};
node.properties.set(property, child);
}
return child;
}
getPropertyLoadNode(n: ReactiveScopeDependency): PropertyLoadNode {
CompilerError.invariant(n.path.length > 0, {
reason:
'[CollectHoistablePropertyLoads] Expected property node, found root node',
loc: GeneratedSource,
});
/**
* We add ReactiveScopeDependencies according to instruction ordering,
* so all subpaths of a PropertyLoad should already exist
* (e.g. a.b is added before a.b.c),
*/
let currNode = this.#getOrCreateRoot(n.identifier);
for (let i = 0; i < n.path.length - 1; i++) {
currNode = assertNonNull(currNode.properties.get(n.path[i].property));
}
return Tree.#getOrCreateProperty(currNode, n.path.at(-1)!.property);
}
}
function collectPropertyLoadsInBlocks(
fn: HIRFunction,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
hoistableFromOptionals: ReadonlyMap<BlockId, ReactiveScopeDependency>,
): ReadonlyMap<BlockId, BlockInfo> {
const registry = new PropertyPathRegistry();
/**
* Due to current limitations of mutable range inference, there are edge cases in
* which we infer known-immutable values (e.g. props or hook params) to have a
@@ -238,53 +97,302 @@ function collectPropertyLoadsInBlocks(
* We track known immutable identifiers to reduce regressions (as PropagateScopeDeps
* is being rewritten to HIR).
*/
const knownImmutableIdentifiers = new Set<Identifier>();
const knownImmutableIdentifiers = new Set<IdentifierId>();
if (fn.fnType === 'Component' || fn.fnType === 'Hook') {
for (const p of fn.params) {
if (p.kind === 'Identifier') {
knownImmutableIdentifiers.add(p.identifier);
knownImmutableIdentifiers.add(p.identifier.id);
}
}
}
const tree = new Tree();
return collectHoistablePropertyLoadsImpl(fn, {
temporaries,
knownImmutableIdentifiers,
hoistableFromOptionals,
registry,
nestedFnImmutableContext: null,
});
}
type CollectHoistablePropertyLoadsContext = {
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>;
knownImmutableIdentifiers: ReadonlySet<IdentifierId>;
hoistableFromOptionals: ReadonlyMap<BlockId, ReactiveScopeDependency>;
registry: PropertyPathRegistry;
/**
* (For nested / inner function declarations)
* Context variables (i.e. captured from an outer scope) that are immutable.
* Note that this technically could be merged into `knownImmutableIdentifiers`,
* but are currently kept separate for readability.
*/
nestedFnImmutableContext: ReadonlySet<IdentifierId> | null;
};
function collectHoistablePropertyLoadsImpl(
fn: HIRFunction,
context: CollectHoistablePropertyLoadsContext,
): ReadonlyMap<BlockId, BlockInfo> {
const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn);
const actuallyEvaluatedTemporaries = new Map(
[...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)),
);
const nodes = collectNonNullsInBlocks(fn, {
...context,
temporaries: actuallyEvaluatedTemporaries,
});
propagateNonNull(fn, nodes, context.registry);
if (DEBUG_PRINT) {
console.log('(printing hoistable nodes in blocks)');
for (const [blockId, node] of nodes) {
console.log(
`bb${blockId}: ${[...node.assumedNonNullObjects].map(n => printDependency(n.fullPath)).join(' ')}`,
);
}
}
return nodes;
}
export function keyByScopeId<T>(
fn: HIRFunction,
source: ReadonlyMap<BlockId, T>,
): ReadonlyMap<ScopeId, T> {
const keyedByScopeId = new Map<ScopeId, T>();
for (const [_, block] of fn.body.blocks) {
if (block.terminal.kind === 'scope') {
keyedByScopeId.set(
block.terminal.scope.id,
source.get(block.terminal.block)!,
);
}
}
return keyedByScopeId;
}
export type BlockInfo = {
block: BasicBlock;
assumedNonNullObjects: ReadonlySet<PropertyPathNode>;
};
/**
* PropertyLoadRegistry data structure to dedupe property loads (e.g. a.b.c)
* and make computing sets intersections simpler.
*/
type RootNode = {
properties: Map<string, PropertyPathNode>;
optionalProperties: Map<string, PropertyPathNode>;
parent: null;
// Recorded to make later computations simpler
fullPath: ReactiveScopeDependency;
hasOptional: boolean;
root: IdentifierId;
};
type PropertyPathNode =
| {
properties: Map<string, PropertyPathNode>;
optionalProperties: Map<string, PropertyPathNode>;
parent: PropertyPathNode;
fullPath: ReactiveScopeDependency;
hasOptional: boolean;
}
| RootNode;
class PropertyPathRegistry {
roots: Map<IdentifierId, RootNode> = new Map();
getOrCreateIdentifier(identifier: Identifier): PropertyPathNode {
/**
* Reads from a statically scoped variable are always safe in JS,
* with the exception of TDZ (not addressed by this pass).
*/
let rootNode = this.roots.get(identifier.id);
if (rootNode === undefined) {
rootNode = {
root: identifier.id,
properties: new Map(),
optionalProperties: new Map(),
fullPath: {
identifier,
path: [],
},
hasOptional: false,
parent: null,
};
this.roots.set(identifier.id, rootNode);
}
return rootNode;
}
static getOrCreatePropertyEntry(
parent: PropertyPathNode,
entry: DependencyPathEntry,
): PropertyPathNode {
const map = entry.optional ? parent.optionalProperties : parent.properties;
let child = map.get(entry.property);
if (child == null) {
child = {
properties: new Map(),
optionalProperties: new Map(),
parent: parent,
fullPath: {
identifier: parent.fullPath.identifier,
path: parent.fullPath.path.concat(entry),
},
hasOptional: parent.hasOptional || entry.optional,
};
map.set(entry.property, child);
}
return child;
}
getOrCreateProperty(n: ReactiveScopeDependency): PropertyPathNode {
/**
* We add ReactiveScopeDependencies according to instruction ordering,
* so all subpaths of a PropertyLoad should already exist
* (e.g. a.b is added before a.b.c),
*/
let currNode = this.getOrCreateIdentifier(n.identifier);
if (n.path.length === 0) {
return currNode;
}
for (let i = 0; i < n.path.length - 1; i++) {
currNode = PropertyPathRegistry.getOrCreatePropertyEntry(
currNode,
n.path[i],
);
}
return PropertyPathRegistry.getOrCreatePropertyEntry(
currNode,
n.path.at(-1)!,
);
}
}
function getMaybeNonNullInInstruction(
instr: InstructionValue,
context: CollectHoistablePropertyLoadsContext,
): PropertyPathNode | null {
let path = null;
if (instr.kind === 'PropertyLoad') {
path = context.temporaries.get(instr.object.identifier.id) ?? {
identifier: instr.object.identifier,
path: [],
};
} else if (instr.kind === 'Destructure') {
path = context.temporaries.get(instr.value.identifier.id) ?? null;
} else if (instr.kind === 'ComputedLoad') {
path = context.temporaries.get(instr.object.identifier.id) ?? null;
}
return path != null ? context.registry.getOrCreateProperty(path) : null;
}
function isImmutableAtInstr(
identifier: Identifier,
instr: InstructionId,
context: CollectHoistablePropertyLoadsContext,
): boolean {
if (context.nestedFnImmutableContext != null) {
/**
* Comparing instructions ids across inner-outer function bodies is not valid, as they are numbered
*/
return context.nestedFnImmutableContext.has(identifier.id);
} else {
/**
* Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges
* are not valid with respect to current instruction id numbering.
* We use attached reactive scope ranges as a proxy for mutable range, but this
* is an overestimate as (1) scope ranges merge and align to form valid program
* blocks and (2) passes like MemoizeFbtAndMacroOperands may assign scopes to
* non-mutable identifiers.
*
* See comment in exported function for why we track known immutable identifiers.
*/
const mutableAtInstr =
identifier.mutableRange.end > identifier.mutableRange.start + 1 &&
identifier.scope != null &&
inRange(
{
id: instr,
},
identifier.scope.range,
);
return (
!mutableAtInstr || context.knownImmutableIdentifiers.has(identifier.id)
);
}
}
function collectNonNullsInBlocks(
fn: HIRFunction,
context: CollectHoistablePropertyLoadsContext,
): ReadonlyMap<BlockId, BlockInfo> {
/**
* Known non-null objects such as functional component props can be safely
* read from any block.
*/
const knownNonNullIdentifiers = new Set<PropertyPathNode>();
if (
fn.fnType === 'Component' &&
fn.params.length > 0 &&
fn.params[0].kind === 'Identifier'
) {
const identifier = fn.params[0].identifier;
knownNonNullIdentifiers.add(
context.registry.getOrCreateIdentifier(identifier),
);
}
const nodes = new Map<BlockId, BlockInfo>();
for (const [_, block] of fn.body.blocks) {
const assumedNonNullObjects = new Set<PropertyLoadNode>();
const assumedNonNullObjects = new Set<PropertyPathNode>(
knownNonNullIdentifiers,
);
const maybeOptionalChain = context.hoistableFromOptionals.get(block.id);
if (maybeOptionalChain != null) {
assumedNonNullObjects.add(
context.registry.getOrCreateProperty(maybeOptionalChain),
);
}
for (const instr of block.instructions) {
if (instr.value.kind === 'PropertyLoad') {
const property = getProperty(
instr.value.object,
instr.value.property,
temporaries,
const maybeNonNull = getMaybeNonNullInInstruction(instr.value, context);
if (
maybeNonNull != null &&
isImmutableAtInstr(maybeNonNull.fullPath.identifier, instr.id, context)
) {
assumedNonNullObjects.add(maybeNonNull);
}
if (
(instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod') &&
!fn.env.config.enableTreatFunctionDepsAsConditional
) {
const innerFn = instr.value.loweredFunc;
const innerHoistableMap = collectHoistablePropertyLoadsImpl(
innerFn.func,
{
...context,
nestedFnImmutableContext:
context.nestedFnImmutableContext ??
new Set(
innerFn.func.context
.filter(place =>
isImmutableAtInstr(place.identifier, instr.id, context),
)
.map(place => place.identifier.id),
),
},
);
const propertyNode = tree.getPropertyLoadNode(property);
const object = instr.value.object.identifier;
/**
* Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges
* are not valid with respect to current instruction id numbering.
* We use attached reactive scope ranges as a proxy for mutable range, but this
* is an overestimate as (1) scope ranges merge and align to form valid program
* blocks and (2) passes like MemoizeFbtAndMacroOperands may assign scopes to
* non-mutable identifiers.
*
* See comment at top of function for why we track known immutable identifiers.
*/
const isMutableAtInstr =
object.mutableRange.end > object.mutableRange.start + 1 &&
object.scope != null &&
inRange(instr, object.scope.range);
if (
!isMutableAtInstr ||
knownImmutableIdentifiers.has(propertyNode.fullPath.identifier)
) {
let curr = propertyNode.parent;
while (curr != null) {
assumedNonNullObjects.add(curr);
curr = curr.parent;
}
const innerHoistables = assertNonNull(
innerHoistableMap.get(innerFn.func.body.entry),
);
for (const entry of innerHoistables.assumedNonNullObjects) {
assumedNonNullObjects.add(entry);
}
}
// TODO handle destructuring
}
nodes.set(block.id, {
@@ -298,6 +406,7 @@ function collectPropertyLoadsInBlocks(
function propagateNonNull(
fn: HIRFunction,
nodes: ReadonlyMap<BlockId, BlockInfo>,
registry: PropertyPathRegistry,
): void {
const blockSuccessors = new Map<BlockId, Set<BlockId>>();
const terminalPreds = new Set<BlockId>();
@@ -320,7 +429,6 @@ function propagateNonNull(
nodeId: BlockId,
direction: 'forward' | 'backward',
traversalState: Map<BlockId, 'active' | 'done'>,
nonNullObjectsByBlock: Map<BlockId, ReadonlySet<PropertyLoadNode>>,
): boolean {
/**
* Avoid re-visiting computed or currently active nodes, which can
@@ -351,7 +459,6 @@ function propagateNonNull(
pred,
direction,
traversalState,
nonNullObjectsByBlock,
);
changed ||= neighborChanged;
}
@@ -380,38 +487,43 @@ function propagateNonNull(
const neighborAccesses = Set_intersect(
Array.from(neighbors)
.filter(n => traversalState.get(n) === 'done')
.map(n => assertNonNull(nonNullObjectsByBlock.get(n))),
.map(n => assertNonNull(nodes.get(n)).assumedNonNullObjects),
);
const prevObjects = assertNonNull(nonNullObjectsByBlock.get(nodeId));
const newObjects = Set_union(prevObjects, neighborAccesses);
const prevObjects = assertNonNull(nodes.get(nodeId)).assumedNonNullObjects;
const mergedObjects = Set_union(prevObjects, neighborAccesses);
reduceMaybeOptionalChains(mergedObjects, registry);
nonNullObjectsByBlock.set(nodeId, newObjects);
assertNonNull(nodes.get(nodeId)).assumedNonNullObjects = mergedObjects;
traversalState.set(nodeId, 'done');
changed ||= prevObjects.size !== newObjects.size;
/**
* Note that it's not sufficient to compare set sizes since
* reduceMaybeOptionalChains may replace optional-chain loads with
* unconditional loads. This could in turn change `assumedNonNullObjects` of
* downstream blocks and backedges.
*/
changed ||= !Set_equal(prevObjects, mergedObjects);
return changed;
}
const fromEntry = new Map<BlockId, ReadonlySet<PropertyLoadNode>>();
const fromExit = new Map<BlockId, ReadonlySet<PropertyLoadNode>>();
for (const [blockId, blockInfo] of nodes) {
fromEntry.set(blockId, blockInfo.assumedNonNullObjects);
fromExit.set(blockId, blockInfo.assumedNonNullObjects);
}
const traversalState = new Map<BlockId, 'done' | 'active'>();
const reversedBlocks = [...fn.body.blocks];
reversedBlocks.reverse();
let i = 0;
let changed;
let i = 0;
do {
i++;
CompilerError.invariant(i++ < 100, {
reason:
'[CollectHoistablePropertyLoads] fixed point iteration did not terminate after 100 loops',
loc: GeneratedSource,
});
changed = false;
for (const [blockId] of fn.body.blocks) {
const forwardChanged = recursivelyPropagateNonNull(
blockId,
'forward',
traversalState,
fromEntry,
);
changed ||= forwardChanged;
}
@@ -421,42 +533,14 @@ function propagateNonNull(
blockId,
'backward',
traversalState,
fromExit,
);
changed ||= backwardChanged;
}
traversalState.clear();
} while (changed);
/**
* TODO: validate against meta internal code, then remove in future PR.
* Currently cannot come up with a case that requires fixed-point iteration.
*/
CompilerError.invariant(i <= 2, {
reason: 'require fixed-point iteration',
description: `#iterations = ${i}`,
loc: GeneratedSource,
});
CompilerError.invariant(
fromEntry.size === fromExit.size && fromEntry.size === nodes.size,
{
reason:
'bad sizes after calculating fromEntry + fromExit ' +
`${fromEntry.size} ${fromExit.size} ${nodes.size}`,
loc: GeneratedSource,
},
);
for (const [id, node] of nodes) {
node.assumedNonNullObjects = Set_union(
assertNonNull(fromEntry.get(id)),
assertNonNull(fromExit.get(id)),
);
}
}
function assertNonNull<T extends NonNullable<U>, U>(
export function assertNonNull<T extends NonNullable<U>, U>(
value: T | null | undefined,
source?: string,
): T {
@@ -467,3 +551,77 @@ function assertNonNull<T extends NonNullable<U>, U>(
});
return value;
}
/**
* Any two optional chains with different operations . vs ?. but the same set of
* property strings paths de-duplicates.
*
* Intuitively: given <base>?.b, we know <base> to be either hoistable or not.
* If unconditional reads from <base> are hoistable, we can replace all
* <base>?.PROPERTY_STRING subpaths with <base>.PROPERTY_STRING
*/
function reduceMaybeOptionalChains(
nodes: Set<PropertyPathNode>,
registry: PropertyPathRegistry,
): void {
let optionalChainNodes = Set_filter(nodes, n => n.hasOptional);
if (optionalChainNodes.size === 0) {
return;
}
let changed: boolean;
do {
changed = false;
for (const original of optionalChainNodes) {
let {identifier, path: origPath} = original.fullPath;
let currNode: PropertyPathNode =
registry.getOrCreateIdentifier(identifier);
for (let i = 0; i < origPath.length; i++) {
const entry = origPath[i];
// If the base is known to be non-null, replace with a non-optional load
const nextEntry: DependencyPathEntry =
entry.optional && nodes.has(currNode)
? {property: entry.property, optional: false}
: entry;
currNode = PropertyPathRegistry.getOrCreatePropertyEntry(
currNode,
nextEntry,
);
}
if (currNode !== original) {
changed = true;
optionalChainNodes.delete(original);
optionalChainNodes.add(currNode);
nodes.delete(original);
nodes.add(currNode);
}
}
} while (changed);
}
function collectFunctionExpressionFakeLoads(
fn: HIRFunction,
): Set<IdentifierId> {
const sources = new Map<IdentifierId, IdentifierId>();
const functionExpressionReferences = new Set<IdentifierId>();
for (const [_, block] of fn.body.blocks) {
for (const {lvalue, value} of block.instructions) {
if (
value.kind === 'FunctionExpression' ||
value.kind === 'ObjectMethod'
) {
for (const reference of value.loweredFunc.dependencies) {
let curr: IdentifierId | undefined = reference.identifier.id;
while (curr != null) {
functionExpressionReferences.add(curr);
curr = sources.get(curr);
}
}
} else if (value.kind === 'PropertyLoad') {
sources.set(lvalue.identifier.id, value.object.identifier.id);
}
}
}
return functionExpressionReferences;
}

View File

@@ -0,0 +1,402 @@
import {CompilerError} from '..';
import {assertNonNull} from './CollectHoistablePropertyLoads';
import {
BlockId,
BasicBlock,
IdentifierId,
ReactiveScopeDependency,
BranchTerminal,
TInstruction,
PropertyLoad,
StoreLocal,
GotoVariant,
TBasicBlock,
OptionalTerminal,
HIRFunction,
DependencyPathEntry,
Instruction,
Terminal,
} from './HIR';
import {printIdentifier} from './PrintHIR';
export function collectOptionalChainSidemap(
fn: HIRFunction,
): OptionalChainSidemap {
const context: OptionalTraversalContext = {
currFn: fn,
blocks: fn.body.blocks,
seenOptionals: new Set(),
processedInstrsInOptional: new Set(),
temporariesReadInOptional: new Map(),
hoistableObjects: new Map(),
};
traverseFunction(fn, context);
return {
temporariesReadInOptional: context.temporariesReadInOptional,
processedInstrsInOptional: context.processedInstrsInOptional,
hoistableObjects: context.hoistableObjects,
};
}
export type OptionalChainSidemap = {
/**
* Stores the correct property mapping (e.g. `a?.b` instead of `a.b`) for
* dependency calculation. Note that we currently do not store anything on
* outer phi nodes.
*/
temporariesReadInOptional: ReadonlyMap<IdentifierId, ReactiveScopeDependency>;
/**
* Records instructions (PropertyLoads, StoreLocals, and test terminals)
* processed in this pass. When extracting dependencies in
* PropagateScopeDependencies, these instructions are skipped.
*
* E.g. given a?.b
* ```
* bb0
* $0 = LoadLocal 'a'
* test $0 then=bb1 <- Avoid adding dependencies from these instructions, as
* bb1 the sidemap produced by readOptionalBlock already maps
* $1 = PropertyLoad $0.'b' <- $1 and $2 back to a?.b. Instead, we want to add a?.b
* StoreLocal $2 = $1 <- as a dependency when $1 or $2 are later used in either
* - an unhoistable expression within an outer optional
* block e.g. MethodCall
* - a phi node (if the entire optional value is hoistable)
* ```
*
* Note that mapping blockIds to their evaluated dependency path does not
* work, since values produced by inner optional chains may be referenced in
* outer ones
* ```
* a?.b.c()
* ->
* bb0
* $0 = LoadLocal 'a'
* test $0 then=bb1
* bb1
* $1 = PropertyLoad $0.'b'
* StoreLocal $2 = $1
* goto bb2
* bb2
* test $2 then=bb3
* bb3:
* $3 = PropertyLoad $2.'c'
* StoreLocal $4 = $3
* goto bb4
* bb4
* test $4 then=bb5
* bb5:
* $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4!
* ```
*
* Also note that InstructionIds are not unique across inner functions.
*/
processedInstrsInOptional: ReadonlySet<Instruction | Terminal>;
/**
* Records optional chains for which we can safely evaluate non-optional
* PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at
* the optional terminal in bb1.
* ```js
* bb1:
* ...
* Optional optional=false test=bb2 fallth=...
* bb2:
* Optional optional=true test=bb3 fallth=...
* ...
* ```
*/
hoistableObjects: ReadonlyMap<BlockId, ReactiveScopeDependency>;
};
type OptionalTraversalContext = {
currFn: HIRFunction;
blocks: ReadonlyMap<BlockId, BasicBlock>;
// Track optional blocks to avoid outer calls into nested optionals
seenOptionals: Set<BlockId>;
processedInstrsInOptional: Set<Instruction | Terminal>;
temporariesReadInOptional: Map<IdentifierId, ReactiveScopeDependency>;
hoistableObjects: Map<BlockId, ReactiveScopeDependency>;
};
function traverseFunction(
fn: HIRFunction,
context: OptionalTraversalContext,
): void {
for (const [_, block] of fn.body.blocks) {
for (const instr of block.instructions) {
if (
instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod'
) {
traverseFunction(instr.value.loweredFunc.func, {
...context,
currFn: instr.value.loweredFunc.func,
blocks: instr.value.loweredFunc.func.body.blocks,
});
}
}
if (
block.terminal.kind === 'optional' &&
!context.seenOptionals.has(block.id)
) {
traverseOptionalBlock(
block as TBasicBlock<OptionalTerminal>,
context,
null,
);
}
}
}
/**
* Match the consequent and alternate blocks of an optional.
* @returns propertyload computed by the consequent block, or null if the
* consequent block is not a simple PropertyLoad.
*/
function matchOptionalTestBlock(
terminal: BranchTerminal,
blocks: ReadonlyMap<BlockId, BasicBlock>,
): {
consequentId: IdentifierId;
property: string;
propertyId: IdentifierId;
storeLocalInstr: Instruction;
consequentGoto: BlockId;
} | null {
const consequentBlock = assertNonNull(blocks.get(terminal.consequent));
if (
consequentBlock.instructions.length === 2 &&
consequentBlock.instructions[0].value.kind === 'PropertyLoad' &&
consequentBlock.instructions[1].value.kind === 'StoreLocal'
) {
const propertyLoad: TInstruction<PropertyLoad> = consequentBlock
.instructions[0] as TInstruction<PropertyLoad>;
const storeLocal: StoreLocal = consequentBlock.instructions[1].value;
const storeLocalInstr = consequentBlock.instructions[1];
CompilerError.invariant(
propertyLoad.value.object.identifier.id === terminal.test.identifier.id,
{
reason:
'[OptionalChainDeps] Inconsistent optional chaining property load',
description: `Test=${printIdentifier(terminal.test.identifier)} PropertyLoad base=${printIdentifier(propertyLoad.value.object.identifier)}`,
loc: propertyLoad.loc,
},
);
CompilerError.invariant(
storeLocal.value.identifier.id === propertyLoad.lvalue.identifier.id,
{
reason: '[OptionalChainDeps] Unexpected storeLocal',
loc: propertyLoad.loc,
},
);
if (
consequentBlock.terminal.kind !== 'goto' ||
consequentBlock.terminal.variant !== GotoVariant.Break
) {
return null;
}
const alternate = assertNonNull(blocks.get(terminal.alternate));
CompilerError.invariant(
alternate.instructions.length === 2 &&
alternate.instructions[0].value.kind === 'Primitive' &&
alternate.instructions[1].value.kind === 'StoreLocal',
{
reason: 'Unexpected alternate structure',
loc: terminal.loc,
},
);
return {
consequentId: storeLocal.lvalue.place.identifier.id,
property: propertyLoad.value.property,
propertyId: propertyLoad.lvalue.identifier.id,
storeLocalInstr,
consequentGoto: consequentBlock.terminal.block,
};
}
return null;
}
/**
* Traverse into the optional block and all transitively referenced blocks to
* collect sidemaps of optional chain dependencies.
*
* @returns the IdentifierId representing the optional block if the block and
* all transitively referenced optional blocks precisely represent a chain of
* property loads. If any part of the optional chain is not hoistable, returns
* null.
*/
function traverseOptionalBlock(
optional: TBasicBlock<OptionalTerminal>,
context: OptionalTraversalContext,
outerAlternate: BlockId | null,
): IdentifierId | null {
context.seenOptionals.add(optional.id);
const maybeTest = context.blocks.get(optional.terminal.test)!;
let test: BranchTerminal;
let baseObject: ReactiveScopeDependency;
if (maybeTest.terminal.kind === 'branch') {
CompilerError.invariant(optional.terminal.optional, {
reason: '[OptionalChainDeps] Expect base case to be always optional',
loc: optional.terminal.loc,
});
/**
* Optional base expressions are currently within value blocks which cannot
* be interrupted by scope boundaries. As such, the only dependencies we can
* hoist out of optional chains are property load chains with no intervening
* instructions.
*
* Ideally, we would be able to flatten base instructions out of optional
* blocks, but this would require changes to HIR.
*
* For now, only match base expressions that are straightforward
* PropertyLoad chains
*/
if (
maybeTest.instructions.length === 0 ||
maybeTest.instructions[0].value.kind !== 'LoadLocal'
) {
return null;
}
const path: Array<DependencyPathEntry> = [];
for (let i = 1; i < maybeTest.instructions.length; i++) {
const instrVal = maybeTest.instructions[i].value;
const prevInstr = maybeTest.instructions[i - 1];
if (
instrVal.kind === 'PropertyLoad' &&
instrVal.object.identifier.id === prevInstr.lvalue.identifier.id
) {
path.push({property: instrVal.property, optional: false});
} else {
return null;
}
}
CompilerError.invariant(
maybeTest.terminal.test.identifier.id ===
maybeTest.instructions.at(-1)!.lvalue.identifier.id,
{
reason: '[OptionalChainDeps] Unexpected test expression',
loc: maybeTest.terminal.loc,
},
);
baseObject = {
identifier: maybeTest.instructions[0].value.place.identifier,
path,
};
test = maybeTest.terminal;
} else if (maybeTest.terminal.kind === 'optional') {
/**
* This is either
* - <inner_optional>?.property (optional=true)
* - <inner_optional>.property (optional=false)
* - <inner_optional> <other operation>
* - a optional base block with a separate nested optional-chain (e.g. a(c?.d)?.d)
*/
const testBlock = context.blocks.get(maybeTest.terminal.fallthrough)!;
if (testBlock!.terminal.kind !== 'branch') {
/**
* Fallthrough of the inner optional should be a block with no
* instructions, terminating with Test($<temporary written to from
* StoreLocal>)
*/
CompilerError.throwTodo({
reason: `Unexpected terminal kind \`${testBlock.terminal.kind}\` for optional fallthrough block`,
loc: maybeTest.terminal.loc,
});
}
/**
* Recurse into inner optional blocks to collect inner optional-chain
* expressions, regardless of whether we can match the outer one to a
* PropertyLoad.
*/
const innerOptional = traverseOptionalBlock(
maybeTest as TBasicBlock<OptionalTerminal>,
context,
testBlock.terminal.alternate,
);
if (innerOptional == null) {
return null;
}
/**
* Check that the inner optional is part of the same optional-chain as the
* outer one. This is not guaranteed, e.g. given a(c?.d)?.d
* ```
* bb0:
* Optional test=bb1
* bb1:
* $0 = LoadLocal a <-- part 1 of the outer optional-chaining base
* Optional test=bb2 fallth=bb5 <-- start of optional chain for c?.d
* bb2:
* ... (optional chain for c?.d)
* ...
* bb5:
* $1 = phi(c.d, undefined) <-- part 2 (continuation) of the outer optional-base
* $2 = Call $0($1)
* Branch $2 ...
* ```
*/
if (testBlock.terminal.test.identifier.id !== innerOptional) {
return null;
}
if (!optional.terminal.optional) {
/**
* If this is an non-optional load participating in an optional chain
* (e.g. loading the `c` property in `a?.b.c`), record that PropertyLoads
* from the inner optional value are hoistable.
*/
context.hoistableObjects.set(
optional.id,
assertNonNull(context.temporariesReadInOptional.get(innerOptional)),
);
}
baseObject = assertNonNull(
context.temporariesReadInOptional.get(innerOptional),
);
test = testBlock.terminal;
} else {
return null;
}
if (test.alternate === outerAlternate) {
CompilerError.invariant(optional.instructions.length === 0, {
reason:
'[OptionalChainDeps] Unexpected instructions an inner optional block. ' +
'This indicates that the compiler may be incorrectly concatenating two unrelated optional chains',
loc: optional.terminal.loc,
});
}
const matchConsequentResult = matchOptionalTestBlock(test, context.blocks);
if (!matchConsequentResult) {
// Optional chain consequent is not hoistable e.g. a?.[computed()]
return null;
}
CompilerError.invariant(
matchConsequentResult.consequentGoto === optional.terminal.fallthrough,
{
reason: '[OptionalChainDeps] Unexpected optional goto-fallthrough',
description: `${matchConsequentResult.consequentGoto} != ${optional.terminal.fallthrough}`,
loc: optional.terminal.loc,
},
);
const load = {
identifier: baseObject.identifier,
path: [
...baseObject.path,
{
property: matchConsequentResult.property,
optional: optional.terminal.optional,
},
],
};
context.processedInstrsInOptional.add(matchConsequentResult.storeLocalInstr);
context.processedInstrsInOptional.add(test);
context.temporariesReadInOptional.set(
matchConsequentResult.consequentId,
load,
);
context.temporariesReadInOptional.set(matchConsequentResult.propertyId, load);
return matchConsequentResult.consequentId;
}

View File

@@ -6,93 +6,173 @@
*/
import {CompilerError} from '../CompilerError';
import {GeneratedSource, Identifier, ReactiveScopeDependency} from '../HIR';
import {
DependencyPathEntry,
GeneratedSource,
Identifier,
ReactiveScopeDependency,
} from '../HIR';
import {printIdentifier} from '../HIR/PrintHIR';
import {ReactiveScopePropertyDependency} from '../ReactiveScopes/DeriveMinimalDependencies';
const ENABLE_DEBUG_INVARIANTS = true;
/**
* Simpler fork of DeriveMinimalDependencies, see PropagateScopeDependenciesHIR
* for detailed explanation.
*/
export class ReactiveScopeDependencyTreeHIR {
#roots: Map<Identifier, DependencyNode> = new Map();
/**
* Paths from which we can hoist PropertyLoads. If an `identifier`,
* `identifier.path`, or `identifier?.path` is in this map, it is safe to
* evaluate (non-optional) PropertyLoads from.
*/
#hoistableObjects: Map<Identifier, HoistableNode> = new Map();
#deps: Map<Identifier, DependencyNode> = new Map();
#getOrCreateRoot(identifier: Identifier, isNonNull: boolean): DependencyNode {
/**
* @param hoistableObjects a set of paths from which we can safely evaluate
* PropertyLoads. Note that we expect these to not contain duplicates (e.g.
* both `a?.b` and `a.b`) only because CollectHoistablePropertyLoads merges
* duplicates when traversing the CFG.
*/
constructor(hoistableObjects: Iterable<ReactiveScopeDependency>) {
for (const {path, identifier} of hoistableObjects) {
let currNode = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot(
identifier,
this.#hoistableObjects,
path.length > 0 && path[0].optional ? 'Optional' : 'NonNull',
);
for (let i = 0; i < path.length; i++) {
const prevAccessType = currNode.properties.get(
path[i].property,
)?.accessType;
const accessType =
i + 1 < path.length && path[i + 1].optional ? 'Optional' : 'NonNull';
CompilerError.invariant(
prevAccessType == null || prevAccessType === accessType,
{
reason: 'Conflicting access types',
loc: GeneratedSource,
},
);
let nextNode = currNode.properties.get(path[i].property);
if (nextNode == null) {
nextNode = {
properties: new Map(),
accessType,
};
currNode.properties.set(path[i].property, nextNode);
}
currNode = nextNode;
}
}
}
static #getOrCreateRoot<T extends string>(
identifier: Identifier,
roots: Map<Identifier, TreeNode<T>>,
defaultAccessType: T,
): TreeNode<T> {
// roots can always be accessed unconditionally in JS
let rootNode = this.#roots.get(identifier);
let rootNode = roots.get(identifier);
if (rootNode === undefined) {
rootNode = {
properties: new Map(),
accessType: isNonNull
? PropertyAccessType.NonNullAccess
: PropertyAccessType.Access,
accessType: defaultAccessType,
};
this.#roots.set(identifier, rootNode);
roots.set(identifier, rootNode);
}
return rootNode;
}
/**
* Join a dependency with `#hoistableObjects` to record the hoistable
* dependency. This effectively truncates @param dep to its maximal
* safe-to-evaluate subpath
*/
addDependency(dep: ReactiveScopePropertyDependency): void {
const {path} = dep;
let currNode = this.#getOrCreateRoot(dep.identifier, false);
const accessType = PropertyAccessType.Access;
currNode.accessType = merge(currNode.accessType, accessType);
for (const property of path) {
// all properties read 'on the way' to a dependency are marked as 'access'
let currChild = getOrMakeProperty(currNode, property.property);
currChild.accessType = merge(currChild.accessType, accessType);
currNode = currChild;
}
/*
* If this property does not have a conditional path (i.e. a.b.c), the
* final property node should be marked as an conditional/unconditional
* `dependency` as based on control flow.
const {identifier, path} = dep;
let depCursor = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot(
identifier,
this.#deps,
PropertyAccessType.UnconditionalAccess,
);
/**
* hoistableCursor is null if depCursor is not an object we can hoist
* property reads from otherwise, it represents the same node in the
* hoistable / cfg-informed tree
*/
currNode.accessType = merge(
currNode.accessType,
PropertyAccessType.Dependency,
let hoistableCursor: HoistableNode | undefined =
this.#hoistableObjects.get(identifier);
// All properties read 'on the way' to a dependency are marked as 'access'
for (const entry of path) {
let nextHoistableCursor: HoistableNode | undefined;
let nextDepCursor: DependencyNode;
if (entry.optional) {
/**
* No need to check the access type since we can match both optional or non-optionals
* in the hoistable
* e.g. a?.b<rest> is hoistable if a.b<rest> is hoistable
*/
if (hoistableCursor != null) {
nextHoistableCursor = hoistableCursor?.properties.get(entry.property);
}
let accessType;
if (
hoistableCursor != null &&
hoistableCursor.accessType === 'NonNull'
) {
/**
* For an optional chain dep `a?.b`: if the hoistable tree only
* contains `a`, we can keep either `a?.b` or 'a.b' as a dependency.
* (note that we currently do the latter for perf)
*/
accessType = PropertyAccessType.UnconditionalAccess;
} else {
/**
* Given that it's safe to evaluate `depCursor` and optional load
* never throws, it's also safe to evaluate `depCursor?.entry`
*/
accessType = PropertyAccessType.OptionalAccess;
}
nextDepCursor = makeOrMergeProperty(
depCursor,
entry.property,
accessType,
);
} else if (
hoistableCursor != null &&
hoistableCursor.accessType === 'NonNull'
) {
nextHoistableCursor = hoistableCursor.properties.get(entry.property);
nextDepCursor = makeOrMergeProperty(
depCursor,
entry.property,
PropertyAccessType.UnconditionalAccess,
);
} else {
/**
* Break to truncate the dependency on its first non-optional entry that PropertyLoads are not hoistable from
*/
break;
}
depCursor = nextDepCursor;
hoistableCursor = nextHoistableCursor;
}
// mark the final node as a dependency
depCursor.accessType = merge(
depCursor.accessType,
PropertyAccessType.OptionalDependency,
);
}
markNodesNonNull(dep: ReactiveScopePropertyDependency): void {
const accessType = PropertyAccessType.NonNullAccess;
let currNode = this.#roots.get(dep.identifier);
let cursor = 0;
while (currNode != null && cursor < dep.path.length) {
currNode.accessType = merge(currNode.accessType, accessType);
currNode = currNode.properties.get(dep.path[cursor++].property);
}
if (currNode != null) {
currNode.accessType = merge(currNode.accessType, accessType);
}
}
/**
* Derive a set of minimal dependencies that are safe to
* access unconditionally (with respect to nullthrows behavior)
*/
deriveMinimalDependencies(): Set<ReactiveScopeDependency> {
const results = new Set<ReactiveScopeDependency>();
for (const [rootId, rootNode] of this.#roots.entries()) {
if (ENABLE_DEBUG_INVARIANTS) {
assertWellFormedTree(rootNode);
}
const deps = deriveMinimalDependenciesInSubtree(rootNode, []);
for (const dep of deps) {
results.add({
identifier: rootId,
path: dep.path.map(s => ({property: s, optional: false})),
});
}
for (const [rootId, rootNode] of this.#deps.entries()) {
collectMinimalDependenciesInSubtree(rootNode, rootId, [], results);
}
return results;
@@ -106,7 +186,7 @@ export class ReactiveScopeDependencyTreeHIR {
printDeps(includeAccesses: boolean): string {
let res: Array<Array<string>> = [];
for (const [rootId, rootNode] of this.#roots.entries()) {
for (const [rootId, rootNode] of this.#deps.entries()) {
const rootResults = printSubtree(rootNode, includeAccesses).map(
result => `${printIdentifier(rootId)}.${result}`,
);
@@ -114,31 +194,64 @@ export class ReactiveScopeDependencyTreeHIR {
}
return res.flat().join('\n');
}
static debug<T extends string>(roots: Map<Identifier, TreeNode<T>>): string {
const buf: Array<string> = [`tree() [`];
for (const [rootId, rootNode] of roots) {
buf.push(`${printIdentifier(rootId)} (${rootNode.accessType}):`);
this.#debugImpl(buf, rootNode, 1);
}
buf.push(']');
return buf.length > 2 ? buf.join('\n') : buf.join('');
}
static #debugImpl<T extends string>(
buf: Array<string>,
node: TreeNode<T>,
depth: number = 0,
): void {
for (const [property, childNode] of node.properties) {
buf.push(`${' '.repeat(depth)}.${property} (${childNode.accessType}):`);
this.#debugImpl(buf, childNode, depth + 1);
}
}
}
enum PropertyAccessType {
Access = 'Access',
NonNullAccess = 'NonNullAccess',
Dependency = 'Dependency',
NonNullDependency = 'NonNullDependency',
}
const MIN_ACCESS_TYPE = PropertyAccessType.Access;
/**
* "NonNull" means that PropertyReads from a node are side-effect free,
* as the node is (1) immutable and (2) has unconditional propertyloads
* somewhere in the cfg.
/*
* Enum representing the access type of single property on a parent object.
* We distinguish on two independent axes:
* Optional / Unconditional:
* - whether this property is an optional load (within an optional chain)
* Access / Dependency:
* - Access: this property is read on the path of a dependency. We do not
* need to track change variables for accessed properties. Tracking accesses
* helps Forget do more granular dependency tracking.
* - Dependency: this property is read as a dependency and we must track changes
* to it for correctness.
* ```javascript
* // props.a is a dependency here and must be tracked
* deps: {props.a, props.a.b} ---> minimalDeps: {props.a}
* // props.a is just an access here and does not need to be tracked
* deps: {props.a.b} ---> minimalDeps: {props.a.b}
* ```
*/
function isNonNull(access: PropertyAccessType): boolean {
enum PropertyAccessType {
OptionalAccess = 'OptionalAccess',
UnconditionalAccess = 'UnconditionalAccess',
OptionalDependency = 'OptionalDependency',
UnconditionalDependency = 'UnconditionalDependency',
}
function isOptional(access: PropertyAccessType): boolean {
return (
access === PropertyAccessType.NonNullAccess ||
access === PropertyAccessType.NonNullDependency
access === PropertyAccessType.OptionalAccess ||
access === PropertyAccessType.OptionalDependency
);
}
function isDependency(access: PropertyAccessType): boolean {
return (
access === PropertyAccessType.Dependency ||
access === PropertyAccessType.NonNullDependency
access === PropertyAccessType.OptionalDependency ||
access === PropertyAccessType.UnconditionalDependency
);
}
@@ -146,92 +259,70 @@ function merge(
access1: PropertyAccessType,
access2: PropertyAccessType,
): PropertyAccessType {
const resultisNonNull = isNonNull(access1) || isNonNull(access2);
const resultIsUnconditional = !(isOptional(access1) && isOptional(access2));
const resultIsDependency = isDependency(access1) || isDependency(access2);
/*
* Straightforward merge.
* This can be represented as bitwise OR, but is written out for readability
*
* Observe that `NonNullAccess | Dependency` produces an
* Observe that `UnconditionalAccess | ConditionalDependency` produces an
* unconditionally accessed conditional dependency. We currently use these
* as we use unconditional dependencies. (i.e. to codegen change variables)
*/
if (resultisNonNull) {
if (resultIsUnconditional) {
if (resultIsDependency) {
return PropertyAccessType.NonNullDependency;
return PropertyAccessType.UnconditionalDependency;
} else {
return PropertyAccessType.NonNullAccess;
return PropertyAccessType.UnconditionalAccess;
}
} else {
// result is optional
if (resultIsDependency) {
return PropertyAccessType.Dependency;
return PropertyAccessType.OptionalDependency;
} else {
return PropertyAccessType.Access;
return PropertyAccessType.OptionalAccess;
}
}
}
type DependencyNode = {
properties: Map<string, DependencyNode>;
accessType: PropertyAccessType;
type TreeNode<T extends string> = {
properties: Map<string, TreeNode<T>>;
accessType: T;
};
type HoistableNode = TreeNode<'Optional' | 'NonNull'>;
type DependencyNode = TreeNode<PropertyAccessType>;
type ReduceResultNode = {
path: Array<string>;
};
function assertWellFormedTree(node: DependencyNode): void {
let nonNullInChildren = false;
for (const childNode of node.properties.values()) {
assertWellFormedTree(childNode);
nonNullInChildren ||= isNonNull(childNode.accessType);
}
if (nonNullInChildren) {
CompilerError.invariant(isNonNull(node.accessType), {
reason:
'[DeriveMinimialDependencies] Not well formed tree, unexpected non-null node',
description: node.accessType,
loc: GeneratedSource,
});
}
}
function deriveMinimalDependenciesInSubtree(
/**
* TODO: this is directly pasted from DeriveMinimalDependencies. Since we no
* longer have conditionally accessed nodes, we can simplify
*
* Recursively calculates minimal dependencies in a subtree.
* @param node DependencyNode representing a dependency subtree.
* @returns a minimal list of dependencies in this subtree.
*/
function collectMinimalDependenciesInSubtree(
node: DependencyNode,
path: Array<string>,
): Array<ReduceResultNode> {
rootIdentifier: Identifier,
path: Array<DependencyPathEntry>,
results: Set<ReactiveScopeDependency>,
): void {
if (isDependency(node.accessType)) {
/**
* If this node is a dependency, we truncate the subtree
* and return this node. e.g. deps=[`obj.a`, `obj.a.b`]
* reduces to deps=[`obj.a`]
*/
return [{path}];
results.add({identifier: rootIdentifier, path});
} else {
if (isNonNull(node.accessType)) {
/*
* Only recurse into subtree dependencies if this node
* is known to be non-null.
*/
const result: Array<ReduceResultNode> = [];
for (const [childName, childNode] of node.properties) {
result.push(
...deriveMinimalDependenciesInSubtree(childNode, [
...path,
childName,
]),
);
}
return result;
} else {
/*
* This only occurs when this subtree contains a dependency,
* but this node is potentially nullish. As we currently
* don't record optional property paths as scope dependencies,
* we truncate and record this node as a dependency.
*/
return [{path}];
for (const [childName, childNode] of node.properties) {
collectMinimalDependenciesInSubtree(
childNode,
rootIdentifier,
[
...path,
{
property: childName,
optional: isOptional(childNode.accessType),
},
],
results,
);
}
}
}
@@ -251,17 +342,20 @@ function printSubtree(
return results;
}
function getOrMakeProperty(
function makeOrMergeProperty(
node: DependencyNode,
property: string,
accessType: PropertyAccessType,
): DependencyNode {
let child = node.properties.get(property);
if (child == null) {
child = {
properties: new Map(),
accessType: MIN_ACCESS_TYPE,
accessType,
};
node.properties.set(property, child);
} else {
child.accessType = merge(child.accessType, accessType);
}
return child;
}

View File

@@ -16,7 +16,7 @@ import {
DEFAULT_SHAPES,
Global,
GlobalRegistry,
installReAnimatedTypes,
getReanimatedModuleType,
installTypeConfig,
} from './Globals';
import {
@@ -50,6 +50,14 @@ import {
import {Scope as BabelScope} from '@babel/traverse';
import {TypeSchema} from './TypeSchema';
export const ReactElementSymbolSchema = z.object({
elementSymbol: z.union([
z.literal('react.element'),
z.literal('react.transitional.element'),
]),
globalDevVar: z.string(),
});
export const ExternalFunctionSchema = z.object({
// Source for the imported module that exports the `importSpecifierName` functions
source: z.string(),
@@ -61,8 +69,8 @@ export const ExternalFunctionSchema = z.object({
export const InstrumentationSchema = z
.object({
fn: ExternalFunctionSchema,
gating: ExternalFunctionSchema.nullish(),
globalGating: z.string().nullish(),
gating: ExternalFunctionSchema.nullable(),
globalGating: z.string().nullable(),
})
.refine(
opts => opts.gating != null || opts.globalGating != null,
@@ -139,7 +147,7 @@ export type Hook = z.infer<typeof HookSchema>;
*/
const EnvironmentConfigSchema = z.object({
customHooks: z.map(z.string(), HookSchema).optional().default(new Map()),
customHooks: z.map(z.string(), HookSchema).default(new Map()),
/**
* A function that, given the name of a module, can optionally return a description
@@ -223,7 +231,7 @@ const EnvironmentConfigSchema = z.object({
*/
enableUseTypeAnnotations: z.boolean().default(false),
enablePropagateDepsInHIR: z.boolean().default(false),
enableFunctionDependencyRewrite: z.boolean().default(true),
/**
* Enables inference of optional dependency chains. Without this flag
@@ -233,6 +241,51 @@ const EnvironmentConfigSchema = z.object({
*/
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
* Currently a prod-only optimization, requiring Fast JSX dependencies
*
* The symbol configuration is set for backwards compatability with pre-React 19 transforms
*/
inlineJsxTransform: ReactElementSymbolSchema.nullable().default(null),
/*
* Enable validation of hooks to partially check that the component honors the rules of hooks.
* When disabled, the component is assumed to follow the rules (though the Babel plugin looks
@@ -322,9 +375,9 @@ const EnvironmentConfigSchema = z.object({
* }
* }
*/
enableEmitFreeze: ExternalFunctionSchema.nullish(),
enableEmitFreeze: ExternalFunctionSchema.nullable().default(null),
enableEmitHookGuards: ExternalFunctionSchema.nullish(),
enableEmitHookGuards: ExternalFunctionSchema.nullable().default(null),
/**
* Enable instruction reordering. See InstructionReordering.ts for the details
@@ -338,6 +391,54 @@ const EnvironmentConfigSchema = z.object({
*/
enableFunctionOutlining: z.boolean().default(true),
/**
* If enabled, this will outline nested JSX into a separate component.
*
* This will enable the compiler to memoize the separate component, giving us
* the same behavior as compiling _within_ the callback.
*
* ```
* function Component(countries, onDelete) {
* const name = useFoo();
* return countries.map(() => {
* return (
* <Foo>
* <Bar>{name}</Bar>
* <Button onclick={onDelete}>delete</Button>
* </Foo>
* );
* });
* }
* ```
*
* will be transpiled to:
*
* ```
* function Component(countries, onDelete) {
* const name = useFoo();
* return countries.map(() => {
* return (
* <Temp name={name} onDelete={onDelete} />
* );
* });
* }
*
* function Temp({name, onDelete}) {
* return (
* <Foo>
* <Bar>{name}</Bar>
* <Button onclick={onDelete}>delete</Button>
* </Foo>
* );
* }
*
* Both, `Component` and `Temp` will then be memoized by the compiler.
*
* With this change, when `countries` is updated by adding one single value,
* only the newly added value is re-rendered and not the entire list.
*/
enableJsxOutlining: z.boolean().default(false),
/*
* Enables instrumentation codegen. This emits a dev-mode only call to an
* instrumentation function, for components and hooks that Forget compiles.
@@ -360,7 +461,7 @@ const EnvironmentConfigSchema = z.object({
* }
*
*/
enableEmitInstrumentForget: InstrumentationSchema.nullish(),
enableEmitInstrumentForget: InstrumentationSchema.nullable().default(null),
// Enable validation of mutable ranges
assertValidMutableRanges: z.boolean().default(false),
@@ -399,8 +500,6 @@ const EnvironmentConfigSchema = z.object({
*/
throwUnknownException__testonly: z.boolean().default(false),
enableSharedRuntime__testonly: z.boolean().default(false),
/**
* Enables deps of a function epxression to be treated as conditional. This
* makes sure we don't load a dep when it's a property (to check if it has
@@ -438,7 +537,8 @@ const EnvironmentConfigSchema = z.object({
* computed one. This detects cases where rules of react violations may cause the
* compiled code to behave differently than the original.
*/
enableChangeDetectionForDebugging: ExternalFunctionSchema.nullish(),
enableChangeDetectionForDebugging:
ExternalFunctionSchema.nullable().default(null),
/**
* The react native re-animated library uses custom Babel transforms that
@@ -478,7 +578,7 @@ const EnvironmentConfigSchema = z.object({
*
* Here the variables `ref` and `myRef` will be typed as Refs.
*/
enableTreatRefLikeIdentifiersAsRefs: z.boolean().nullable().default(false),
enableTreatRefLikeIdentifiersAsRefs: z.boolean().default(false),
/*
* If specified a value, the compiler lowers any calls to `useContext` to use
@@ -500,12 +600,80 @@ const EnvironmentConfigSchema = z.object({
* const {foo, bar} = useCompiledContext(MyContext, (c) => [c.foo, c.bar]);
* ```
*/
lowerContextAccess: ExternalFunctionSchema.nullish(),
lowerContextAccess: ExternalFunctionSchema.nullable().default(null),
});
export type EnvironmentConfig = z.infer<typeof EnvironmentConfigSchema>;
export function parseConfigPragma(pragma: string): EnvironmentConfig {
/**
* For test fixtures and playground only.
*
* Pragmas are straightforward to parse for boolean options (`:true` and
* `:false`). These are 'enabled' config values for non-boolean configs (i.e.
* what is used when parsing `:true`).
*/
const testComplexConfigDefaults: PartialEnvironmentConfig = {
validateNoCapitalizedCalls: [],
enableChangeDetectionForDebugging: {
source: 'react-compiler-runtime',
importSpecifierName: '$structuralCheck',
},
enableEmitFreeze: {
source: 'react-compiler-runtime',
importSpecifierName: 'makeReadOnly',
},
enableEmitInstrumentForget: {
fn: {
source: 'react-compiler-runtime',
importSpecifierName: 'useRenderCounter',
},
gating: {
source: 'react-compiler-runtime',
importSpecifierName: 'shouldInstrument',
},
globalGating: '__DEV__',
},
enableEmitHookGuards: {
source: 'react-compiler-runtime',
importSpecifierName: '$dispatcherGuard',
},
inlineJsxTransform: {
elementSymbol: 'react.transitional.element',
globalDevVar: 'DEV',
},
lowerContextAccess: {
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,
},
],
};
/**
* For snap test fixtures and playground only.
*/
export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig {
const maybeConfig: any = {};
// Get the defaults to programmatically check for boolean properties
const defaultConfig = EnvironmentConfigSchema.parse({});
@@ -515,21 +683,12 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig {
continue;
}
const keyVal = token.slice(1);
let [key, val]: any = keyVal.split(':');
let [key, val = undefined] = keyVal.split(':');
const isSet = val === undefined || val === 'true';
if (key === 'validateNoCapitalizedCalls') {
maybeConfig[key] = [];
continue;
}
if (
key === 'enableChangeDetectionForDebugging' &&
(val === undefined || val === 'true')
) {
maybeConfig[key] = {
source: 'react-compiler-runtime',
importSpecifierName: '$structuralCheck',
};
if (isSet && key in testComplexConfigDefaults) {
maybeConfig[key] =
testComplexConfigDefaults[key as keyof PartialEnvironmentConfig];
continue;
}
@@ -544,7 +703,6 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig {
props.push({type: 'name', name: elt});
}
}
console.log([valSplit[0], props.map(x => x.name ?? '*').join('.')]);
maybeConfig[key] = [[valSplit[0], props]];
}
continue;
@@ -555,11 +713,10 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig {
continue;
}
if (val === undefined || val === 'true') {
val = true;
maybeConfig[key] = true;
} else {
val = false;
maybeConfig[key] = false;
}
maybeConfig[key] = val;
}
const config = EnvironmentConfigSchema.safeParse(maybeConfig);
@@ -672,7 +829,8 @@ export class Environment {
}
if (config.enableCustomTypeDefinitionForReanimated) {
installReAnimatedTypes(this.#globals, this.#shapes);
const reanimatedModuleType = getReanimatedModuleType(this.#shapes);
this.#moduleTypes.set(REANIMATED_MODULE_NAME, reanimatedModuleType);
}
this.#contextIdentifiers = contextIdentifiers;
@@ -718,11 +876,11 @@ export class Environment {
}
#resolveModuleType(moduleName: string, loc: SourceLocation): Global | null {
if (this.config.moduleTypeProvider == null) {
return null;
}
let moduleType = this.#moduleTypes.get(moduleName);
if (moduleType === undefined) {
if (this.config.moduleTypeProvider == null) {
return null;
}
const unparsedModuleConfig = this.config.moduleTypeProvider(moduleName);
if (unparsedModuleConfig != null) {
const parsedModuleConfig = TypeSchema.safeParse(unparsedModuleConfig);
@@ -787,7 +945,9 @@ export class Environment {
*/
return (
this.#globals.get(binding.imported) ??
(isHookName(binding.imported) ? this.#getCustomHookType() : null)
(isHookName(binding.imported) || isHookName(binding.name)
? this.#getCustomHookType()
: null)
);
} else {
const moduleType = this.#resolveModuleType(binding.module, loc);
@@ -941,6 +1101,8 @@ export class Environment {
}
}
const REANIMATED_MODULE_NAME = 'react-native-reanimated';
// From https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js#LL18C1-L23C2
export function isHookName(name: string): boolean {
return /^use[A-Z0-9]/.test(name);
@@ -992,3 +1154,5 @@ export function tryParseExternalFunction(
suggestions: null,
});
}
export const DEFAULT_EXPORT = 'default';

View File

@@ -25,7 +25,7 @@ import {
addHook,
addObject,
} from './ObjectShape';
import {BuiltInType, PolyType} from './Types';
import {BuiltInType, ObjectType, PolyType} from './Types';
import {TypeConfig} from './TypeSchema';
import {assertExhaustive} from '../Utils/utils';
import {isHookName} from './Environment';
@@ -652,10 +652,7 @@ export function installTypeConfig(
}
}
export function installReAnimatedTypes(
globals: GlobalRegistry,
registry: ShapeRegistry,
): void {
export function getReanimatedModuleType(registry: ShapeRegistry): ObjectType {
// hooks that freeze args and return frozen value
const frozenHooks = [
'useFrameCallback',
@@ -665,8 +662,9 @@ export function installReAnimatedTypes(
'useAnimatedReaction',
'useWorkletCallback',
];
const reanimatedType: Array<[string, BuiltInType]> = [];
for (const hook of frozenHooks) {
globals.set(
reanimatedType.push([
hook,
addHook(registry, {
positionalParams: [],
@@ -677,7 +675,7 @@ export function installReAnimatedTypes(
calleeEffect: Effect.Read,
hookKind: 'Custom',
}),
);
]);
}
/**
@@ -686,7 +684,7 @@ export function installReAnimatedTypes(
*/
const mutableHooks = ['useSharedValue', 'useDerivedValue'];
for (const hook of mutableHooks) {
globals.set(
reanimatedType.push([
hook,
addHook(registry, {
positionalParams: [],
@@ -697,7 +695,7 @@ export function installReAnimatedTypes(
calleeEffect: Effect.Read,
hookKind: 'Custom',
}),
);
]);
}
// functions that return mutable value
@@ -711,7 +709,7 @@ export function installReAnimatedTypes(
'executeOnUIRuntimeSync',
];
for (const fn of funcs) {
globals.set(
reanimatedType.push([
fn,
addFunction(registry, [], {
positionalParams: [],
@@ -721,6 +719,8 @@ export function installReAnimatedTypes(
returnValueKind: ValueKind.Mutable,
noAlias: true,
}),
);
]);
}
return addObject(registry, null, reanimatedType);
}

View File

@@ -367,6 +367,7 @@ export type BasicBlock = {
preds: Set<BlockId>;
phis: Set<Phi>;
};
export type TBasicBlock<T extends Terminal> = BasicBlock & {terminal: T};
/*
* Terminal nodes generally represent statements that affect control flow, such as
@@ -760,8 +761,8 @@ function _staticInvariantInstructionValueHasLocation(
export type Phi = {
kind: 'Phi';
id: Identifier;
operands: Map<BlockId, Identifier>;
place: Place;
operands: Map<BlockId, Place>;
};
/**
@@ -874,13 +875,7 @@ export type InstructionValue =
};
loc: SourceLocation;
}
| {
kind: 'StoreLocal';
lvalue: LValue;
value: Place;
type: t.FlowType | t.TSType | null;
loc: SourceLocation;
}
| StoreLocal
| {
kind: 'StoreContext';
lvalue: {
@@ -925,15 +920,7 @@ export type InstructionValue =
type: Type;
loc: SourceLocation;
}
| {
kind: 'JsxExpression';
tag: Place | BuiltinTag;
props: Array<JsxAttribute>;
children: Array<Place> | null; // null === no children
loc: SourceLocation;
openingLoc: SourceLocation;
closingLoc: SourceLocation;
}
| JsxExpression
| {
kind: 'ObjectExpression';
properties: Array<ObjectProperty | SpreadPattern>;
@@ -1079,6 +1066,16 @@ export type InstructionValue =
loc: SourceLocation;
};
export type JsxExpression = {
kind: 'JsxExpression';
tag: Place | BuiltinTag;
props: Array<JsxAttribute>;
children: Array<Place> | null; // null === no children
loc: SourceLocation;
openingLoc: SourceLocation;
closingLoc: SourceLocation;
};
export type JsxAttribute =
| {kind: 'JsxSpreadAttribute'; argument: Place}
| {kind: 'JsxAttribute'; name: string; place: Place};
@@ -1123,6 +1120,13 @@ export type Primitive = {
export type JSXText = {kind: 'JSXText'; value: string; loc: SourceLocation};
export type StoreLocal = {
kind: 'StoreLocal';
lvalue: LValue;
value: Place;
type: t.FlowType | t.TSType | null;
loc: SourceLocation;
};
export type PropertyLoad = {
kind: 'PropertyLoad';
object: Place;
@@ -1239,6 +1243,17 @@ export function makeTemporaryIdentifier(
};
}
export function forkTemporaryIdentifier(
id: IdentifierId,
source: Identifier,
): Identifier {
return {
...source,
mutableRange: {start: makeInstructionId(0), end: makeInstructionId(0)},
id,
};
}
/**
* Creates a valid identifier name. This should *not* be used for synthesizing
* identifier names: only call this method for identifier names that appear in the
@@ -1496,7 +1511,8 @@ export type ReactiveScopeDeclaration = {
scope: ReactiveScope; // the scope in which the variable was originally declared
};
export type DependencyPath = Array<{property: string; optional: boolean}>;
export type DependencyPathEntry = {property: string; optional: boolean};
export type DependencyPath = Array<DependencyPathEntry>;
export type ReactiveScopeDependency = {
identifier: Identifier;
path: DependencyPath;

View File

@@ -912,3 +912,23 @@ export function clonePlaceToTemporary(env: Environment, place: Place): Place {
temp.reactive = place.reactive;
return temp;
}
/**
* Fix scope and identifier ranges to account for renumbered instructions
*/
export function fixScopeAndIdentifierRanges(func: HIR): void {
for (const [, block] of func.blocks) {
const terminal = block.terminal;
if (terminal.kind === 'scope' || terminal.kind === 'pruned-scope') {
/*
* Scope ranges should always align to start at the 'scope' terminal
* and end at the first instruction of the fallthrough block
*/
const fallthroughBlock = func.blocks.get(terminal.fallthrough)!;
const firstId =
fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id;
terminal.scope.range.start = terminal.id;
terminal.scope.range.end = firstId;
}
}
}

View File

@@ -84,20 +84,14 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
id: predecessor.terminal.id,
lvalue: {
kind: 'Identifier',
identifier: phi.id,
identifier: phi.place.identifier,
effect: Effect.ConditionallyMutate,
reactive: false,
loc: GeneratedSource,
},
value: {
kind: 'LoadLocal',
place: {
kind: 'Identifier',
identifier: operand,
effect: Effect.Read,
reactive: false,
loc: GeneratedSource,
},
place: {...operand},
loc: GeneratedSource,
},
loc: GeneratedSource,

View File

@@ -163,13 +163,13 @@ export function printInstruction(instr: ReactiveInstruction): string {
export function printPhi(phi: Phi): string {
const items = [];
items.push(printIdentifier(phi.id));
items.push(printMutableRange(phi.id));
items.push(printType(phi.id.type));
items.push(printPlace(phi.place));
items.push(printMutableRange(phi.place.identifier));
items.push(printType(phi.place.identifier.type));
items.push(': phi(');
const phis = [];
for (const [blockId, id] of phi.operands) {
phis.push(`bb${blockId}: ${printIdentifier(id)}`);
for (const [blockId, place] of phi.operands) {
phis.push(`bb${blockId}: ${printPlace(place)}`);
}
items.push(phis.join(', '));

View File

@@ -16,11 +16,11 @@ import {
DeclarationId,
areEqualPaths,
IdentifierId,
Terminal,
} from './HIR';
import {
BlockInfo,
collectHoistablePropertyLoads,
getProperty,
keyByScopeId,
} from './CollectHoistablePropertyLoads';
import {
ScopeBlockTraversal,
@@ -33,37 +33,60 @@ import {Stack, empty} from '../Utils/Stack';
import {CompilerError} from '../CompilerError';
import {Iterable_some} from '../Utils/utils';
import {ReactiveScopeDependencyTreeHIR} from './DeriveMinimalDependenciesHIR';
import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies';
export function propagateScopeDependenciesHIR(fn: HIRFunction): void {
const usedOutsideDeclaringScope =
findTemporariesUsedOutsideDeclaringScope(fn);
const temporaries = collectTemporariesSidemap(fn, usedOutsideDeclaringScope);
const {
temporariesReadInOptional,
processedInstrsInOptional,
hoistableObjects,
} = collectOptionalChainSidemap(fn);
const hoistablePropertyLoads = collectHoistablePropertyLoads(fn, temporaries);
const hoistablePropertyLoads = keyByScopeId(
fn,
collectHoistablePropertyLoads(fn, temporaries, hoistableObjects),
);
const scopeDeps = collectDependencies(
fn,
usedOutsideDeclaringScope,
temporaries,
new Map([...temporaries, ...temporariesReadInOptional]),
processedInstrsInOptional,
);
/**
* Derive the minimal set of hoistable dependencies for each scope.
*/
for (const [scope, deps] of scopeDeps) {
const tree = new ReactiveScopeDependencyTreeHIR();
if (deps.length === 0) {
continue;
}
/**
* Step 1: Add every dependency used by this scope (e.g. `a.b.c`)
* Step 1: Find hoistable accesses, given the basic block in which the scope
* begins.
*/
const hoistables = hoistablePropertyLoads.get(scope.id);
CompilerError.invariant(hoistables != null, {
reason: '[PropagateScopeDependencies] Scope not found in tracked blocks',
loc: GeneratedSource,
});
/**
* Step 2: Calculate hoistable dependencies.
*/
const tree = new ReactiveScopeDependencyTreeHIR(
[...hoistables.assumedNonNullObjects].map(o => o.fullPath),
);
for (const dep of deps) {
tree.addDependency({...dep});
}
/**
* Step 2: Mark hoistable dependencies, given the basic block in
* which the scope begins.
* Step 3: Reduce dependencies to a minimal set.
*/
recordHoistablePropertyReads(hoistablePropertyLoads, scope.id, tree);
const candidates = tree.deriveMinimalDependencies();
for (const candidateDep of candidates) {
if (
@@ -154,8 +177,10 @@ function findTemporariesUsedOutsideDeclaringScope(
* $2 = LoadLocal 'foo'
* $3 = CallExpression $2($1)
* ```
* Only map LoadLocal and PropertyLoad lvalues to their source if we know that
* reordering the read (from the time-of-load to time-of-use) is valid.
* @param usedOutsideDeclaringScope is used to check the correctness of
* reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal /
* PropertyLoad in the returned temporaries map if reordering the read (from the
* time-of-load to time-of-use) is valid.
*
* If a LoadLocal or PropertyLoad instruction is within the reactive scope range
* (a proxy for mutable range) of the load source, later instructions may
@@ -189,11 +214,33 @@ function findTemporariesUsedOutsideDeclaringScope(
* of $1, as the evaluation of `arr.length` changes between instructions $1 and
* $3. We do not track $1 -> arr.length in this case.
*/
function collectTemporariesSidemap(
export function collectTemporariesSidemap(
fn: HIRFunction,
usedOutsideDeclaringScope: ReadonlySet<DeclarationId>,
): ReadonlyMap<IdentifierId, ReactiveScopeDependency> {
const temporaries = new Map<IdentifierId, ReactiveScopeDependency>();
const temporaries = new Map();
collectTemporariesSidemapImpl(
fn,
usedOutsideDeclaringScope,
temporaries,
false,
);
return temporaries;
}
/**
* Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a
* function and all nested functions.
*
* Note that IdentifierIds are currently unique, so we can use a single
* Map<IdentifierId, ...> across all nested functions.
*/
function collectTemporariesSidemapImpl(
fn: HIRFunction,
usedOutsideDeclaringScope: ReadonlySet<DeclarationId>,
temporaries: Map<IdentifierId, ReactiveScopeDependency>,
isInnerFn: boolean,
): void {
for (const [_, block] of fn.body.blocks) {
for (const instr of block.instructions) {
const {value, lvalue} = instr;
@@ -202,22 +249,97 @@ function collectTemporariesSidemap(
);
if (value.kind === 'PropertyLoad' && !usedOutside) {
const property = getProperty(value.object, value.property, temporaries);
temporaries.set(lvalue.identifier.id, property);
if (!isInnerFn || temporaries.has(value.object.identifier.id)) {
/**
* All dependencies of a inner / nested function must have a base
* identifier from the outermost component / hook. This is because the
* compiler cannot break an inner function into multiple granular
* scopes.
*/
const property = getProperty(
value.object,
value.property,
false,
temporaries,
);
temporaries.set(lvalue.identifier.id, property);
}
} else if (
value.kind === 'LoadLocal' &&
lvalue.identifier.name == null &&
value.place.identifier.name !== null &&
!usedOutside
) {
temporaries.set(lvalue.identifier.id, {
identifier: value.place.identifier,
path: [],
});
if (
!isInnerFn ||
fn.context.some(
context => context.identifier.id === value.place.identifier.id,
)
) {
temporaries.set(lvalue.identifier.id, {
identifier: value.place.identifier,
path: [],
});
}
} else if (
value.kind === 'FunctionExpression' ||
value.kind === 'ObjectMethod'
) {
collectTemporariesSidemapImpl(
value.loweredFunc.func,
usedOutsideDeclaringScope,
temporaries,
true,
);
}
}
}
return temporaries;
}
function getProperty(
object: Place,
propertyName: string,
optional: boolean,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
): ReactiveScopeDependency {
/*
* (1) Get the base object either from the temporary sidemap (e.g. a LoadLocal)
* or a deep copy of an existing property dependency.
* Example 1:
* $0 = LoadLocal x
* $1 = PropertyLoad $0.y
* getProperty($0, ...) -> resolvedObject = x, resolvedDependency = null
*
* Example 2:
* $0 = LoadLocal x
* $1 = PropertyLoad $0.y
* $2 = PropertyLoad $1.z
* getProperty($1, ...) -> resolvedObject = null, resolvedDependency = x.y
*
* Example 3:
* $0 = Call(...)
* $1 = PropertyLoad $0.y
* getProperty($0, ...) -> resolvedObject = null, resolvedDependency = null
*/
const resolvedDependency = temporaries.get(object.identifier.id);
/**
* (2) Push the last PropertyLoad
* TODO(mofeiZ): understand optional chaining
*/
let property: ReactiveScopeDependency;
if (resolvedDependency == null) {
property = {
identifier: object.identifier,
path: [{property: propertyName, optional}],
};
} else {
property = {
identifier: resolvedDependency.identifier,
path: [...resolvedDependency.path, {property: propertyName, optional}],
};
}
return property;
}
type Decl = {
@@ -237,6 +359,12 @@ class Context {
#temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>;
#temporariesUsedOutsideScope: ReadonlySet<DeclarationId>;
/**
* Tracks the traversal state. See Context.declare for explanation of why this
* is needed.
*/
inInnerFn: boolean = false;
constructor(
temporariesUsedOutsideScope: ReadonlySet<DeclarationId>,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
@@ -287,12 +415,23 @@ class Context {
}
/*
* Records where a value was declared, and optionally, the scope where the value originated from.
* This is later used to determine if a dependency should be added to a scope; if the current
* scope we are visiting is the same scope where the value originates, it can't be a dependency
* on itself.
* Records where a value was declared, and optionally, the scope where the
* value originated from. This is later used to determine if a dependency
* should be added to a scope; if the current scope we are visiting is the
* same scope where the value originates, it can't be a dependency on itself.
*
* Note that we do not track declarations or reassignments within inner
* functions for the following reasons:
* - inner functions cannot be split by scope boundaries and are guaranteed
* to consume their own declarations
* - reassignments within inner functions are tracked as context variables,
* which already have extended mutable ranges to account for reassignments
* - *most importantly* it's currently simply incorrect to compare inner
* function instruction ids (tracked by `decl`) with outer ones (as stored
* by root identifier mutable ranges).
*/
declare(identifier: Identifier, decl: Decl): void {
if (this.inInnerFn) return;
if (!this.#declarations.has(identifier.declarationId)) {
this.#declarations.set(identifier.declarationId, decl);
}
@@ -301,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;
@@ -362,8 +493,13 @@ class Context {
);
}
visitProperty(object: Place, property: string): void {
const nextDependency = getProperty(object, property, this.#temporaries);
visitProperty(object: Place, property: string, optional: boolean): void {
const nextDependency = getProperty(
object,
property,
optional,
this.#temporaries,
);
this.visitDependency(nextDependency);
}
@@ -405,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);
}
@@ -442,7 +588,7 @@ function handleInstruction(instr: Instruction, context: Context): void {
}
} else if (value.kind === 'PropertyLoad') {
if (context.isUsedOutsideDeclaringScope(lvalue)) {
context.visitProperty(value.object, value.property);
context.visitProperty(value.object, value.property, false);
}
} else if (value.kind === 'StoreLocal') {
context.visitOperand(value.value);
@@ -497,6 +643,7 @@ function collectDependencies(
fn: HIRFunction,
usedOutsideDeclaringScope: ReadonlySet<DeclarationId>,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
processedInstrsInOptional: ReadonlySet<Instruction | Terminal>,
): Map<ReactiveScope, Array<ReactiveScopeDependency>> {
const context = new Context(usedOutsideDeclaringScope, temporaries);
@@ -516,42 +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);
}
}
for (const instr of block.instructions) {
handleInstruction(instr, context);
if (!processedInstrsInOptional.has(block.terminal)) {
for (const place of eachTerminalOperand(block.terminal)) {
context.visitOperand(place);
}
}
}
for (const place of eachTerminalOperand(block.terminal)) {
context.visitOperand(place);
}
}
};
handleFunction(fn);
return context.deps;
}
/**
* Compute the set of hoistable property reads.
*/
function recordHoistablePropertyReads(
nodes: ReadonlyMap<ScopeId, BlockInfo>,
scopeId: ScopeId,
tree: ReactiveScopeDependencyTreeHIR,
): void {
const node = nodes.get(scopeId);
CompilerError.invariant(node != null, {
reason: '[PropagateScopeDependencies] Scope not found in tracked blocks',
loc: GeneratedSource,
});
for (const item of node.assumedNonNullObjects) {
tree.markNodesNonNull({
...item.fullPath,
});
}
}

View File

@@ -17,7 +17,7 @@ export {buildReactiveScopeTerminalsHIR} from './BuildReactiveScopeTerminalsHIR';
export {computeDominatorTree, computePostDominatorTree} from './Dominator';
export {
Environment,
parseConfigPragma,
parseConfigPragmaForTests,
validateEnvironmentConfig,
type EnvironmentConfig,
type ExternalFunction,

View File

@@ -1215,9 +1215,17 @@ export class ScopeBlockTraversal {
}
}
/**
* @returns if the given scope is currently 'active', i.e. if the scope start
* block but not the scope fallthrough has been recorded.
*/
isScopeActive(scopeId: ScopeId): boolean {
return this.#activeScopes.indexOf(scopeId) !== -1;
}
/**
* The current, innermost active scope.
*/
get currentScope(): ScopeId | null {
return this.#activeScopes.at(-1) ?? null;
}

View File

@@ -15,11 +15,11 @@ export function inferAliasForPhis(
for (const [_, block] of func.body.blocks) {
for (const phi of block.phis) {
const isPhiMutatedAfterCreation: boolean =
phi.id.mutableRange.end >
phi.place.identifier.mutableRange.end >
(block.instructions.at(0)?.id ?? block.terminal.id);
if (isPhiMutatedAfterCreation) {
for (const [, operand] of phi.operands) {
aliases.union([phi.id, operand]);
aliases.union([phi.place.identifier, operand.identifier]);
}
}
}

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

@@ -116,19 +116,23 @@ export function inferMutableLifetimes(
for (const [_, block] of func.body.blocks) {
for (const phi of block.phis) {
const isPhiMutatedAfterCreation: boolean =
phi.id.mutableRange.end >
phi.place.identifier.mutableRange.end >
(block.instructions.at(0)?.id ?? block.terminal.id);
if (
inferMutableRangeForStores &&
isPhiMutatedAfterCreation &&
phi.id.mutableRange.start === 0
phi.place.identifier.mutableRange.start === 0
) {
for (const [, operand] of phi.operands) {
if (phi.id.mutableRange.start === 0) {
phi.id.mutableRange.start = operand.mutableRange.start;
if (phi.place.identifier.mutableRange.start === 0) {
phi.place.identifier.mutableRange.start =
operand.identifier.mutableRange.start;
} else {
phi.id.mutableRange.start = makeInstructionId(
Math.min(phi.id.mutableRange.start, operand.mutableRange.start),
phi.place.identifier.mutableRange.start = makeInstructionId(
Math.min(
phi.place.identifier.mutableRange.start,
operand.identifier.mutableRange.start,
),
);
}
}

View File

@@ -157,28 +157,27 @@ export function inferReactivePlaces(fn: HIRFunction): void {
}
do {
const identifierMapping = new Map<Identifier, Identifier>();
for (const [, block] of fn.body.blocks) {
let hasReactiveControl = isReactiveControlledBlock(block.id);
for (const phi of block.phis) {
if (reactiveIdentifiers.isReactiveIdentifier(phi.id)) {
if (reactiveIdentifiers.isReactive(phi.place)) {
// Already marked reactive on a previous pass
continue;
}
let isPhiReactive = false;
for (const [, operand] of phi.operands) {
if (reactiveIdentifiers.isReactiveIdentifier(operand)) {
if (reactiveIdentifiers.isReactive(operand)) {
isPhiReactive = true;
break;
}
}
if (isPhiReactive) {
reactiveIdentifiers.markReactiveIdentifier(phi.id);
reactiveIdentifiers.markReactive(phi.place);
} else {
for (const [pred] of phi.operands) {
if (isReactiveControlledBlock(pred)) {
reactiveIdentifiers.markReactiveIdentifier(phi.id);
reactiveIdentifiers.markReactive(phi.place);
break;
}
}
@@ -233,10 +232,6 @@ export function inferReactivePlaces(fn: HIRFunction): void {
case Effect.ConditionallyMutate:
case Effect.Mutate: {
if (isMutable(instruction, operand)) {
const resolvedId = identifierMapping.get(operand.identifier);
if (resolvedId !== undefined) {
reactiveIdentifiers.markReactiveIdentifier(resolvedId);
}
reactiveIdentifiers.markReactive(operand);
}
break;
@@ -263,31 +258,6 @@ export function inferReactivePlaces(fn: HIRFunction): void {
}
}
}
switch (value.kind) {
case 'LoadLocal': {
identifierMapping.set(
instruction.lvalue.identifier,
value.place.identifier,
);
break;
}
case 'PropertyLoad':
case 'ComputedLoad': {
const resolvedId =
identifierMapping.get(value.object.identifier) ??
value.object.identifier;
identifierMapping.set(instruction.lvalue.identifier, resolvedId);
break;
}
case 'LoadContext': {
identifierMapping.set(
instruction.lvalue.identifier,
value.place.identifier,
);
break;
}
}
}
for (const operand of eachTerminalOperand(block.terminal)) {
reactiveIdentifiers.isReactive(operand);
@@ -372,27 +342,19 @@ class ReactivityMap {
}
isReactive(place: Place): boolean {
const reactive = this.isReactiveIdentifier(place.identifier);
const identifier =
this.aliasedIdentifiers.find(place.identifier) ?? place.identifier;
const reactive = this.reactive.has(identifier.id);
if (reactive) {
place.reactive = true;
}
return reactive;
}
isReactiveIdentifier(inputIdentifier: Identifier): boolean {
const identifier =
this.aliasedIdentifiers.find(inputIdentifier) ?? inputIdentifier;
return this.reactive.has(identifier.id);
}
markReactive(place: Place): void {
place.reactive = true;
this.markReactiveIdentifier(place.identifier);
}
markReactiveIdentifier(inputIdentifier: Identifier): void {
const identifier =
this.aliasedIdentifiers.find(inputIdentifier) ?? inputIdentifier;
this.aliasedIdentifiers.find(place.identifier) ?? place.identifier;
if (!this.reactive.has(identifier.id)) {
this.hasChanges = true;
this.reactive.add(identifier.id);

View File

@@ -635,7 +635,7 @@ class InferenceState {
inferPhi(phi: Phi): void {
const values: Set<InstructionValue> = new Set();
for (const [_, operand] of phi.operands) {
const operandValues = this.#variables.get(operand.id);
const operandValues = this.#variables.get(operand.identifier.id);
// This is a backedge that will be handled later by State.merge
if (operandValues === undefined) continue;
for (const v of operandValues) {
@@ -644,7 +644,7 @@ class InferenceState {
}
if (values.size > 0) {
this.#variables.set(phi.id.id, values);
this.#variables.set(phi.place.identifier.id, values);
}
}
}

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

@@ -8,7 +8,6 @@
import {isValidIdentifier} from '@babel/types';
import {CompilerError} from '../CompilerError';
import {
Environment,
GotoVariant,
HIRFunction,
IdentifierId,
@@ -117,7 +116,7 @@ function applyConstantPropagation(
for (const phi of block.phis) {
let value = evaluatePhi(phi, constants);
if (value !== null) {
constants.set(phi.id.id, value);
constants.set(phi.place.identifier.id, value);
}
}
@@ -130,7 +129,7 @@ function applyConstantPropagation(
continue;
}
const instr = block.instructions[i]!;
const value = evaluateInstruction(fn.env, constants, instr);
const value = evaluateInstruction(constants, instr);
if (value !== null) {
constants.set(instr.lvalue.identifier.id, value);
}
@@ -167,7 +166,7 @@ function applyConstantPropagation(
function evaluatePhi(phi: Phi, constants: Constants): Constant | null {
let value: Constant | null = null;
for (const [, operand] of phi.operands) {
const operandValue = constants.get(operand.id) ?? null;
const operandValue = constants.get(operand.identifier.id) ?? null;
// did not find a constant, can't constant propogate
if (operandValue === null) {
return null;
@@ -223,7 +222,6 @@ function evaluatePhi(phi: Phi, constants: Constants): Constant | null {
}
function evaluateInstruction(
env: Environment,
constants: Constants,
instr: Instruction,
): Constant | null {

View File

@@ -6,7 +6,6 @@
*/
import {
ArrayPattern,
BlockId,
HIRFunction,
Identifier,
@@ -42,7 +41,7 @@ export function deadCodeElimination(fn: HIRFunction): void {
*/
for (const [, block] of fn.body.blocks) {
for (const phi of block.phis) {
if (!state.isIdOrNameUsed(phi.id)) {
if (!state.isIdOrNameUsed(phi.place.identifier)) {
block.phis.delete(phi);
}
}
@@ -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 {
@@ -159,9 +166,9 @@ function findReferencedIdentifiers(fn: HIRFunction): State {
}
}
for (const phi of block.phis) {
if (state.isIdOrNameUsed(phi.id)) {
if (state.isIdOrNameUsed(phi.place.identifier)) {
for (const [_pred, operand] of phi.operands) {
state.reference(operand);
state.reference(operand.identifier);
}
}
}
@@ -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

@@ -0,0 +1,775 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {
BasicBlock,
BlockId,
BuiltinTag,
DeclarationId,
Effect,
forkTemporaryIdentifier,
GotoTerminal,
GotoVariant,
HIRFunction,
Identifier,
IfTerminal,
Instruction,
InstructionKind,
JsxAttribute,
makeInstructionId,
ObjectProperty,
Phi,
Place,
promoteTemporary,
SpreadPattern,
} from '../HIR';
import {
createTemporaryPlace,
fixScopeAndIdentifierRanges,
markInstructionIds,
markPredecessors,
reversePostorderBlocks,
} from '../HIR/HIRBuilder';
import {CompilerError, EnvironmentConfig} from '..';
import {
mapInstructionLValues,
mapInstructionOperands,
mapInstructionValueOperands,
mapTerminalOperands,
} from '../HIR/visitors';
type InlinedJsxDeclarationMap = Map<
DeclarationId,
{identifier: Identifier; blockIdsToIgnore: Set<BlockId>}
>;
/**
* A prod-only, RN optimization to replace JSX with inlined ReactElement object literals
*
* Example:
* <>foo</>
* _______________
* let t1;
* if (__DEV__) {
* t1 = <>foo</>
* } else {
* t1 = {...}
* }
*
*/
export function inlineJsxTransform(
fn: HIRFunction,
inlineJsxTransformConfig: NonNullable<
EnvironmentConfig['inlineJsxTransform']
>,
): void {
const inlinedJsxDeclarations: InlinedJsxDeclarationMap = new Map();
/**
* Step 1: Codegen the conditional and ReactElement object literal
*/
for (const [_, currentBlock] of [...fn.body.blocks]) {
let fallthroughBlockInstructions: Array<Instruction> | null = null;
const instructionCount = currentBlock.instructions.length;
for (let i = 0; i < instructionCount; i++) {
const instr = currentBlock.instructions[i]!;
// TODO: Support value blocks
if (currentBlock.kind === 'value') {
fn.env.logger?.logEvent(fn.env.filename, {
kind: 'CompileDiagnostic',
fnLoc: null,
detail: {
reason: 'JSX Inlining is not supported on value blocks',
loc: instr.loc,
},
});
continue;
}
switch (instr.value.kind) {
case 'JsxExpression':
case 'JsxFragment': {
/**
* Split into blocks for new IfTerminal:
* current, then, else, fallthrough
*/
const currentBlockInstructions = currentBlock.instructions.slice(
0,
i,
);
const thenBlockInstructions = currentBlock.instructions.slice(
i,
i + 1,
);
const elseBlockInstructions: Array<Instruction> = [];
fallthroughBlockInstructions ??= currentBlock.instructions.slice(
i + 1,
);
const fallthroughBlockId = fn.env.nextBlockId;
const fallthroughBlock: BasicBlock = {
kind: currentBlock.kind,
id: fallthroughBlockId,
instructions: fallthroughBlockInstructions,
terminal: currentBlock.terminal,
preds: new Set(),
phis: new Set(),
};
/**
* Complete current block
* - Add instruction for variable declaration
* - Add instruction for LoadGlobal used by conditional
* - End block with a new IfTerminal
*/
const varPlace = createTemporaryPlace(fn.env, instr.value.loc);
promoteTemporary(varPlace.identifier);
const varLValuePlace = createTemporaryPlace(fn.env, instr.value.loc);
const thenVarPlace = {
...varPlace,
identifier: forkTemporaryIdentifier(
fn.env.nextIdentifierId,
varPlace.identifier,
),
};
const elseVarPlace = {
...varPlace,
identifier: forkTemporaryIdentifier(
fn.env.nextIdentifierId,
varPlace.identifier,
),
};
const varInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...varLValuePlace},
value: {
kind: 'DeclareLocal',
lvalue: {place: {...varPlace}, kind: InstructionKind.Let},
type: null,
loc: instr.value.loc,
},
loc: instr.loc,
};
currentBlockInstructions.push(varInstruction);
const devGlobalPlace = createTemporaryPlace(fn.env, instr.value.loc);
const devGlobalInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...devGlobalPlace, effect: Effect.Mutate},
value: {
kind: 'LoadGlobal',
binding: {
kind: 'Global',
name: inlineJsxTransformConfig.globalDevVar,
},
loc: instr.value.loc,
},
loc: instr.loc,
};
currentBlockInstructions.push(devGlobalInstruction);
const thenBlockId = fn.env.nextBlockId;
const elseBlockId = fn.env.nextBlockId;
const ifTerminal: IfTerminal = {
kind: 'if',
test: {...devGlobalPlace, effect: Effect.Read},
consequent: thenBlockId,
alternate: elseBlockId,
fallthrough: fallthroughBlockId,
loc: instr.loc,
id: makeInstructionId(0),
};
currentBlock.instructions = currentBlockInstructions;
currentBlock.terminal = ifTerminal;
/**
* Set up then block where we put the original JSX return
*/
const thenBlock: BasicBlock = {
id: thenBlockId,
instructions: thenBlockInstructions,
kind: 'block',
phis: new Set(),
preds: new Set(),
terminal: {
kind: 'goto',
block: fallthroughBlockId,
variant: GotoVariant.Break,
id: makeInstructionId(0),
loc: instr.loc,
},
};
fn.body.blocks.set(thenBlockId, thenBlock);
const resassignElsePlace = createTemporaryPlace(
fn.env,
instr.value.loc,
);
const reassignElseInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...resassignElsePlace},
value: {
kind: 'StoreLocal',
lvalue: {
place: elseVarPlace,
kind: InstructionKind.Reassign,
},
value: {...instr.lvalue},
type: null,
loc: instr.value.loc,
},
loc: instr.loc,
};
thenBlockInstructions.push(reassignElseInstruction);
/**
* Set up else block where we add new codegen
*/
const elseBlockTerminal: GotoTerminal = {
kind: 'goto',
block: fallthroughBlockId,
variant: GotoVariant.Break,
id: makeInstructionId(0),
loc: instr.loc,
};
const elseBlock: BasicBlock = {
id: elseBlockId,
instructions: elseBlockInstructions,
kind: 'block',
phis: new Set(),
preds: new Set(),
terminal: elseBlockTerminal,
};
fn.body.blocks.set(elseBlockId, elseBlock);
/**
* ReactElement object literal codegen
*/
const {refProperty, keyProperty, propsProperty} =
createPropsProperties(
fn,
instr,
elseBlockInstructions,
instr.value.kind === 'JsxExpression' ? instr.value.props : [],
instr.value.children,
);
const reactElementInstructionPlace = createTemporaryPlace(
fn.env,
instr.value.loc,
);
const reactElementInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...reactElementInstructionPlace, effect: Effect.Store},
value: {
kind: 'ObjectExpression',
properties: [
createSymbolProperty(
fn,
instr,
elseBlockInstructions,
'$$typeof',
inlineJsxTransformConfig.elementSymbol,
),
instr.value.kind === 'JsxExpression'
? createTagProperty(
fn,
instr,
elseBlockInstructions,
instr.value.tag,
)
: createSymbolProperty(
fn,
instr,
elseBlockInstructions,
'type',
'react.fragment',
),
refProperty,
keyProperty,
propsProperty,
],
loc: instr.value.loc,
},
loc: instr.loc,
};
elseBlockInstructions.push(reactElementInstruction);
const reassignConditionalInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...createTemporaryPlace(fn.env, instr.value.loc)},
value: {
kind: 'StoreLocal',
lvalue: {
place: {...elseVarPlace},
kind: InstructionKind.Reassign,
},
value: {...reactElementInstruction.lvalue},
type: null,
loc: instr.value.loc,
},
loc: instr.loc,
};
elseBlockInstructions.push(reassignConditionalInstruction);
/**
* Create phis to reassign the var
*/
const operands: Map<BlockId, Place> = new Map();
operands.set(thenBlockId, {
...elseVarPlace,
});
operands.set(elseBlockId, {
...thenVarPlace,
});
const phiIdentifier = forkTemporaryIdentifier(
fn.env.nextIdentifierId,
varPlace.identifier,
);
const phiPlace = {
...createTemporaryPlace(fn.env, instr.value.loc),
identifier: phiIdentifier,
};
const phis: Set<Phi> = new Set([
{
kind: 'Phi',
operands,
place: phiPlace,
},
]);
fallthroughBlock.phis = phis;
fn.body.blocks.set(fallthroughBlockId, fallthroughBlock);
/**
* Track this JSX instruction so we can replace references in step 2
*/
inlinedJsxDeclarations.set(instr.lvalue.identifier.declarationId, {
identifier: phiIdentifier,
blockIdsToIgnore: new Set([thenBlockId, elseBlockId]),
});
break;
}
case 'FunctionExpression':
case 'ObjectMethod': {
inlineJsxTransform(
instr.value.loweredFunc.func,
inlineJsxTransformConfig,
);
break;
}
}
}
}
/**
* Step 2: Replace declarations with new phi values
*/
for (const [blockId, block] of fn.body.blocks) {
for (const instr of block.instructions) {
mapInstructionOperands(instr, place =>
handlePlace(place, blockId, inlinedJsxDeclarations),
);
mapInstructionLValues(instr, lvalue =>
handlelValue(lvalue, blockId, inlinedJsxDeclarations),
);
mapInstructionValueOperands(instr.value, place =>
handlePlace(place, blockId, inlinedJsxDeclarations),
);
}
mapTerminalOperands(block.terminal, place =>
handlePlace(place, blockId, inlinedJsxDeclarations),
);
if (block.terminal.kind === 'scope') {
const scope = block.terminal.scope;
for (const dep of scope.dependencies) {
dep.identifier = handleIdentifier(
dep.identifier,
inlinedJsxDeclarations,
);
}
for (const [origId, decl] of [...scope.declarations]) {
const newDecl = handleIdentifier(
decl.identifier,
inlinedJsxDeclarations,
);
if (newDecl.id !== origId) {
scope.declarations.delete(origId);
scope.declarations.set(decl.identifier.id, {
identifier: newDecl,
scope: decl.scope,
});
}
}
}
}
/**
* Step 3: Fixup the HIR
* Restore RPO, ensure correct predecessors, renumber instructions, fix scope and ranges.
*/
reversePostorderBlocks(fn.body);
markPredecessors(fn.body);
markInstructionIds(fn.body);
fixScopeAndIdentifierRanges(fn.body);
}
function createSymbolProperty(
fn: HIRFunction,
instr: Instruction,
nextInstructions: Array<Instruction>,
propertyName: string,
symbolName: string,
): ObjectProperty {
const symbolPlace = createTemporaryPlace(fn.env, instr.value.loc);
const symbolInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...symbolPlace, effect: Effect.Mutate},
value: {
kind: 'LoadGlobal',
binding: {kind: 'Global', name: 'Symbol'},
loc: instr.value.loc,
},
loc: instr.loc,
};
nextInstructions.push(symbolInstruction);
const symbolForPlace = createTemporaryPlace(fn.env, instr.value.loc);
const symbolForInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...symbolForPlace, effect: Effect.Read},
value: {
kind: 'PropertyLoad',
object: {...symbolInstruction.lvalue},
property: 'for',
loc: instr.value.loc,
},
loc: instr.loc,
};
nextInstructions.push(symbolForInstruction);
const symbolValuePlace = createTemporaryPlace(fn.env, instr.value.loc);
const symbolValueInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...symbolValuePlace, effect: Effect.Mutate},
value: {
kind: 'Primitive',
value: symbolName,
loc: instr.value.loc,
},
loc: instr.loc,
};
nextInstructions.push(symbolValueInstruction);
const $$typeofPlace = createTemporaryPlace(fn.env, instr.value.loc);
const $$typeofInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...$$typeofPlace, effect: Effect.Mutate},
value: {
kind: 'MethodCall',
receiver: symbolInstruction.lvalue,
property: symbolForInstruction.lvalue,
args: [symbolValueInstruction.lvalue],
loc: instr.value.loc,
},
loc: instr.loc,
};
const $$typeofProperty: ObjectProperty = {
kind: 'ObjectProperty',
key: {name: propertyName, kind: 'string'},
type: 'property',
place: {...$$typeofPlace, effect: Effect.Capture},
};
nextInstructions.push($$typeofInstruction);
return $$typeofProperty;
}
function createTagProperty(
fn: HIRFunction,
instr: Instruction,
nextInstructions: Array<Instruction>,
componentTag: BuiltinTag | Place,
): ObjectProperty {
let tagProperty: ObjectProperty;
switch (componentTag.kind) {
case 'BuiltinTag': {
const tagPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc);
const tagInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...tagPropertyPlace, effect: Effect.Mutate},
value: {
kind: 'Primitive',
value: componentTag.name,
loc: instr.value.loc,
},
loc: instr.loc,
};
tagProperty = {
kind: 'ObjectProperty',
key: {name: 'type', kind: 'string'},
type: 'property',
place: {...tagPropertyPlace, effect: Effect.Capture},
};
nextInstructions.push(tagInstruction);
break;
}
case 'Identifier': {
tagProperty = {
kind: 'ObjectProperty',
key: {name: 'type', kind: 'string'},
type: 'property',
place: {...componentTag, effect: Effect.Capture},
};
break;
}
}
return tagProperty;
}
function createPropsProperties(
fn: HIRFunction,
instr: Instruction,
nextInstructions: Array<Instruction>,
propAttributes: Array<JsxAttribute>,
children: Array<Place> | null,
): {
refProperty: ObjectProperty;
keyProperty: ObjectProperty;
propsProperty: ObjectProperty;
} {
let refProperty: ObjectProperty | undefined;
let keyProperty: ObjectProperty | undefined;
const props: Array<ObjectProperty | SpreadPattern> = [];
const jsxAttributesWithoutKeyAndRef = propAttributes.filter(
p => p.kind === 'JsxAttribute' && p.name !== 'key' && p.name !== 'ref',
);
const jsxSpreadAttributes = propAttributes.filter(
p => p.kind === 'JsxSpreadAttribute',
);
const spreadPropsOnly =
jsxAttributesWithoutKeyAndRef.length === 0 &&
jsxSpreadAttributes.length === 1;
propAttributes.forEach(prop => {
switch (prop.kind) {
case 'JsxAttribute': {
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;
}
case 'JsxSpreadAttribute': {
props.push({
kind: 'Spread',
place: {...prop.argument},
});
break;
}
}
});
const propsPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc);
if (children) {
let childrenPropProperty: ObjectProperty;
if (children.length === 1) {
childrenPropProperty = {
kind: 'ObjectProperty',
key: {name: 'children', kind: 'string'},
type: 'property',
place: {...children[0], effect: Effect.Capture},
};
} else {
const childrenPropPropertyPlace = createTemporaryPlace(
fn.env,
instr.value.loc,
);
const childrenPropInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...childrenPropPropertyPlace, effect: Effect.Mutate},
value: {
kind: 'ArrayExpression',
elements: [...children],
loc: instr.value.loc,
},
loc: instr.loc,
};
nextInstructions.push(childrenPropInstruction);
childrenPropProperty = {
kind: 'ObjectProperty',
key: {name: 'children', kind: 'string'},
type: 'property',
place: {...childrenPropPropertyPlace, effect: Effect.Capture},
};
}
props.push(childrenPropProperty);
}
if (refProperty == null) {
const refPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc);
const refInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...refPropertyPlace, effect: Effect.Mutate},
value: {
kind: 'Primitive',
value: null,
loc: instr.value.loc,
},
loc: instr.loc,
};
refProperty = {
kind: 'ObjectProperty',
key: {name: 'ref', kind: 'string'},
type: 'property',
place: {...refPropertyPlace, effect: Effect.Capture},
};
nextInstructions.push(refInstruction);
}
if (keyProperty == null) {
const keyPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc);
const keyInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...keyPropertyPlace, effect: Effect.Mutate},
value: {
kind: 'Primitive',
value: null,
loc: instr.value.loc,
},
loc: instr.loc,
};
keyProperty = {
kind: 'ObjectProperty',
key: {name: 'key', kind: 'string'},
type: 'property',
place: {...keyPropertyPlace, effect: Effect.Capture},
};
nextInstructions.push(keyInstruction);
}
let propsProperty: ObjectProperty;
if (spreadPropsOnly) {
const spreadProp = jsxSpreadAttributes[0];
CompilerError.invariant(spreadProp.kind === 'JsxSpreadAttribute', {
reason: 'Spread prop attribute must be of kind JSXSpreadAttribute',
loc: instr.loc,
});
propsProperty = {
kind: 'ObjectProperty',
key: {name: 'props', kind: 'string'},
type: 'property',
place: {...spreadProp.argument, effect: Effect.Mutate},
};
} else {
const propsInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...propsPropertyPlace, effect: Effect.Mutate},
value: {
kind: 'ObjectExpression',
properties: props,
loc: instr.value.loc,
},
loc: instr.loc,
};
propsProperty = {
kind: 'ObjectProperty',
key: {name: 'props', kind: 'string'},
type: 'property',
place: {...propsPropertyPlace, effect: Effect.Capture},
};
nextInstructions.push(propsInstruction);
}
return {refProperty, keyProperty, propsProperty};
}
function handlePlace(
place: Place,
blockId: BlockId,
inlinedJsxDeclarations: InlinedJsxDeclarationMap,
): Place {
const inlinedJsxDeclaration = inlinedJsxDeclarations.get(
place.identifier.declarationId,
);
if (
inlinedJsxDeclaration == null ||
inlinedJsxDeclaration.blockIdsToIgnore.has(blockId)
) {
return place;
}
return {...place, identifier: inlinedJsxDeclaration.identifier};
}
function handlelValue(
lvalue: Place,
blockId: BlockId,
inlinedJsxDeclarations: InlinedJsxDeclarationMap,
): Place {
const inlinedJsxDeclaration = inlinedJsxDeclarations.get(
lvalue.identifier.declarationId,
);
if (
inlinedJsxDeclaration == null ||
inlinedJsxDeclaration.blockIdsToIgnore.has(blockId)
) {
return lvalue;
}
return {...lvalue, identifier: inlinedJsxDeclaration.identifier};
}
function handleIdentifier(
identifier: Identifier,
inlinedJsxDeclarations: InlinedJsxDeclarationMap,
): Identifier {
const inlinedJsxDeclaration = inlinedJsxDeclarations.get(
identifier.declarationId,
);
return inlinedJsxDeclaration == null
? identifier
: inlinedJsxDeclaration.identifier;
}

View File

@@ -0,0 +1,520 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import invariant from 'invariant';
import {Environment} from '../HIR';
import {
BasicBlock,
GeneratedSource,
HIRFunction,
IdentifierId,
Instruction,
InstructionId,
InstructionKind,
JsxAttribute,
JsxExpression,
LoadGlobal,
makeBlockId,
makeIdentifierName,
makeInstructionId,
makeType,
ObjectProperty,
Place,
promoteTemporary,
promoteTemporaryJsxTag,
} from '../HIR/HIR';
import {createTemporaryPlace} from '../HIR/HIRBuilder';
import {printIdentifier} from '../HIR/PrintHIR';
import {deadCodeElimination} from './DeadCodeElimination';
import {assertExhaustive} from '../Utils/utils';
export function outlineJSX(fn: HIRFunction): void {
const outlinedFns: Array<HIRFunction> = [];
outlineJsxImpl(fn, outlinedFns);
for (const outlinedFn of outlinedFns) {
fn.env.outlineFunction(outlinedFn, 'Component');
}
}
type JsxInstruction = Instruction & {value: JsxExpression};
type LoadGlobalInstruction = Instruction & {value: LoadGlobal};
type LoadGlobalMap = Map<IdentifierId, LoadGlobalInstruction>;
type State = {
jsx: Array<JsxInstruction>;
children: Set<IdentifierId>;
};
function outlineJsxImpl(
fn: HIRFunction,
outlinedFns: Array<HIRFunction>,
): void {
const globals: LoadGlobalMap = new Map();
function processAndOutlineJSX(
state: State,
rewriteInstr: Map<InstructionId, Array<Instruction>>,
): void {
if (state.jsx.length <= 1) {
return;
}
const result = process(
fn,
[...state.jsx].sort((a, b) => a.id - b.id),
globals,
);
if (result) {
outlinedFns.push(result.fn);
rewriteInstr.set(state.jsx.at(0)!.id, result.instrs);
}
}
for (const [, block] of fn.body.blocks) {
const rewriteInstr = new Map();
let state: State = {
jsx: [],
children: new Set(),
};
for (let i = block.instructions.length - 1; i >= 0; i--) {
const instr = block.instructions[i];
const {value, lvalue} = instr;
switch (value.kind) {
case 'LoadGlobal': {
globals.set(lvalue.identifier.id, instr as LoadGlobalInstruction);
break;
}
case 'FunctionExpression': {
outlineJsxImpl(value.loweredFunc.func, outlinedFns);
break;
}
case 'JsxExpression': {
if (!state.children.has(lvalue.identifier.id)) {
processAndOutlineJSX(state, rewriteInstr);
state = {
jsx: [],
children: new Set(),
};
}
state.jsx.push(instr as JsxInstruction);
if (value.children) {
for (const child of value.children) {
state.children.add(child.identifier.id);
}
}
break;
}
case 'ArrayExpression':
case 'Await':
case 'BinaryExpression':
case 'CallExpression':
case 'ComputedDelete':
case 'ComputedLoad':
case 'ComputedStore':
case 'Debugger':
case 'DeclareContext':
case 'DeclareLocal':
case 'Destructure':
case 'FinishMemoize':
case 'GetIterator':
case 'IteratorNext':
case 'JSXText':
case 'JsxFragment':
case 'LoadContext':
case 'LoadLocal':
case 'MetaProperty':
case 'MethodCall':
case 'NewExpression':
case 'NextPropertyOf':
case 'ObjectExpression':
case 'ObjectMethod':
case 'PostfixUpdate':
case 'PrefixUpdate':
case 'Primitive':
case 'PropertyDelete':
case 'PropertyLoad':
case 'PropertyStore':
case 'RegExpLiteral':
case 'StartMemoize':
case 'StoreContext':
case 'StoreGlobal':
case 'StoreLocal':
case 'TaggedTemplateExpression':
case 'TemplateLiteral':
case 'TypeCastExpression':
case 'UnsupportedNode':
case 'UnaryExpression': {
break;
}
default: {
assertExhaustive(value, `Unexpected instruction: ${value}`);
}
}
}
processAndOutlineJSX(state, rewriteInstr);
if (rewriteInstr.size > 0) {
const newInstrs = [];
for (let i = 0; i < block.instructions.length; i++) {
// InstructionId's are one-indexed, so add one to account for them.
const id = i + 1;
if (rewriteInstr.has(id)) {
const instrs = rewriteInstr.get(id);
newInstrs.push(...instrs);
} else {
newInstrs.push(block.instructions[i]);
}
}
block.instructions = newInstrs;
}
deadCodeElimination(fn);
}
}
type OutlinedResult = {
instrs: Array<Instruction>;
fn: HIRFunction;
};
function process(
fn: HIRFunction,
jsx: Array<JsxInstruction>,
globals: LoadGlobalMap,
): OutlinedResult | null {
/**
* In the future, add a check for backedge to outline jsx inside loops in a
* top level component. For now, only outline jsx in callbacks.
*/
if (fn.fnType === 'Component') {
return null;
}
const props = collectProps(jsx);
if (!props) return null;
const outlinedTag = fn.env.generateGloballyUniqueIdentifierName(null).value;
const newInstrs = emitOutlinedJsx(fn.env, jsx, props, outlinedTag);
if (!newInstrs) return null;
const outlinedFn = emitOutlinedFn(fn.env, jsx, props, globals);
if (!outlinedFn) return null;
outlinedFn.id = outlinedTag;
return {instrs: newInstrs, fn: outlinedFn};
}
type OutlinedJsxAttribute = {
originalName: string;
newName: string;
place: Place;
};
function collectProps(
instructions: Array<JsxInstruction>,
): Array<OutlinedJsxAttribute> | null {
let id = 1;
function generateName(oldName: string): string {
let newName = oldName;
while (seen.has(newName)) {
newName = `${oldName}${id++}`;
}
seen.add(newName);
return newName;
}
const attributes: Array<OutlinedJsxAttribute> = [];
const jsxIds = new Set(instructions.map(i => i.lvalue.identifier.id));
const seen: Set<string> = new Set();
for (const instr of instructions) {
const {value} = instr;
for (const at of value.props) {
if (at.kind === 'JsxSpreadAttribute') {
return null;
}
if (at.kind === 'JsxAttribute') {
const newName = generateName(at.name);
attributes.push({
originalName: at.name,
newName,
place: at.place,
});
}
}
if (value.children) {
for (const child of value.children) {
if (jsxIds.has(child.identifier.id)) {
continue;
}
promoteTemporary(child.identifier);
const newName = generateName('t');
attributes.push({
originalName: child.identifier.name!.value,
newName: newName,
place: child,
});
}
}
}
return attributes;
}
function emitOutlinedJsx(
env: Environment,
instructions: Array<Instruction>,
outlinedProps: Array<OutlinedJsxAttribute>,
outlinedTag: string,
): Array<Instruction> {
const props: Array<JsxAttribute> = outlinedProps.map(p => ({
kind: 'JsxAttribute',
name: p.newName,
place: p.place,
}));
const loadJsx: Instruction = {
id: makeInstructionId(0),
loc: GeneratedSource,
lvalue: createTemporaryPlace(env, GeneratedSource),
value: {
kind: 'LoadGlobal',
binding: {
kind: 'ModuleLocal',
name: outlinedTag,
},
loc: GeneratedSource,
},
};
promoteTemporaryJsxTag(loadJsx.lvalue.identifier);
const jsxExpr: Instruction = {
id: makeInstructionId(0),
loc: GeneratedSource,
lvalue: instructions.at(-1)!.lvalue,
value: {
kind: 'JsxExpression',
tag: {...loadJsx.lvalue},
props,
children: null,
loc: GeneratedSource,
openingLoc: GeneratedSource,
closingLoc: GeneratedSource,
},
};
return [loadJsx, jsxExpr];
}
function emitOutlinedFn(
env: Environment,
jsx: Array<JsxInstruction>,
oldProps: Array<OutlinedJsxAttribute>,
globals: LoadGlobalMap,
): HIRFunction | null {
const instructions: Array<Instruction> = [];
const oldToNewProps = createOldToNewPropsMapping(env, oldProps);
const propsObj: Place = createTemporaryPlace(env, GeneratedSource);
promoteTemporary(propsObj.identifier);
const destructurePropsInstr = emitDestructureProps(
env,
propsObj,
oldToNewProps,
);
instructions.push(destructurePropsInstr);
const updatedJsxInstructions = emitUpdatedJsx(jsx, oldToNewProps);
const loadGlobalInstrs = emitLoadGlobals(jsx, globals);
if (!loadGlobalInstrs) {
return null;
}
instructions.push(...loadGlobalInstrs);
instructions.push(...updatedJsxInstructions);
const block: BasicBlock = {
kind: 'block',
id: makeBlockId(0),
instructions,
terminal: {
id: makeInstructionId(0),
kind: 'return',
loc: GeneratedSource,
value: instructions.at(-1)!.lvalue,
},
preds: new Set(),
phis: new Set(),
};
const fn: HIRFunction = {
loc: GeneratedSource,
id: null,
fnType: 'Other',
env,
params: [propsObj],
returnTypeAnnotation: null,
returnType: makeType(),
context: [],
effects: null,
body: {
entry: block.id,
blocks: new Map([[block.id, block]]),
},
generator: false,
async: false,
directives: [],
};
return fn;
}
function emitLoadGlobals(
jsx: Array<JsxInstruction>,
globals: LoadGlobalMap,
): Array<Instruction> | null {
const instructions: Array<Instruction> = [];
for (const {value} of jsx) {
// Add load globals instructions for jsx tags
if (value.tag.kind === 'Identifier') {
const loadGlobalInstr = globals.get(value.tag.identifier.id);
if (!loadGlobalInstr) {
return null;
}
instructions.push(loadGlobalInstr);
}
}
return instructions;
}
function emitUpdatedJsx(
jsx: Array<JsxInstruction>,
oldToNewProps: Map<IdentifierId, OutlinedJsxAttribute>,
): Array<JsxInstruction> {
const newInstrs: Array<JsxInstruction> = [];
const jsxIds = new Set(jsx.map(i => i.lvalue.identifier.id));
for (const instr of jsx) {
const {value} = instr;
const newProps: Array<JsxAttribute> = [];
// Update old props references to use the newly destructured props param
for (const prop of value.props) {
invariant(
prop.kind === 'JsxAttribute',
`Expected only attributes but found ${prop.kind}`,
);
if (prop.name === 'key') {
continue;
}
const newProp = oldToNewProps.get(prop.place.identifier.id);
invariant(
newProp !== undefined,
`Expected a new property for ${printIdentifier(prop.place.identifier)}`,
);
newProps.push({
kind: 'JsxAttribute',
name: newProp.originalName,
place: newProp.place,
});
}
let newChildren: Array<Place> | null = null;
if (value.children) {
newChildren = [];
for (const child of value.children) {
if (jsxIds.has(child.identifier.id)) {
newChildren.push({...child});
continue;
}
const newChild = oldToNewProps.get(child.identifier.id);
invariant(
newChild !== undefined,
`Expected a new prop for ${printIdentifier(child.identifier)}`,
);
newChildren.push({...newChild.place});
}
}
newInstrs.push({
...instr,
value: {
...value,
props: newProps,
children: newChildren,
},
});
}
return newInstrs;
}
function createOldToNewPropsMapping(
env: Environment,
oldProps: Array<OutlinedJsxAttribute>,
): Map<IdentifierId, OutlinedJsxAttribute> {
const oldToNewProps = new Map();
for (const oldProp of oldProps) {
// Do not read key prop in the outlined component
if (oldProp.originalName === 'key') {
continue;
}
const newProp: OutlinedJsxAttribute = {
...oldProp,
place: createTemporaryPlace(env, GeneratedSource),
};
newProp.place.identifier.name = makeIdentifierName(oldProp.newName);
oldToNewProps.set(oldProp.place.identifier.id, newProp);
}
return oldToNewProps;
}
function emitDestructureProps(
env: Environment,
propsObj: Place,
oldToNewProps: Map<IdentifierId, OutlinedJsxAttribute>,
): Instruction {
const properties: Array<ObjectProperty> = [];
for (const [_, prop] of oldToNewProps) {
properties.push({
kind: 'ObjectProperty',
key: {
kind: 'string',
name: prop.newName,
},
type: 'property',
place: prop.place,
});
}
const destructurePropsInstr: Instruction = {
id: makeInstructionId(0),
lvalue: createTemporaryPlace(env, GeneratedSource),
loc: GeneratedSource,
value: {
kind: 'Destructure',
lvalue: {
pattern: {
kind: 'ObjectPattern',
properties,
},
kind: InstructionKind.Let,
},
loc: GeneratedSource,
value: propsObj,
},
};
return destructurePropsInstr;
}

View File

@@ -23,7 +23,7 @@ import {
removeUnnecessaryTryCatch,
removeUnreachableForUpdates,
} from '../HIR/HIRBuilder';
import {printIdentifier} from '../HIR/PrintHIR';
import {printPlace} from '../HIR/PrintHIR';
/*
* This pass prunes `maybe-throw` terminals for blocks that can provably *never* throw.
@@ -55,7 +55,7 @@ export function pruneMaybeThrows(fn: HIRFunction): void {
loc: GeneratedSource,
description: `Could not find mapping for predecessor bb${predecessor} in block bb${
block.id
} for phi ${printIdentifier(phi.id)}`,
} for phi ${printPlace(phi.place)}`,
suggestions: null,
});
phi.operands.delete(predecessor);

View File

@@ -8,3 +8,4 @@
export {constantPropagation} from './ConstantPropagation';
export {deadCodeElimination} from './DeadCodeElimination';
export {pruneMaybeThrows} from './PruneMaybeThrows';
export {inlineJsxTransform} from './InlineJsxTransform';

View File

@@ -34,6 +34,7 @@ import {
ReactiveInstruction,
ReactiveScope,
ReactiveScopeBlock,
ReactiveScopeDeclaration,
ReactiveScopeDependency,
ReactiveTerminal,
ReactiveValue,
@@ -572,7 +573,8 @@ function codegenReactiveScope(
const changeExpressions: Array<t.Expression> = [];
const changeExpressionComments: Array<string> = [];
const outputComments: Array<string> = [];
for (const dep of scope.dependencies) {
for (const dep of [...scope.dependencies].sort(compareScopeDependency)) {
const index = cx.nextCacheIndex;
changeExpressionComments.push(printDependencyComment(dep));
const comparison = t.binaryExpression(
@@ -615,7 +617,10 @@ function codegenReactiveScope(
);
}
let firstOutputIndex: number | null = null;
for (const [, {identifier}] of scope.declarations) {
for (const [, {identifier}] of [...scope.declarations].sort(([, a], [, b]) =>
compareScopeDeclaration(a, b),
)) {
const index = cx.nextCacheIndex;
if (firstOutputIndex === null) {
firstOutputIndex = index;
@@ -2566,3 +2571,45 @@ function convertIdentifier(identifier: Identifier): t.Identifier {
);
return t.identifier(identifier.name.value);
}
function compareScopeDependency(
a: ReactiveScopeDependency,
b: ReactiveScopeDependency,
): number {
CompilerError.invariant(
a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named',
{
reason: '[Codegen] Expected named identifier for dependency',
loc: a.identifier.loc,
},
);
const aName = [
a.identifier.name.value,
...a.path.map(entry => `${entry.optional ? '?' : ''}${entry.property}`),
].join('.');
const bName = [
b.identifier.name.value,
...b.path.map(entry => `${entry.optional ? '?' : ''}${entry.property}`),
].join('.');
if (aName < bName) return -1;
else if (aName > bName) return 1;
else return 0;
}
function compareScopeDeclaration(
a: ReactiveScopeDeclaration,
b: ReactiveScopeDeclaration,
): number {
CompilerError.invariant(
a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named',
{
reason: '[Codegen] Expected named identifier for declaration',
loc: a.identifier.loc,
},
);
const aName = a.identifier.name.value;
const bName = b.identifier.name.value;
if (aName < bName) return -1;
else if (aName > bName) return 1;
else return 0;
}

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

@@ -281,22 +281,25 @@ export function findDisjointMutableValues(
*/
for (const phi of block.phis) {
if (
phi.id.mutableRange.start + 1 !== phi.id.mutableRange.end &&
phi.id.mutableRange.end >
phi.place.identifier.mutableRange.start + 1 !==
phi.place.identifier.mutableRange.end &&
phi.place.identifier.mutableRange.end >
(block.instructions.at(0)?.id ?? block.terminal.id)
) {
const operands = [phi.id];
const declaration = declarations.get(phi.id.declarationId);
const operands = [phi.place.identifier];
const declaration = declarations.get(
phi.place.identifier.declarationId,
);
if (declaration !== undefined) {
operands.push(declaration);
}
for (const [_, phiId] of phi.operands) {
operands.push(phiId);
operands.push(phiId.identifier);
}
scopeIdentifiers.union(operands);
} else if (fn.env.config.enableForest) {
for (const [, phiId] of phi.operands) {
scopeIdentifiers.union([phi.id, phiId]);
scopeIdentifiers.union([phi.place.identifier, phiId.identifier]);
}
}
}

View File

@@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh
export {printReactiveFunction} from './PrintReactiveFunction';
export {promoteUsedTemporaries} from './PromoteUsedTemporaries';
export {propagateEarlyReturns} from './PropagateEarlyReturns';
export {propagateScopeDependencies} from './PropagateScopeDependencies';
export {pruneAllReactiveScopes} from './PruneAllReactiveScopes';
export {pruneHoistedContexts} from './PruneHoistedContexts';
export {pruneNonEscapingScopes} from './PruneNonEscapingScopes';

View File

@@ -68,18 +68,13 @@ export function eliminateRedundantPhi(
// Find any redundant phis
phis: for (const phi of block.phis) {
// Remap phis in case operands are from eliminated phis
phi.operands = new Map(
Array.from(phi.operands).map(([block, id]) => [
block,
rewrites.get(id) ?? id,
]),
);
phi.operands.forEach((place, _) => rewritePlace(place, rewrites));
// Find if the phi can be eliminated
let same: Identifier | null = null;
for (const [_, operand] of phi.operands) {
if (
(same !== null && operand.id === same.id) ||
operand.id === phi.id.id
(same !== null && operand.identifier.id === same.id) ||
operand.identifier.id === phi.place.identifier.id
) {
/*
* This operand is the same as the phi or is the same as the
@@ -94,7 +89,7 @@ export function eliminateRedundantPhi(
continue phis;
} else {
// First non-phi operand
same = operand;
same = operand.identifier;
}
}
CompilerError.invariant(same !== null, {
@@ -103,7 +98,7 @@ export function eliminateRedundantPhi(
loc: null,
suggestions: null,
});
rewrites.set(phi.id, same);
rewrites.set(phi.place.identifier, same);
block.phis.delete(phi);
}

View File

@@ -18,7 +18,7 @@ import {
Phi,
Place,
} from '../HIR/HIR';
import {printIdentifier} from '../HIR/PrintHIR';
import {printIdentifier, printPlace} from '../HIR/PrintHIR';
import {
eachTerminalSuccessor,
mapInstructionLValues,
@@ -27,8 +27,8 @@ import {
} from '../HIR/visitors';
type IncompletePhi = {
oldId: Identifier;
newId: Identifier;
oldPlace: Place;
newPlace: Place;
};
type State = {
@@ -122,33 +122,33 @@ class SSABuilder {
}
getPlace(oldPlace: Place): Place {
const newId = this.getIdAt(oldPlace.identifier, this.#current!.id);
const newId = this.getIdAt(oldPlace, this.#current!.id);
return {
...oldPlace,
identifier: newId,
};
}
getIdAt(oldId: Identifier, blockId: BlockId): Identifier {
getIdAt(oldPlace: Place, blockId: BlockId): Identifier {
// check if Place is defined locally
const block = this.#blocks.get(blockId)!;
const state = this.#states.get(block)!;
if (state.defs.has(oldId)) {
return state.defs.get(oldId)!;
if (state.defs.has(oldPlace.identifier)) {
return state.defs.get(oldPlace.identifier)!;
}
if (block.preds.size == 0) {
/*
* We're at the entry block and haven't found our defintion yet.
* console.log(
* `Unable to find "${printIdentifier(
* oldId
* `Unable to find "${printPlace(
* oldPlace
* )}" in bb${blockId}, assuming it's a global`
* );
*/
this.#unknown.add(oldId);
return oldId;
this.#unknown.add(oldPlace.identifier);
return oldPlace.identifier;
}
if (this.unsealedPreds.get(block)! > 0) {
@@ -156,52 +156,55 @@ class SSABuilder {
* We haven't visited all our predecessors, let's place an incomplete phi
* for now.
*/
const newId = this.makeId(oldId);
state.incompletePhis.push({oldId, newId});
state.defs.set(oldId, newId);
const newId = this.makeId(oldPlace.identifier);
state.incompletePhis.push({
oldPlace,
newPlace: {...oldPlace, identifier: newId},
});
state.defs.set(oldPlace.identifier, newId);
return newId;
}
// Only one predecessor, let's check there
if (block.preds.size == 1) {
const [pred] = block.preds;
const newId = this.getIdAt(oldId, pred);
state.defs.set(oldId, newId);
const newId = this.getIdAt(oldPlace, pred);
state.defs.set(oldPlace.identifier, newId);
return newId;
}
// There are multiple predecessors, we may need a phi.
const newId = this.makeId(oldId);
const newId = this.makeId(oldPlace.identifier);
/*
* Adding a phi may loop back to our block if there is a loop in the CFG. We
* update our defs before adding the phi to terminate the recursion rather than
* looping infinitely.
*/
state.defs.set(oldId, newId);
return this.addPhi(block, oldId, newId);
state.defs.set(oldPlace.identifier, newId);
return this.addPhi(block, oldPlace, {...oldPlace, identifier: newId});
}
addPhi(block: BasicBlock, oldId: Identifier, newId: Identifier): Identifier {
const predDefs: Map<BlockId, Identifier> = new Map();
addPhi(block: BasicBlock, oldPlace: Place, newPlace: Place): Identifier {
const predDefs: Map<BlockId, Place> = new Map();
for (const predBlockId of block.preds) {
const predId = this.getIdAt(oldId, predBlockId);
predDefs.set(predBlockId, predId);
const predId = this.getIdAt(oldPlace, predBlockId);
predDefs.set(predBlockId, {...oldPlace, identifier: predId});
}
const phi: Phi = {
kind: 'Phi',
id: newId,
place: newPlace,
operands: predDefs,
};
block.phis.add(phi);
return newId;
return newPlace.identifier;
}
fixIncompletePhis(block: BasicBlock): void {
const state = this.#states.get(block)!;
for (const phi of state.incompletePhis) {
this.addPhi(block, phi.oldId, phi.newId);
this.addPhi(block, phi.oldPlace, phi.newPlace);
}
}
@@ -223,9 +226,9 @@ class SSABuilder {
for (const incompletePhi of state.incompletePhis) {
text.push(
` iphi \$${printIdentifier(
incompletePhi.newId,
)} = \$${printIdentifier(incompletePhi.oldId)}`,
` iphi \$${printPlace(
incompletePhi.newPlace,
)} = \$${printPlace(incompletePhi.oldPlace)}`,
);
}
}

View File

@@ -69,7 +69,7 @@ export function inferTypes(func: HIRFunction): void {
function apply(func: HIRFunction, unifier: Unifier): void {
for (const [_, block] of func.body.blocks) {
for (const phi of block.phis) {
phi.id.type = unifier.get(phi.id.type);
phi.place.identifier.type = unifier.get(phi.place.identifier.type);
}
for (const instr of block.instructions) {
for (const operand of eachInstructionLValue(instr)) {
@@ -127,9 +127,9 @@ function* generate(
const returnTypes: Array<Type> = [];
for (const [_, block] of func.body.blocks) {
for (const phi of block.phis) {
yield equation(phi.id.type, {
yield equation(phi.place.identifier.type, {
kind: 'Phi',
operands: [...phi.operands.values()].map(id => id.type),
operands: [...phi.operands.values()].map(id => id.identifier.type),
});
}

View File

@@ -62,21 +62,24 @@ export function propagatePhiTypes(fn: HIRFunction): void {
* We also don't propagate scopes for named variables, to preserve compatibility
* with previous LeaveSSA behavior.
*/
if (phi.id.type.kind !== 'Type' || phi.id.name !== null) {
if (
phi.place.identifier.type.kind !== 'Type' ||
phi.place.identifier.name !== null
) {
continue;
}
let type: Type | null = null;
for (const [, operand] of phi.operands) {
if (type === null) {
type = operand.type;
} else if (!typeEquals(type, operand.type)) {
type = operand.identifier.type;
} else if (!typeEquals(type, operand.identifier.type)) {
type = null;
break;
}
}
if (type !== null) {
phi.id.type = type;
propagated.add(phi.id.id);
phi.place.identifier.type = type;
propagated.add(phi.place.identifier.id);
}
}
for (const instr of block.instructions) {

View File

@@ -82,6 +82,17 @@ export function getOrInsertDefault<U, V>(
return defaultValue;
}
}
export function Set_equal<T>(a: ReadonlySet<T>, b: ReadonlySet<T>): boolean {
if (a.size !== b.size) {
return false;
}
for (const item of a) {
if (!b.has(item)) {
return false;
}
}
return true;
}
export function Set_union<T>(a: ReadonlySet<T>, b: ReadonlySet<T>): Set<T> {
const union = new Set<T>(a);
@@ -128,6 +139,19 @@ export function nonNull<T extends NonNullable<U>, U>(
return value != null;
}
export function Set_filter<T>(
source: ReadonlySet<T>,
fn: (arg: T) => boolean,
): Set<T> {
const result = new Set<T>();
for (const entry of source) {
if (fn(entry)) {
result.add(entry);
}
}
return result;
}
export function hasNode<T>(
input: NodePath<T | null | undefined>,
): input is NodePath<NonNullable<T>> {

View File

@@ -198,11 +198,12 @@ export function validateHooksUsage(fn: HIRFunction): void {
for (const [, block] of fn.body.blocks) {
for (const phi of block.phis) {
let kind: Kind =
phi.id.name !== null && isHookName(phi.id.name.value)
phi.place.identifier.name !== null &&
isHookName(phi.place.identifier.name.value)
? Kind.PotentialHook
: Kind.Local;
for (const [, operand] of phi.operands) {
const operandKind = valueKinds.get(operand.id);
const operandKind = valueKinds.get(operand.identifier.id);
/*
* NOTE: we currently skip operands whose value is unknown
* (which can only occur for functions with loops), we may
@@ -213,7 +214,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
kind = joinKinds(kind, operandKind);
}
}
valueKinds.set(phi.id.id, kind);
valueKinds.set(phi.place.identifier.id, kind);
}
for (const instr of block.instructions) {
switch (instr.value.kind) {

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