Compare commits

..

41 Commits

Author SHA1 Message Date
Sebastian Markbage
bbed0b0ee6 Bring ReactFlightClient fixes to FlightReplyServer
We did a bunch of refactors to ReactFlightClient in PRs like #29823 and #33664.

This brings a bunch of those related refactors to the equivalent FlightReplyServer. Such as deep resolution of cycles and deferred error handling.
2025-12-03 10:00:46 +01:00
Sebastian Sebbie Silbermann
f3e3f6fbac Version 19.0.1 2025-12-02 23:12:51 +01:00
Sebastian "Sebbie" Silbermann
154f4b15e2 [release] Allow building single release channel with processed versions (#35270) 2025-12-02 22:54:07 +01: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
393 changed files with 5730 additions and 12022 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

@@ -31,6 +31,8 @@ jobs:
with:
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
@@ -63,6 +65,8 @@ jobs:
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4

View File

@@ -53,6 +53,8 @@ jobs:
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: node ./scripts/tasks/flow-ci ${{ matrix.flow_inline_config_shortname }}
@@ -73,6 +75,8 @@ jobs:
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: |
yarn generate-inline-fizz-runtime
@@ -95,6 +99,8 @@ jobs:
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn flags
@@ -144,6 +150,8 @@ jobs:
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
@@ -173,6 +181,8 @@ jobs:
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci
env:
@@ -243,6 +253,8 @@ jobs:
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -271,6 +283,8 @@ jobs:
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -314,6 +328,8 @@ jobs:
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -345,6 +361,8 @@ jobs:
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -373,6 +391,8 @@ jobs:
with:
path: "**/node_modules"
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
@@ -413,6 +433,8 @@ jobs:
with:
path: "**/node_modules"
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
@@ -469,6 +491,8 @@ jobs:
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -515,6 +539,8 @@ jobs:
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -547,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
@@ -586,6 +614,8 @@ jobs:
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build for PR
uses: actions/download-artifact@v4

View File

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

View File

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

View File

@@ -30,6 +30,8 @@ jobs:
with:
path: "**/node_modules"
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
@@ -48,6 +50,8 @@ jobs:
with:
path: "**/node_modules"
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
@@ -66,6 +70,8 @@ jobs:
with:
path: "**/node_modules"
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
@@ -84,5 +90,7 @@ jobs:
with:
path: "**/node_modules"
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

@@ -7,18 +7,18 @@
//
// The @latest channel uses the version as-is, e.g.:
//
// 19.0.0
// 19.0.1
//
// The @canary channel appends additional information, with the scheme
// <version>-<label>-<commit_sha>, e.g.:
//
// 19.0.0-canary-a1c2d3e4
// 19.0.1-canary-a1c2d3e4
//
// The @experimental channel doesn't include a version, only a date and a sha, e.g.:
//
// 0.0.0-experimental-241c4467e-20200129
const ReactVersion = '19.0.0';
const ReactVersion = '19.0.1';
// The label used by the @canary channel. Represents the upcoming release's
// stability. Most of the time, this will be "canary", but we may temporarily
@@ -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

@@ -14,7 +14,7 @@ import {
CompilerErrorDetail,
Effect,
ErrorSeverity,
parseConfigPragma,
parseConfigPragmaForTests,
ValueKind,
runPlayground,
type Hook,
@@ -208,7 +208,7 @@ 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);
const config = parseConfigPragmaForTests(pragma);
for (const fn of parseFunctions(source, language)) {
const id = withIdentifier(getFunctionIdentifier(fn));

View File

View File

@@ -1 +0,0 @@
Reference library compiled with React Compiler.

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,50 +0,0 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
```js
// eslint.config.js
import react from 'eslint-plugin-react'
export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
})
```

View File

@@ -1,28 +0,0 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{ignores: ['dist']},
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{allowConstantExport: true},
],
},
}
);

View File

@@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -1,32 +0,0 @@
{
"name": "app-18",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-compiler-runtime": "19.0.0-beta-9ee70a1-20241017",
"react-dom": "^18.3.1",
"runtime-compat-lib": "file:../lib"
},
"devDependencies": {
"@eslint/js": "^9.11.1",
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.2",
"babel-plugin-react-compiler": "19.0.0-beta-9ee70a1-20241017",
"eslint": "^9.11.1",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.12",
"globals": "^15.9.0",
"typescript": "^5.5.3",
"typescript-eslint": "^8.7.0",
"vite": "^5.4.8"
}
}

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,42 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View File

@@ -1,16 +0,0 @@
import 'react';
import './App.css';
// @ts-expect-error no types
import {useTime} from 'runtime-compat-lib';
function App() {
const time = useTime();
return (
<>
<h1>React 18</h1>
<span>Current time: {time.toLocaleString()}</span>
</>
);
}
export default App;

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -1,68 +0,0 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@@ -1,10 +0,0 @@
import {StrictMode} from 'react';
import {createRoot} from 'react-dom/client';
import App from './App.tsx';
import './index.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);

View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@@ -1,24 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}

View File

@@ -1 +0,0 @@
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts"],"version":"5.6.3"}

View File

@@ -1,7 +0,0 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

View File

@@ -1,22 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1 +0,0 @@
{"root":["./vite.config.ts"],"version":"5.6.3"}

View File

@@ -1,11 +0,0 @@
import {defineConfig} from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react({
babel: {plugins: [['babel-plugin-react-compiler', {target: '18'}]]},
}),
],
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,50 +0,0 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
```js
// eslint.config.js
import react from 'eslint-plugin-react'
export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
})
```

View File

@@ -1,28 +0,0 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{ignores: ['dist']},
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{allowConstantExport: true},
],
},
}
);

View File

@@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -1,31 +0,0 @@
{
"name": "app-19",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"react": "19.0.0-beta-26f2496093-20240514",
"react-dom": "19.0.0-beta-26f2496093-20240514",
"runtime-compat-lib": "file:../lib"
},
"devDependencies": {
"@eslint/js": "^9.11.1",
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.2",
"babel-plugin-react-compiler": "19.0.0-beta-9ee70a1-20241017",
"eslint": "^9.11.1",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.12",
"globals": "^15.9.0",
"typescript": "^5.5.3",
"typescript-eslint": "^8.7.0",
"vite": "^5.4.8"
}
}

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,42 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View File

@@ -1,16 +0,0 @@
import 'react';
import './App.css';
// @ts-expect-error no types
import {useTime} from 'runtime-compat-lib';
function App() {
const time = useTime();
return (
<>
<h1>React 19</h1>
<span>Current time: {time.toLocaleString()}</span>
</>
);
}
export default App;

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -1,68 +0,0 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@@ -1,10 +0,0 @@
import {StrictMode} from 'react';
import {createRoot} from 'react-dom/client';
import App from './App.tsx';
import './index.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);

View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@@ -1,24 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}

View File

@@ -1 +0,0 @@
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts"],"version":"5.6.3"}

View File

@@ -1,7 +0,0 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

View File

@@ -1,22 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1 +0,0 @@
{"root":["./vite.config.ts"],"version":"5.6.3"}

View File

@@ -1,11 +0,0 @@
import {defineConfig} from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react({
babel: {plugins: [['babel-plugin-react-compiler', {target: '19'}]]},
}),
],
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +0,0 @@
const plugins = [
[
'babel-plugin-react-compiler',
{
target: '18',
},
],
];
module.exports = {plugins};

View File

@@ -1,13 +0,0 @@
import {useState, useEffect} from 'react';
export function useTime() {
const [time, setTime] = useState(() => new Date());
useEffect(() => {
const id = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(id);
}, []);
return time;
}

View File

@@ -1,32 +0,0 @@
{
"name": "runtime-compat-lib",
"version": "0.0.0",
"description": "Testing ground for libraries compiled with React Compiler",
"main": "dist/index.js",
"scripts": {
"build": "rimraf dist && rollup --config --bundleConfigAsCjs",
"test": "echo 'no tests'"
},
"license": "MIT",
"devDependencies": {
"@babel/cli": "^7.25.7",
"@babel/core": "^7.25.7",
"@babel/preset-env": "^7.25.7",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-json": "^6.1.0",
"babel-plugin-react-compiler": "19.0.0-beta-9ee70a1-20241017",
"@rollup/plugin-terser": "^0.4.4",
"react": "19.0.0-beta-26f2496093-20240514",
"react-dom": "19.0.0-beta-26f2496093-20240514",
"rimraf": "5",
"rollup": "^4.22.4",
"rollup-plugin-banner2": "^1.2.3",
"rollup-plugin-prettier": "^4.1.1"
},
"dependencies": {
"react-compiler-runtime": "19.0.0-beta-9ee70a1-20241017"
},
"peerDependencies": {
"react": "^18 || ^19"
}
}

View File

@@ -1,51 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import json from '@rollup/plugin-json';
import terser from '@rollup/plugin-terser';
import prettier from 'rollup-plugin-prettier';
import banner2 from 'rollup-plugin-banner2';
import babel from '@rollup/plugin-babel';
const ROLLUP_CONFIG = {
input: 'index.js',
output: {
file: 'dist/index.js',
format: 'esm',
sourcemap: false,
exports: 'named',
},
plugins: [
json(),
babel({babelHelpers: 'bundled'}),
terser({
format: {
comments: false,
},
compress: false,
mangle: false,
}),
prettier(),
banner2(
() => `/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @lightSyntaxTransform
* @noflow
* @nolint
* @preventMunge
* @preserve-invariant-messages
*/
`
),
],
};
export default ROLLUP_CONFIG;

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
#!/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 lib && yarn --silent link
cd $HERE/app-18 && yarn --silent link runtime-compat-lib
cd $HERE/app-19 && yarn --silent link runtime-compat-lib

View File

@@ -57,7 +57,6 @@ import {
mergeReactiveScopesThatInvalidateTogether,
promoteUsedTemporaries,
propagateEarlyReturns,
propagateScopeDependencies,
pruneHoistedContexts,
pruneNonEscapingScopes,
pruneNonReactiveDependencies,
@@ -348,14 +347,12 @@ function* runWithEnvironment(
});
assertTerminalSuccessorsExist(hir);
assertTerminalPredsExist(hir);
if (env.config.enablePropagateDepsInHIR) {
propagateScopeDependenciesHIR(hir);
yield log({
kind: 'hir',
name: 'PropagateScopeDependenciesHIR',
value: hir,
});
}
propagateScopeDependenciesHIR(hir);
yield log({
kind: 'hir',
name: 'PropagateScopeDependenciesHIR',
value: hir,
});
if (env.config.inlineJsxTransform) {
inlineJsxTransform(hir, env.config.inlineJsxTransform);
@@ -383,15 +380,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',

View File

@@ -1,5 +1,6 @@
import {CompilerError} from '../CompilerError';
import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables';
import {printDependency} from '../ReactiveScopes/PrintReactiveFunction';
import {
Set_equal,
Set_filter,
@@ -7,7 +8,6 @@ import {
Set_union,
getOrInsertDefault,
} from '../Utils/utils';
import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies';
import {
BasicBlock,
BlockId,
@@ -21,7 +21,8 @@ import {
ReactiveScopeDependency,
ScopeId,
} from './HIR';
import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR';
const DEBUG_PRINT = false;
/**
* Helper function for `PropagateScopeDependencies`. Uses control flow graph
@@ -86,15 +87,8 @@ export function collectHoistablePropertyLoads(
fn: HIRFunction,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
hoistableFromOptionals: ReadonlyMap<BlockId, ReactiveScopeDependency>,
nestedFnImmutableContext: ReadonlySet<IdentifierId> | null,
): ReadonlyMap<BlockId, BlockInfo> {
const registry = new PropertyPathRegistry();
const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn);
const actuallyEvaluatedTemporaries = new Map(
[...temporaries].filter(([id]) => !functionExpressionLoads.has(id)),
);
/**
* 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
@@ -111,14 +105,51 @@ export function collectHoistablePropertyLoads(
}
}
}
const nodes = collectNonNullsInBlocks(fn, {
temporaries: actuallyEvaluatedTemporaries,
return collectHoistablePropertyLoadsImpl(fn, {
temporaries,
knownImmutableIdentifiers,
hoistableFromOptionals,
registry,
nestedFnImmutableContext,
nestedFnImmutableContext: null,
});
propagateNonNull(fn, nodes, registry);
}
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;
}
@@ -243,7 +274,7 @@ class PropertyPathRegistry {
function getMaybeNonNullInInstruction(
instr: InstructionValue,
context: CollectNonNullsInBlocksContext,
context: CollectHoistablePropertyLoadsContext,
): PropertyPathNode | null {
let path = null;
if (instr.kind === 'PropertyLoad') {
@@ -262,7 +293,7 @@ function getMaybeNonNullInInstruction(
function isImmutableAtInstr(
identifier: Identifier,
instr: InstructionId,
context: CollectNonNullsInBlocksContext,
context: CollectHoistablePropertyLoadsContext,
): boolean {
if (context.nestedFnImmutableContext != null) {
/**
@@ -295,22 +326,9 @@ function isImmutableAtInstr(
}
}
type CollectNonNullsInBlocksContext = {
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 collectNonNullsInBlocks(
fn: HIRFunction,
context: CollectNonNullsInBlocksContext,
context: CollectHoistablePropertyLoadsContext,
): ReadonlyMap<BlockId, BlockInfo> {
/**
* Known non-null objects such as functional component props can be safely
@@ -348,27 +366,25 @@ function collectNonNullsInBlocks(
assumedNonNullObjects.add(maybeNonNull);
}
if (
instr.value.kind === 'FunctionExpression' &&
(instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod') &&
!fn.env.config.enableTreatFunctionDepsAsConditional
) {
const innerFn = instr.value.loweredFunc;
const innerTemporaries = collectTemporariesSidemap(
const innerHoistableMap = collectHoistablePropertyLoadsImpl(
innerFn.func,
new Set(),
);
const innerOptionals = collectOptionalChainSidemap(innerFn.func);
const innerHoistableMap = collectHoistablePropertyLoads(
innerFn.func,
innerTemporaries,
innerOptionals.hoistableObjects,
context.nestedFnImmutableContext ??
new Set(
innerFn.func.context
.filter(place =>
isImmutableAtInstr(place.identifier, instr.id, context),
)
.map(place => place.identifier.id),
),
{
...context,
nestedFnImmutableContext:
context.nestedFnImmutableContext ??
new Set(
innerFn.func.context
.filter(place =>
isImmutableAtInstr(place.identifier, instr.id, context),
)
.map(place => place.identifier.id),
),
},
);
const innerHoistables = assertNonNull(
innerHoistableMap.get(innerFn.func.body.entry),
@@ -591,7 +607,10 @@ function collectFunctionExpressionFakeLoads(
for (const [_, block] of fn.body.blocks) {
for (const {lvalue, value} of block.instructions) {
if (value.kind === 'FunctionExpression') {
if (
value.kind === 'FunctionExpression' ||
value.kind === 'ObjectMethod'
) {
for (const reference of value.loweredFunc.dependencies) {
let curr: IdentifierId | undefined = reference.identifier.id;
while (curr != null) {

View File

@@ -3,7 +3,6 @@ import {assertNonNull} from './CollectHoistablePropertyLoads';
import {
BlockId,
BasicBlock,
InstructionId,
IdentifierId,
ReactiveScopeDependency,
BranchTerminal,
@@ -15,6 +14,8 @@ import {
OptionalTerminal,
HIRFunction,
DependencyPathEntry,
Instruction,
Terminal,
} from './HIR';
import {printIdentifier} from './PrintHIR';
@@ -22,25 +23,14 @@ 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(),
};
for (const [_, block] of fn.body.blocks) {
if (
block.terminal.kind === 'optional' &&
!context.seenOptionals.has(block.id)
) {
traverseOptionalBlock(
block as TBasicBlock<OptionalTerminal>,
context,
null,
);
}
}
traverseFunction(fn, context);
return {
temporariesReadInOptional: context.temporariesReadInOptional,
processedInstrsInOptional: context.processedInstrsInOptional,
@@ -96,8 +86,10 @@ export type OptionalChainSidemap = {
* 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<InstructionId>;
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
@@ -115,16 +107,46 @@ export type OptionalChainSidemap = {
};
type OptionalTraversalContext = {
currFn: HIRFunction;
blocks: ReadonlyMap<BlockId, BasicBlock>;
// Track optional blocks to avoid outer calls into nested optionals
seenOptionals: Set<BlockId>;
processedInstrsInOptional: Set<InstructionId>;
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
@@ -137,7 +159,7 @@ function matchOptionalTestBlock(
consequentId: IdentifierId;
property: string;
propertyId: IdentifierId;
storeLocalInstrId: InstructionId;
storeLocalInstr: Instruction;
consequentGoto: BlockId;
} | null {
const consequentBlock = assertNonNull(blocks.get(terminal.consequent));
@@ -149,7 +171,7 @@ function matchOptionalTestBlock(
const propertyLoad: TInstruction<PropertyLoad> = consequentBlock
.instructions[0] as TInstruction<PropertyLoad>;
const storeLocal: StoreLocal = consequentBlock.instructions[1].value;
const storeLocalInstrId = consequentBlock.instructions[1].id;
const storeLocalInstr = consequentBlock.instructions[1];
CompilerError.invariant(
propertyLoad.value.object.identifier.id === terminal.test.identifier.id,
{
@@ -189,7 +211,7 @@ function matchOptionalTestBlock(
consequentId: storeLocal.lvalue.place.identifier.id,
property: propertyLoad.value.property,
propertyId: propertyLoad.lvalue.identifier.id,
storeLocalInstrId,
storeLocalInstr,
consequentGoto: consequentBlock.terminal.block,
};
}
@@ -369,10 +391,8 @@ function traverseOptionalBlock(
},
],
};
context.processedInstrsInOptional.add(
matchConsequentResult.storeLocalInstrId,
);
context.processedInstrsInOptional.add(test.id);
context.processedInstrsInOptional.add(matchConsequentResult.storeLocalInstr);
context.processedInstrsInOptional.add(test);
context.temporariesReadInOptional.set(
matchConsequentResult.consequentId,
load,

View File

@@ -69,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,
@@ -147,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
@@ -231,16 +231,6 @@ const EnvironmentConfigSchema = z.object({
*/
enableUseTypeAnnotations: z.boolean().default(false),
enablePropagateDepsInHIR: z.boolean().default(false),
/**
* Enables inference of optional dependency chains. Without this flag
* a property chain such as `props?.items?.foo` will infer as a dep on
* just `props`. With this flag enabled, we'll infer that full path as
* the dependency.
*/
enableOptionalDependencies: z.boolean().default(true),
/**
* Enables inlining ReactElement object literals in place of JSX
* An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime
@@ -248,7 +238,7 @@ const EnvironmentConfigSchema = z.object({
*
* The symbol configuration is set for backwards compatability with pre-React 19 transforms
*/
inlineJsxTransform: ReactElementSymbolSchema.nullish(),
inlineJsxTransform: ReactElementSymbolSchema.nullable().default(null),
/*
* Enable validation of hooks to partially check that the component honors the rules of hooks.
@@ -339,9 +329,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
@@ -425,7 +415,7 @@ const EnvironmentConfigSchema = z.object({
* }
*
*/
enableEmitInstrumentForget: InstrumentationSchema.nullish(),
enableEmitInstrumentForget: InstrumentationSchema.nullable().default(null),
// Enable validation of mutable ranges
assertValidMutableRanges: z.boolean().default(false),
@@ -464,8 +454,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
@@ -503,7 +491,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
@@ -543,7 +532,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
@@ -565,12 +554,57 @@ 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',
},
};
/**
* 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({});
@@ -580,21 +614,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;
}
@@ -609,7 +634,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;
@@ -620,11 +644,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);

View File

@@ -16,6 +16,7 @@ import {
DeclarationId,
areEqualPaths,
IdentifierId,
Terminal,
} from './HIR';
import {
collectHoistablePropertyLoads,
@@ -46,7 +47,7 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void {
const hoistablePropertyLoads = keyByScopeId(
fn,
collectHoistablePropertyLoads(fn, temporaries, hoistableObjects, null),
collectHoistablePropertyLoads(fn, temporaries, hoistableObjects),
);
const scopeDeps = collectDependencies(
@@ -176,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
@@ -215,7 +218,29 @@ 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;
@@ -224,27 +249,51 @@ export function collectTemporariesSidemap(
);
if (value.kind === 'PropertyLoad' && !usedOutside) {
const property = getProperty(
value.object,
value.property,
false,
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(
@@ -310,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>,
@@ -360,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);
}
@@ -575,7 +641,7 @@ function collectDependencies(
fn: HIRFunction,
usedOutsideDeclaringScope: ReadonlySet<DeclarationId>,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
processedInstrsInOptional: ReadonlySet<InstructionId>,
processedInstrsInOptional: ReadonlySet<Instruction | Terminal>,
): Map<ReactiveScope, Array<ReactiveScopeDependency>> {
const context = new Context(usedOutsideDeclaringScope, temporaries);
@@ -614,12 +680,12 @@ function collectDependencies(
}
}
for (const instr of block.instructions) {
if (!processedInstrsInOptional.has(instr.id)) {
if (!processedInstrsInOptional.has(instr)) {
handleInstruction(instr, context);
}
}
if (!processedInstrsInOptional.has(block.terminal.id)) {
if (!processedInstrsInOptional.has(block.terminal)) {
for (const place of eachTerminalOperand(block.terminal)) {
context.visitOperand(place);
}

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

@@ -383,6 +383,30 @@ export function inlineJsxTransform(
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,
});
}
}
}
}
/**
@@ -697,10 +721,10 @@ function handlePlace(
inlinedJsxDeclaration == null ||
inlinedJsxDeclaration.blockIdsToIgnore.has(blockId)
) {
return {...place};
return place;
}
return {...place, identifier: {...inlinedJsxDeclaration.identifier}};
return {...place, identifier: inlinedJsxDeclaration.identifier};
}
function handlelValue(
@@ -715,8 +739,20 @@ function handlelValue(
inlinedJsxDeclaration == null ||
inlinedJsxDeclaration.blockIdsToIgnore.has(blockId)
) {
return {...lvalue};
return lvalue;
}
return {...lvalue, identifier: {...inlinedJsxDeclaration.identifier}};
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

@@ -210,12 +210,30 @@ function process(
return {instrs: newInstrs, fn: outlinedFn};
}
type OutlinedJsxAttribute = {
originalName: string;
newName: string;
place: Place;
};
function collectProps(
instructions: Array<JsxInstruction>,
): Array<JsxAttribute> | null {
const attributes: Array<JsxAttribute> = [];
): 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;
@@ -224,26 +242,30 @@ function collectProps(
return null;
}
/*
* TODO(gsn): Handle attributes that have same value across
* the outlined jsx instructions.
*/
if (seen.has(at.name)) {
return null;
}
if (at.kind === 'JsxAttribute') {
seen.add(at.name);
attributes.push(at);
const newName = generateName(at.name);
attributes.push({
originalName: at.name,
newName,
place: at.place,
});
}
}
// TODO(gsn): Add support for children that are not jsx expressions
if (
value.children &&
value.children.some(child => !jsxIds.has(child.identifier.id))
) {
return null;
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;
@@ -252,9 +274,15 @@ function collectProps(
function emitOutlinedJsx(
env: Environment,
instructions: Array<Instruction>,
props: Array<JsxAttribute>,
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,
@@ -290,7 +318,7 @@ function emitOutlinedJsx(
function emitOutlinedFn(
env: Environment,
jsx: Array<JsxInstruction>,
oldProps: Array<JsxAttribute>,
oldProps: Array<OutlinedJsxAttribute>,
globals: LoadGlobalMap,
): HIRFunction | null {
const instructions: Array<Instruction> = [];
@@ -299,9 +327,11 @@ function emitOutlinedFn(
const propsObj: Place = createTemporaryPlace(env, GeneratedSource);
promoteTemporary(propsObj.identifier);
const destructurePropsInstr = emitDestructureProps(env, propsObj, [
...oldToNewProps.values(),
]);
const destructurePropsInstr = emitDestructureProps(
env,
propsObj,
oldToNewProps,
);
instructions.push(destructurePropsInstr);
const updatedJsxInstructions = emitUpdatedJsx(jsx, oldToNewProps);
@@ -368,9 +398,10 @@ function emitLoadGlobals(
function emitUpdatedJsx(
jsx: Array<JsxInstruction>,
oldToNewProps: Map<IdentifierId, ObjectProperty>,
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;
@@ -390,16 +421,36 @@ function emitUpdatedJsx(
`Expected a new property for ${printIdentifier(prop.place.identifier)}`,
);
newProps.push({
...prop,
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,
},
});
}
@@ -409,31 +460,21 @@ function emitUpdatedJsx(
function createOldToNewPropsMapping(
env: Environment,
oldProps: Array<JsxAttribute>,
): Map<IdentifierId, ObjectProperty> {
oldProps: Array<OutlinedJsxAttribute>,
): Map<IdentifierId, OutlinedJsxAttribute> {
const oldToNewProps = new Map();
for (const oldProp of oldProps) {
invariant(
oldProp.kind === 'JsxAttribute',
`Expected only attributes but found ${oldProp.kind}`,
);
// Do not read key prop in the outlined component
if (oldProp.name === 'key') {
if (oldProp.originalName === 'key') {
continue;
}
const newProp: ObjectProperty = {
kind: 'ObjectProperty',
key: {
kind: 'string',
name: oldProp.name,
},
type: 'property',
const newProp: OutlinedJsxAttribute = {
...oldProp,
place: createTemporaryPlace(env, GeneratedSource),
};
newProp.place.identifier.name = makeIdentifierName(oldProp.name);
newProp.place.identifier.name = makeIdentifierName(oldProp.newName);
oldToNewProps.set(oldProp.place.identifier.id, newProp);
}
@@ -443,8 +484,21 @@ function createOldToNewPropsMapping(
function emitDestructureProps(
env: Environment,
propsObj: Place,
properties: Array<ObjectProperty>,
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),

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

@@ -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

@@ -47,7 +47,7 @@ import { identity, mutate, setProperty } from "shared-runtime";
function AllocatingPrimitiveAsDepNested(props) {
const $ = _c(5);
let t0;
if ($[0] !== props.b || $[1] !== props.a) {
if ($[0] !== props.a || $[1] !== props.b) {
const x = {};
mutate(x);
const t1 = identity(props.b) + 1;
@@ -62,8 +62,8 @@ function AllocatingPrimitiveAsDepNested(props) {
const y = t2;
setProperty(x, props.a);
t0 = [x, y];
$[0] = props.b;
$[1] = props.a;
$[0] = props.a;
$[1] = props.b;
$[2] = t0;
} else {
t0 = $[2];

View File

@@ -41,7 +41,7 @@ function ArrayAtTest(props) {
}
const arr = t1;
let t2;
if ($[4] !== props.y || $[5] !== arr) {
if ($[4] !== arr || $[5] !== props.y) {
let t3;
if ($[7] !== props.y) {
t3 = bar(props.y);
@@ -51,8 +51,8 @@ function ArrayAtTest(props) {
t3 = $[8];
}
t2 = arr.at(t3);
$[4] = props.y;
$[5] = arr;
$[4] = arr;
$[5] = props.y;
$[6] = t2;
} else {
t2 = $[6];

View File

@@ -22,10 +22,10 @@ import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(3);
let t0;
if ($[0] !== props.foo || $[1] !== props.bar) {
if ($[0] !== props.bar || $[1] !== props.foo) {
t0 = [0, ...props.foo, null, ...props.bar, "z"];
$[0] = props.foo;
$[1] = props.bar;
$[0] = props.bar;
$[1] = props.foo;
$[2] = t0;
} else {
t0 = $[2];

View File

@@ -24,18 +24,18 @@ export const FIXTURE_ENTRYPOINT = {
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(11);
let t0;
let a;
let t0;
if ($[0] !== props.a || $[1] !== props.b) {
a = [props.a, props.b, "hello"];
t0 = a.push(42);
$[0] = props.a;
$[1] = props.b;
$[2] = t0;
$[3] = a;
$[2] = a;
$[3] = t0;
} else {
t0 = $[2];
a = $[3];
a = $[2];
t0 = $[3];
}
const x = t0;
let t1;

View File

@@ -0,0 +1,107 @@
## Input
```javascript
// @flow @enableTransitivelyFreezeFunctionExpressions:false
import {arrayPush, setPropertyByKey, Stringify} from 'shared-runtime';
/**
* 1. `InferMutableRanges` derives the mutable range of identifiers and their
* aliases from `LoadLocal`, `PropertyLoad`, etc
* - After this pass, y's mutable range only extends to `arrayPush(x, y)`
* - We avoid assigning mutable ranges to loads after y's mutable range, as
* these are working with an immutable value. As a result, `LoadLocal y` and
* `PropertyLoad y` do not get mutable ranges
* 2. `InferReactiveScopeVariables` extends mutable ranges and creates scopes,
* as according to the 'co-mutation' of different values
* - Here, we infer that
* - `arrayPush(y, x)` might alias `x` and `y` to each other
* - `setPropertyKey(x, ...)` may mutate both `x` and `y`
* - This pass correctly extends the mutable range of `y`
* - Since we didn't run `InferMutableRange` logic again, the LoadLocal /
* PropertyLoads still don't have a mutable range
*
* Note that the this bug is an edge case. Compiler output is only invalid for:
* - function expressions with
* `enableTransitivelyFreezeFunctionExpressions:false`
* - functions that throw and get retried without clearing the memocache
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":11},"shouldInvokeFns":true}</div>
* Forget:
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}</div>
*/
function useFoo({a, b}: {a: number, b: number}) {
const x = [];
const y = {value: a};
arrayPush(x, y); // x and y co-mutate
const y_alias = y;
const cb = () => y_alias.value;
setPropertyByKey(x[0], 'value', b); // might overwrite y.value
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{a: 2, b: 10}],
sequentialRenders: [
{a: 2, b: 10},
{a: 2, b: 11},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { arrayPush, setPropertyByKey, Stringify } from "shared-runtime";
function useFoo(t0) {
const $ = _c(5);
const { a, b } = t0;
let t1;
if ($[0] !== a || $[1] !== b) {
const x = [];
const y = { value: a };
arrayPush(x, y);
const y_alias = y;
let t2;
if ($[3] !== y_alias.value) {
t2 = () => y_alias.value;
$[3] = y_alias.value;
$[4] = t2;
} else {
t2 = $[4];
}
const cb = t2;
setPropertyByKey(x[0], "value", b);
t1 = <Stringify cb={cb} shouldInvokeFns={true} />;
$[0] = a;
$[1] = b;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ a: 2, b: 10 }],
sequentialRenders: [
{ a: 2, b: 10 },
{ a: 2, b: 11 },
],
};
```

View File

@@ -0,0 +1,53 @@
// @flow @enableTransitivelyFreezeFunctionExpressions:false
import {arrayPush, setPropertyByKey, Stringify} from 'shared-runtime';
/**
* 1. `InferMutableRanges` derives the mutable range of identifiers and their
* aliases from `LoadLocal`, `PropertyLoad`, etc
* - After this pass, y's mutable range only extends to `arrayPush(x, y)`
* - We avoid assigning mutable ranges to loads after y's mutable range, as
* these are working with an immutable value. As a result, `LoadLocal y` and
* `PropertyLoad y` do not get mutable ranges
* 2. `InferReactiveScopeVariables` extends mutable ranges and creates scopes,
* as according to the 'co-mutation' of different values
* - Here, we infer that
* - `arrayPush(y, x)` might alias `x` and `y` to each other
* - `setPropertyKey(x, ...)` may mutate both `x` and `y`
* - This pass correctly extends the mutable range of `y`
* - Since we didn't run `InferMutableRange` logic again, the LoadLocal /
* PropertyLoads still don't have a mutable range
*
* Note that the this bug is an edge case. Compiler output is only invalid for:
* - function expressions with
* `enableTransitivelyFreezeFunctionExpressions:false`
* - functions that throw and get retried without clearing the memocache
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":11},"shouldInvokeFns":true}</div>
* Forget:
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}</div>
*/
function useFoo({a, b}: {a: number, b: number}) {
const x = [];
const y = {value: a};
arrayPush(x, y); // x and y co-mutate
const y_alias = y;
const cb = () => y_alias.value;
setPropertyByKey(x[0], 'value', b); // might overwrite y.value
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{a: 2, b: 10}],
sequentialRenders: [
{a: 2, b: 10},
{a: 2, b: 11},
],
};

View File

@@ -0,0 +1,87 @@
## Input
```javascript
// @flow @enableTransitivelyFreezeFunctionExpressions:false
import {setPropertyByKey, Stringify} from 'shared-runtime';
/**
* Variation of bug in `bug-aliased-capture-aliased-mutate`
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":3},"shouldInvokeFns":true}</div>
* Forget:
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
*/
function useFoo({a}: {a: number, b: number}) {
const arr = [];
const obj = {value: a};
setPropertyByKey(obj, 'arr', arr);
const obj_alias = obj;
const cb = () => obj_alias.arr.length;
for (let i = 0; i < a; i++) {
arr.push(i);
}
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{a: 2}],
sequentialRenders: [{a: 2}, {a: 3}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { setPropertyByKey, Stringify } from "shared-runtime";
function useFoo(t0) {
const $ = _c(4);
const { a } = t0;
let t1;
if ($[0] !== a) {
const arr = [];
const obj = { value: a };
setPropertyByKey(obj, "arr", arr);
const obj_alias = obj;
let t2;
if ($[2] !== obj_alias.arr.length) {
t2 = () => obj_alias.arr.length;
$[2] = obj_alias.arr.length;
$[3] = t2;
} else {
t2 = $[3];
}
const cb = t2;
for (let i = 0; i < a; i++) {
arr.push(i);
}
t1 = <Stringify cb={cb} shouldInvokeFns={true} />;
$[0] = a;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ a: 2 }],
sequentialRenders: [{ a: 2 }, { a: 3 }],
};
```

View File

@@ -0,0 +1,34 @@
// @flow @enableTransitivelyFreezeFunctionExpressions:false
import {setPropertyByKey, Stringify} from 'shared-runtime';
/**
* Variation of bug in `bug-aliased-capture-aliased-mutate`
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":3},"shouldInvokeFns":true}</div>
* Forget:
* (kind: ok)
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
* <div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>
*/
function useFoo({a}: {a: number, b: number}) {
const arr = [];
const obj = {value: a};
setPropertyByKey(obj, 'arr', arr);
const obj_alias = obj;
const cb = () => obj_alias.arr.length;
for (let i = 0; i < a; i++) {
arr.push(i);
}
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{a: 2}],
sequentialRenders: [{a: 2}, {a: 3}],
};

View File

@@ -0,0 +1,81 @@
## Input
```javascript
import {Stringify} from 'shared-runtime';
/**
* Fixture currently fails with
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <div>{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}</div>
* Forget:
* (kind: exception) bar is not a function
*/
function Foo({value}) {
const result = bar();
function bar() {
return {value};
}
return <Stringify result={result} fn={bar} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{value: 2}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
/**
* Fixture currently fails with
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <div>{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}</div>
* Forget:
* (kind: exception) bar is not a function
*/
function Foo(t0) {
const $ = _c(6);
const { value } = t0;
let bar;
let result;
if ($[0] !== value) {
result = bar();
bar = function bar() {
return { value };
};
$[0] = value;
$[1] = bar;
$[2] = result;
} else {
bar = $[1];
result = $[2];
}
const t1 = bar;
let t2;
if ($[3] !== result || $[4] !== t1) {
t2 = <Stringify result={result} fn={t1} shouldInvokeFns={true} />;
$[3] = result;
$[4] = t1;
$[5] = t2;
} else {
t2 = $[5];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{ value: 2 }],
};
```

View File

@@ -0,0 +1,22 @@
import {Stringify} from 'shared-runtime';
/**
* Fixture currently fails with
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <div>{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}</div>
* Forget:
* (kind: exception) bar is not a function
*/
function Foo({value}) {
const result = bar();
function bar() {
return {value};
}
return <Stringify result={result} fn={bar} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{value: 2}],
};

View File

@@ -58,7 +58,7 @@ function Component(t0) {
const $ = _c(5);
const { obj, isObjNull } = t0;
let t1;
if ($[0] !== isObjNull || $[1] !== obj.prop) {
if ($[0] !== isObjNull || $[1] !== obj) {
t1 = () => {
if (!isObjNull) {
return obj.prop;
@@ -67,7 +67,7 @@ function Component(t0) {
}
};
$[0] = isObjNull;
$[1] = obj.prop;
$[1] = obj;
$[2] = t1;
} else {
t1 = $[2];

View File

@@ -70,10 +70,10 @@ function Component() {
throw new Error("invariant broken");
}
let t1;
if ($[1] !== obj || $[2] !== boxedInner) {
if ($[1] !== boxedInner || $[2] !== obj) {
t1 = <Stringify obj={obj} inner={boxedInner} />;
$[1] = obj;
$[2] = boxedInner;
$[1] = boxedInner;
$[2] = obj;
$[3] = t1;
} else {
t1 = $[3];

View File

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

View File

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

View File

@@ -0,0 +1,109 @@
## Input
```javascript
import {identity, mutate} from 'shared-runtime';
/**
* Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr
* with the mutation hoisted to a named variable instead of being directly
* inlined into the Object key.
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}]
* [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}]
* Forget:
* (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}]
* [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}]
*/
function Component(props) {
const key = {};
const tmp = (mutate(key), key);
const context = {
// Here, `tmp` is frozen (as it's inferred to be a primitive/string)
[tmp]: identity([props.value]),
};
mutate(key);
return [context, key];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
sequentialRenders: [{value: 42}, {value: 42}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { identity, mutate } from "shared-runtime";
/**
* Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr
* with the mutation hoisted to a named variable instead of being directly
* inlined into the Object key.
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}]
* [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}]
* Forget:
* (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}]
* [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}]
*/
function Component(props) {
const $ = _c(8);
let key;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
key = {};
t0 = (mutate(key), key);
$[0] = key;
$[1] = t0;
} else {
key = $[0];
t0 = $[1];
}
const tmp = t0;
let t1;
if ($[2] !== props.value) {
t1 = identity([props.value]);
$[2] = props.value;
$[3] = t1;
} else {
t1 = $[3];
}
let t2;
if ($[4] !== t1) {
t2 = { [tmp]: t1 };
$[4] = t1;
$[5] = t2;
} else {
t2 = $[5];
}
const context = t2;
mutate(key);
let t3;
if ($[6] !== context) {
t3 = [context, key];
$[6] = context;
$[7] = t3;
} else {
t3 = $[7];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 42 }],
sequentialRenders: [{ value: 42 }, { value: 42 }],
};
```

View File

@@ -0,0 +1,31 @@
import {identity, mutate} from 'shared-runtime';
/**
* Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr
* with the mutation hoisted to a named variable instead of being directly
* inlined into the Object key.
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}]
* [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}]
* Forget:
* (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}]
* [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}]
*/
function Component(props) {
const key = {};
const tmp = (mutate(key), key);
const context = {
// Here, `tmp` is frozen (as it's inferred to be a primitive/string)
[tmp]: identity([props.value]),
};
mutate(key);
return [context, key];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
sequentialRenders: [{value: 42}, {value: 42}],
};

View File

@@ -38,16 +38,24 @@ import { identity } from "shared-runtime";
* try-catch block, as that might throw
*/
function useFoo(maybeNullObject) {
const $ = _c(2);
const $ = _c(4);
let y;
if ($[0] !== maybeNullObject.value.inner) {
if ($[0] !== maybeNullObject) {
y = [];
try {
y.push(identity(maybeNullObject.value.inner));
let t0;
if ($[2] !== maybeNullObject.value.inner) {
t0 = identity(maybeNullObject.value.inner);
$[2] = maybeNullObject.value.inner;
$[3] = t0;
} else {
t0 = $[3];
}
y.push(t0);
} catch {
y.push("null");
}
$[0] = maybeNullObject.value.inner;
$[0] = maybeNullObject;
$[1] = y;
} else {
y = $[1];

View File

@@ -32,7 +32,7 @@ import { mutate } from "shared-runtime";
function component(foo, bar) {
const $ = _c(3);
let x;
if ($[0] !== foo || $[1] !== bar) {
if ($[0] !== bar || $[1] !== foo) {
x = { foo };
const y = { bar };
@@ -41,8 +41,8 @@ function component(foo, bar) {
a.x = b;
mutate(y);
$[0] = foo;
$[1] = bar;
$[0] = bar;
$[1] = foo;
$[2] = x;
} else {
x = $[2];

View File

@@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime";
function component(foo, bar) {
const $ = _c(3);
let x;
if ($[0] !== foo || $[1] !== bar) {
if ($[0] !== bar || $[1] !== foo) {
x = { foo };
const y = { bar };
const f0 = function () {
@@ -35,8 +35,8 @@ function component(foo, bar) {
f0();
mutate(y);
$[0] = foo;
$[1] = bar;
$[0] = bar;
$[1] = foo;
$[2] = x;
} else {
x = $[2];

View File

@@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime");
function component(foo, bar) {
const $ = _c(3);
let x;
if ($[0] !== foo || $[1] !== bar) {
if ($[0] !== bar || $[1] !== foo) {
x = { foo };
const y = { bar };
@@ -41,8 +41,8 @@ function component(foo, bar) {
a.x = b;
mutate(y);
$[0] = foo;
$[1] = bar;
$[0] = bar;
$[1] = foo;
$[2] = x;
} else {
x = $[2];

View File

@@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime";
function component(foo, bar) {
const $ = _c(3);
let x;
if ($[0] !== foo || $[1] !== bar) {
if ($[0] !== bar || $[1] !== foo) {
x = { foo };
const y = { bar };
const f0 = function () {
@@ -35,8 +35,8 @@ function component(foo, bar) {
f0();
mutate(y);
$[0] = foo;
$[1] = bar;
$[0] = bar;
$[1] = foo;
$[2] = x;
} else {
x = $[2];

View File

@@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime");
function component(foo, bar) {
const $ = _c(3);
let y;
if ($[0] !== foo || $[1] !== bar) {
if ($[0] !== bar || $[1] !== foo) {
const x = { foo };
y = { bar };
@@ -41,8 +41,8 @@ function component(foo, bar) {
a.x = b;
mutate(y);
$[0] = foo;
$[1] = bar;
$[0] = bar;
$[1] = foo;
$[2] = y;
} else {
y = $[2];

View File

@@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime";
function component(foo, bar) {
const $ = _c(3);
let y;
if ($[0] !== foo || $[1] !== bar) {
if ($[0] !== bar || $[1] !== foo) {
const x = { foo };
y = { bar };
const f0 = function () {
@@ -35,8 +35,8 @@ function component(foo, bar) {
f0();
mutate(y);
$[0] = foo;
$[1] = bar;
$[0] = bar;
$[1] = foo;
$[2] = y;
} else {
y = $[2];

View File

@@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime");
function component(foo, bar) {
const $ = _c(3);
let y;
if ($[0] !== foo || $[1] !== bar) {
if ($[0] !== bar || $[1] !== foo) {
const x = { foo };
y = { bar };
@@ -41,8 +41,8 @@ function component(foo, bar) {
a.x = b;
mutate(y);
$[0] = foo;
$[1] = bar;
$[0] = bar;
$[1] = foo;
$[2] = y;
} else {
y = $[2];

View File

@@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime";
function component(foo, bar) {
const $ = _c(3);
let y;
if ($[0] !== foo || $[1] !== bar) {
if ($[0] !== bar || $[1] !== foo) {
const x = { foo };
y = { bar };
const f0 = function () {
@@ -35,8 +35,8 @@ function component(foo, bar) {
f0();
mutate(y);
$[0] = foo;
$[1] = bar;
$[0] = bar;
$[1] = foo;
$[2] = y;
} else {
y = $[2];

View File

@@ -37,7 +37,7 @@ function component(a, b) {
}
const y = t0;
let z;
if ($[2] !== a || $[3] !== y.b) {
if ($[2] !== a || $[3] !== y) {
z = { a };
const x = function () {
z.a = 2;
@@ -45,7 +45,7 @@ function component(a, b) {
x();
$[2] = a;
$[3] = y.b;
$[3] = y;
$[4] = z;
} else {
z = $[4];

View File

@@ -46,10 +46,10 @@ function component(t0) {
}
const hide = t2;
let t3;
if ($[4] !== poke || $[5] !== hide) {
if ($[4] !== hide || $[5] !== poke) {
t3 = <Foo poke={poke} hide={hide} />;
$[4] = poke;
$[5] = hide;
$[4] = hide;
$[5] = poke;
$[6] = t3;
} else {
t3 = $[6];

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enableEmitFreeze @instrumentForget
// @enableEmitFreeze @enableEmitInstrumentForget
function useFoo(props) {
return foo(props.x);
@@ -18,7 +18,7 @@ import {
shouldInstrument,
makeReadOnly,
} from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget
import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @enableEmitInstrumentForget
function useFoo(props) {
if (__DEV__ && shouldInstrument)

View File

@@ -1,4 +1,4 @@
// @enableEmitFreeze @instrumentForget
// @enableEmitFreeze @enableEmitInstrumentForget
function useFoo(props) {
return foo(props.x);

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @instrumentForget @compilationMode(annotation) @gating
// @enableEmitInstrumentForget @compilationMode(annotation) @gating
function Bar(props) {
'use forget';
@@ -25,7 +25,7 @@ function Foo(props) {
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { useRenderCounter, shouldInstrument } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @instrumentForget @compilationMode(annotation) @gating
import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating
const Bar = isForgetEnabled_Fixtures()
? function Bar(props) {
"use forget";

View File

@@ -1,4 +1,4 @@
// @instrumentForget @compilationMode(annotation) @gating
// @enableEmitInstrumentForget @compilationMode(annotation) @gating
function Bar(props) {
'use forget';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @instrumentForget @compilationMode(annotation)
// @enableEmitInstrumentForget @compilationMode(annotation)
function Bar(props) {
'use forget';
@@ -24,7 +24,7 @@ function Foo(props) {
```javascript
import { useRenderCounter, shouldInstrument } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @instrumentForget @compilationMode(annotation)
import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation)
function Bar(props) {
"use forget";

View File

@@ -1,4 +1,4 @@
// @instrumentForget @compilationMode(annotation)
// @enableEmitInstrumentForget @compilationMode(annotation)
function Bar(props) {
'use forget';

View File

@@ -46,7 +46,7 @@ function Component(props) {
const items = props.items;
const maxItems = props.maxItems;
let renderedItems;
if ($[0] !== maxItems || $[1] !== items) {
if ($[0] !== items || $[1] !== maxItems) {
renderedItems = [];
const seen = new Set();
const max = Math.max(0, maxItems);
@@ -62,8 +62,8 @@ function Component(props) {
break;
}
}
$[0] = maxItems;
$[1] = items;
$[0] = items;
$[1] = maxItems;
$[2] = renderedItems;
} else {
renderedItems = $[2];
@@ -79,15 +79,15 @@ function Component(props) {
t0 = $[4];
}
let t1;
if ($[5] !== t0 || $[6] !== renderedItems) {
if ($[5] !== renderedItems || $[6] !== t0) {
t1 = (
<div>
{t0}
{renderedItems}
</div>
);
$[5] = t0;
$[6] = renderedItems;
$[5] = renderedItems;
$[6] = t0;
$[7] = t1;
} else {
t1 = $[7];

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