## Summary
When upgrading to `babel-plugin-react-compiler@1.0.0` in a project that
uses `zod@3` we are running into TypeScript errors like:
```
node_modules/babel-plugin-react-compiler/dist/index.d.ts:435:10 - error TS2694: Namespace '"/REDACTED/node_modules/zod/v3/external"' has no exported member 'core'.
435 }, z.core.$strip>>>;
~~~~
```
This problem seems to be related to
d6eb735938, which introduced zod v3/v4
compatibility. Since `zod` is bundled into the compiler source this does
not cause runtime issues and only manifests as TypeScript errors. My
proposed solution is this PR is to use zod's [subpath versioning
strategy](https://zod.dev/v4/versioning?id=versioning-in-zod-4) which
allows you to support v3 and v4 APIs on both major versions.
Changes in this PR include:
- Updated `zod` import paths to `zod/v4`
- Bumped min `zod` version to `^3.25.0` for zod which guarantees the
`zod/v4` subpath is available.
- Updated `zod-validation-error` import paths to
`zod-validation-error/v4`
- Bumped min `zod-validation-error ` version to `^3.5.0`
- Updated `externals` tsup configuration where appropriate.
Once the compiler drops zod v3 support we could optionally remove the
`/v4` subpath from the imports.
## How did you test this change?
Not totally sure the best way to test. I ran `NODE_ENV=production yarn
workspace babel-plugin-react-compiler run build --dts` and diffed the
`dist/` folder between my change and `v1.0.0` and it looks correct. We
have a `patch-package` patch to workaround this for now and it works as
expected.
```diff
diff --git a/node_modules/babel-plugin-react-compiler/dist/index.d.ts b/node_modules/babel-plugin-react-compiler/dist/index.d.ts
index 81c3f3d..daafc2c 100644
--- a/node_modules/babel-plugin-react-compiler/dist/index.d.ts
+++ b/node_modules/babel-plugin-react-compiler/dist/index.d.ts
@@ -1,7 +1,7 @@
import * as BabelCore from '@babel/core';
import { NodePath as NodePath$1 } from '@babel/core';
import * as t from '@babel/types';
-import { z } from 'zod';
+import { z } from 'zod/v4';
import { NodePath, Scope } from '@babel/traverse';
interface Result<T, E> {
```
Co-authored-by: Henry Q. Dineen <henryqdineen@gmail.com>
We previously always generated import statements for any modules that
had to be required, notably the `import {c} from
'react/compiler-runtime'` for the memo cache function. However, this
obviously doesn't work when the source is using commonjs. Now we check
the sourceType of the module and generate require() statements if the
source type is 'script'.
I initially explored using
https://babeljs.io/docs/babel-helper-module-imports, but the API design
was unfortunately not flexible enough for our use-case. Specifically,
our pipeline is as follows:
* Compile individual functions. Generate candidate imports,
pre-allocating the local names for those imports.
* If the file is compiled successfully, actually add the imports to the
program.
Ie we need to pre-allocate identifier names for the imports before we
add them to the program — but that isn't supported by
babel-helper-module-imports. So instead we generate our own require()
calls if the sourceType is script.
A few libraries are known to be incompatible with memoization, whether
manually via `useMemo()` or via React Compiler. This puts us in a tricky
situation. On the one hand, we understand that these libraries were
developed prior to our documenting the [Rules of
React](https://react.dev/reference/rules), and their designs were the
result of trying to deliver a great experience for their users and
balance multiple priorities around DX, performance, etc. At the same
time, using these libraries with memoization — and in particular with
automatic memoization via React Compiler — can break apps by causing the
components using these APIs not to update. Concretely, the APIs have in
common that they return a function which returns different values over
time, but where the function itself does not change. Memoizing the
result on the identity of the function will mean that the value never
changes. Developers reasonable interpret this as "React Compiler broke
my code".
Of course, the best solution is to work with developers of these
libraries to address the root cause, and we're doing that. We've
previously discussed this situation with both of the respective
libraries:
* React Hook Form:
https://github.com/react-hook-form/react-hook-form/issues/11910#issuecomment-2135608761
* TanStack Table:
https://github.com/facebook/react/issues/33057#issuecomment-2840600158
and https://github.com/TanStack/table/issues/5567
In the meantime we need to make sure that React Compiler can work out of
the box as much as possible. This means teaching it about popular
libraries that cannot be memoized. We also can't silently skip
compilation, as this confuses users, so we need these error messages to
be visible to users. To that end, this PR adds:
* A flag to mark functions/hooks as incompatible
* Validation against use of such functions
* A default type provider to provide declarations for two
known-incompatible libraries
Note that Mobx is also incompatible, but the `observable()` function is
called outside of the component itself, so the compiler cannot currently
detect it. We may add validation for such APIs in the future.
Again, we really empathize with the developers of these libraries. We've
tried to word the error message non-judgementally, because we get that
it's hard! We're open to feedback about the error message, please let us
know.
NOTE: this is a merged version of @mofeiZ's original PR along with my
edits per offline discussion. The description is updated to reflect the
latest approach.
The key problem we're trying to solve with this PR is to allow
developers more control over the compiler's various validations. The
idea is to have a number of rules targeting a specific category of
issues, such as enforcing immutability of props/state/etc or disallowing
access to refs during render. We don't want to have to run the compiler
again for every single rule, though, so @mofeiZ added an LRU cache that
caches the full compilation output of N most recent files. The first
rule to run on a given file will cause it to get cached, and then
subsequent rules can pull from the cache, with each rule filtering down
to its specific category of errors.
For the categories, I went through and assigned a category roughly 1:1
to existing validations, and then used my judgement on some places that
felt distinct enough to warrant a separate error. Every error in the
compiler now has to supply both a severity (for legacy reasons) and a
category (for ESLint). Each category corresponds 1:1 to a ESLint rule
definition, so that the set of rules is automatically populated based on
the defined categories.
Categories include a flag for whether they should be in the recommended
set or not.
Note that as with the original version of this PR, only
eslint-plugin-react-compiler is changed. We still have to update the
main lint rule.
## Test Plan
* Created a sample project using ESLint v9 and verified that the plugin
can be configured correctly and detects errors
* Edited `fixtures/eslint-v9` and introduced errors, verified that the w
latest config changes in that fixture it correctly detects the errors
* In the sample project, confirmed that the LRU caching is correctly
caching compiler output, ie compiling files just once.
Co-authored-by: Mofei Zhang <feifei0@meta.com>
Work in progress, i'm experimenting with revamping our diagnostic infra.
Starting with a better format for representing errors, with an ability
to point ot multiple locations, along with better printing of errors. Of
course, Babel still controls the printing in the majority case so this
still needs more work.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33751).
* #33981
* #33777
* #33767
* #33765
* #33760
* #33759
* #33758
* __->__ #33751
* #33752
* #33753
Adds some typed helpers to represent aliasing, assign, capture,
createfrom, and mutate effects along with representative runtime
behavior, and then adds tests to demonstrate that we model
capture->createfrom and createfrom->capture correctly.
There is one case (createfrom->capture in a lambda) where we infer a
less precise effect, but in the more conservative direction (we include
more code/deps than necesssary rather than fewer).
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33543).
* #33571
* #33558
* #33547
* __->__ #33543
Squashed, review-friendly version of the stack from
https://github.com/facebook/react/pull/33488.
This is new version of our mutability and inference model, designed to
replace the core algorithm for determining the sets of instructions
involved in constructing a given value or set of values. The new model
replaces InferReferenceEffects, InferMutableRanges (and all of its
subcomponents), and parts of AnalyzeFunctions. The new model does not
use per-Place effect values, but in order to make this drop-in the end
_result_ of the inference adds these per-Place effects.
I'll write up a larger document on the model, first i'm doing some
housekeeping to rebase the PR.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33494).
* #33571
* #33558
* #33547
* #33543
* #33533
* #33532
* #33530
* #33526
* #33522
* #33518
* #33514
* #33513
* #33512
* #33504
* #33500
* #33497
* #33496
* #33495
* __->__ #33494
* #33572
This bug was reported via our wg and appears to only affect values
created as a ref.
Currently, postfix operators used in a callback gets compiled to:
```js
modalId.current = modalId.current + 1; // 1
const id = modalId.current; // 1
return id;
```
which is semantically incorrect. The postfix increment operator should
return the value before incrementing. In other words something like this
should have been compiled instead:
```js
const id = modalId.current; // 0
modalId.current = modalId.current + 1; // 1
return id;
```
This bug does not trigger when the incremented value is a plain
primitive, instead there is a TODO bailout.
This is a babel bug + edge case.
Babel compact mode produces invalid JavaScript (i.e. parse error) when
given a `NumericLiteral` with a negative value.
See https://codesandbox.io/p/devbox/5d47fr for repro.
(Almost) all pragmas are now one of the following:
- `@...TestOnly`: custom pragma for test fixtures
- `@<configName>` | `@<configName>:true`: enables with either true or a
default enabled value
- `@<configName>:<json value>`
Accidentally broke this when migrating our test runner to use the
bundled build https://github.com/facebook/react/pull/32758
The fix is pretty simple. File watcher should listen for changes in
`packages/babel-plugin-react-compiler` instead of `cwd`, which is now
`packages/snap`.
Currently, inferred effect dependencies are considered a
"compiler-required" feature. This means that untransformed callsites
should escalate to a build error.
`ValidateNoUntransformedReferences` iterates 'special effect' callsites
and checks that the compiler was able to successfully transform them.
Prior to this PR, this relied on checking the number of arguments passed
to this special effect.
This obviously doesn't work with `noEmit: true`, which is used for our
eslint plugin (this avoids mutating the babel program as other linters
run with the same ast). This PR adds a set of `babel.SourceLocation`s to
do best effort matching in this mode.
Currently, `babel-plugin-react-compiler` is bundled with (almost) all
external dependencies. This is because babel traversal and ast logic is
not forward-compatible. Since `babel-plugin-react-compiler` needs to be
compatible with babel pipelines across a wide semvar range, we (1) set
this package's babel dependency to an early version and (2) inline babel
libraries into our bundle.
A few other packages in `react/compiler` depend on the compiler. This PR
moves `snap`, our test fixture compiler and evaluator, to use the
bundled version of `babel-plugin-react-compiler`. This decouples the
babel version used by `snap` with the version used by
`babel-plugin-react-compiler`, which means that `snap` now can test
features from newer babel versions (see
https://github.com/facebook/react/pull/32742).
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32758).
* #32759
* __->__ #32758
Alternative to facebook/react#31584 which sets
enableTreatFunctionDepsAsConditional:true` by default.
This PR changes dependency hoisting to be more conservative while trying
to preserve an optimal "happy path". We assume that a function "is
likely called" if we observe the following in the react function body.
- a direct callsite
- passed directly as a jsx attribute or child
- passed directly to a hook
- a direct return
A function is also "likely called" if it is directly called, passed to
jsx / hooks, or returned from another function that "is likely called".
Note that this approach marks the function definition site with its
hoistable properties (not its use site). I tried implementing use-site
hoisting semantics, but it felt both unpredictable (i.e. as a developer,
I can't trust that callbacks are well memoized) and not helpful (type +
null checks of a value are usually colocated with their use site)
In this fixture (copied here for easy reference), it should be safe to
use `a.value` and `b.value` as dependencies, even though these functions
are conditionally called.
```js
// inner-function/nullable-objects/assume-invoked/conditional-call-chain.tsx
function Component({a, b}) {
const logA = () => {
console.log(a.value);
};
const logB = () => {
console.log(b.value);
};
const hasLogged = useRef(false);
const log = () => {
if (!hasLogged.current) {
logA();
logB();
hasLogged.current = true;
}
};
return <Stringify log={log} shouldInvokeFns={true} />;
}
```
On the other hand, this means that we produce invalid output for code
like manually implementing `Array.map`
```js
// inner-function/nullable-objects/bug-invalid-array-map-manual.js
function useFoo({arr1, arr2}) {
const cb = e => arr2[0].value + e.value;
const y = [];
for (let i = 0; i < arr1.length; i++) {
y.push(cb(arr1[i]));
}
return y;
}
```
Extracting portions of #32416 for easier review. This PR dedupes
@babel/types to resolve to 7.26.3, for compatibility in the root
workspace where eslint-plugin-react-hooks resides.
I also needed to update @babel/preset-typescript in snap.
The compiler changes in HIR and ReactiveScopes were needed due to types
changing. Notably, Babel [added support for optional chaining
assignment](https://github.com/babel/babel/pull/15751) (currently [Stage
1](https://github.com/tc39/proposal-optional-chaining-assignment)), so
in the latest versions of @babel/types, AssignmentExpression.left can
now also be of t.OptionalMemberExpression.
Given that this is in Stage 1, the compiler probably shouldn't support
this syntax, so this PR updates HIR to bailout with a TODO if there is a
non LVal on the lhs of an Assignment Expression.
There was also a small superficial SourceLocation change needed in
`InferReactiveScopeVariables` as Babel 8 changes were [accidentally
released in
7](https://github.com/babel/babel/issues/10746#issuecomment-2699146670).
It doesn't affect our analysis so it seems fine to just update with the
new properties.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32581).
* #32582
* __->__ #32581
Co-authored-by: michael faith <michaelfaith@users.noreply.github.com>
Co-authored-by: michael faith <michaelfaith@users.noreply.github.com>
Test fixtures testing different compiler features (e.g. non-auto
memoization) should live in separate directories.
Remove bug-prefixed fixtures that have since been fixed
Add test evaluator export to more fixtures
Alternative to #32071. As a follow up to #31993, the `platform` target
was incorrectly being set to `browser` since it was the default argument
for the build script. This corrects it to `node` and `cjs` which I think
should resolve node 20 issues.
- Adds @compilationMode(all|infer|syntax|annotation) and
@panicMode(none) directives. This is now shared with our test infra
- Playground still defaults to `infer` mode while tests default to `all`
mode
- See added fixture tests
This migrates the compiler's bundler to esbuild instead of rollup.
Unlike React, our bundling use cases are far simpler since the majority
of our packages are meant to be run on node. Rollup was adding
considerable build time overhead whereas esbuild remains fast and has
all the functionality we need out of the box.
### Before
```
time yarn workspaces run build
yarn workspaces v1.22.22
> babel-plugin-react-compiler
yarn run v1.22.22
$ rimraf dist && rollup --config --bundleConfigAsCjs
src/index.ts → dist/index.js...
(!) Circular dependencies
# ...
created dist/index.js in 15.5s
✨ Done in 16.45s.
> eslint-plugin-react-compiler
yarn run v1.22.22
$ rimraf dist && rollup --config --bundleConfigAsCjs
src/index.ts → dist/index.js...
(!) Circular dependencies
# ...
created dist/index.js in 9.1s
✨ Done in 10.11s.
> make-read-only-util
yarn run v1.22.22
warning package.json: No license field
$ tsc
✨ Done in 1.81s.
> react-compiler-healthcheck
yarn run v1.22.22
$ rimraf dist && rollup --config --bundleConfigAsCjs
src/index.ts → dist/index.js...
(!) Circular dependencies
# ...
created dist/index.js in 8.7s
✨ Done in 10.43s.
> react-compiler-runtime
yarn run v1.22.22
$ rimraf dist && rollup --config --bundleConfigAsCjs
src/index.ts → dist/index.js...
(!) src/index.ts (1:0): Module level directives cause errors when bundled, "use no memo" in "src/index.ts" was ignored.
# ...
created dist/index.js in 1.1s
✨ Done in 1.82s.
> snap
yarn run v1.22.22
$ rimraf dist && concurrently -n snap,runtime "tsc --build" "yarn --silent workspace react-compiler-runtime build --silent"
$ rimraf dist && rollup --config --bundleConfigAsCjs --silent
[runtime] yarn --silent workspace react-compiler-runtime build --silent exited with code 0
[snap] tsc --build exited with code 0
✨ Done in 5.73s.
✨ Done in 47.30s.
yarn workspaces run build 75.92s user 5.48s system 170% cpu 47.821 total
```
### After
```
time yarn workspaces run build
yarn workspaces v1.22.22
> babel-plugin-react-compiler
yarn run v1.22.22
$ rimraf dist && scripts/build.js
✨ Done in 1.02s.
> eslint-plugin-react-compiler
yarn run v1.22.22
$ rimraf dist && scripts/build.js
✨ Done in 0.93s.
> make-read-only-util
yarn run v1.22.22
warning package.json: No license field
$ rimraf dist && scripts/build.js
✨ Done in 0.89s.
> react-compiler-healthcheck
yarn run v1.22.22
$ rimraf dist && scripts/build.js
✨ Done in 0.58s.
> react-compiler-runtime
yarn run v1.22.22
$ rimraf dist && scripts/build.js
✨ Done in 0.48s.
> snap
yarn run v1.22.22
$ rimraf dist && concurrently -n snap,runtime "tsc --build" "yarn --silent workspace react-compiler-runtime build"
$ rimraf dist && scripts/build.js
[runtime] yarn --silent workspace react-compiler-runtime build exited with code 0
[snap] tsc --build exited with code 0
✨ Done in 4.69s.
✨ Done in 9.46s.
yarn workspaces run build 9.70s user 0.99s system 103% cpu 10.329 total
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31963).
* #31964
* __->__ #31963
* #31962
We previously didn't track context variables in the hoistable values
sidemap of `propagateScopeDependencies`. This was overly conservative as
we *do* track the mutable range of context variables, and it is safe to
hoist accesses to context variables after their last direct / aliased
maybe-assignment.
```js
function Component({value}) {
// start of mutable range for `x`
let x = DEFAULT;
const setX = () => x = value;
const aliasedSet = maybeAlias(setX);
maybeCall(aliasedSet);
// end of mutable range for `x`
// here, we should be able to take x (and property reads
// off of x) as dependencies
return <Jsx value={x} />
}
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31582).
* #31583
* __->__ #31582
Adds `target: 'donotuse_meta_internal'`, which inserts useMemoCache
imports directly from `react`. Note that this is only valid for Meta
bundles, as others do not [re-export the `c`
function](5b0ef217ef/packages/react/index.fb.js (L68-L70)).
```js
// target=donotuse_meta_internal
import {c as _c} from 'react';
// target=19
import {c as _c} from 'react/compiler-runtime';
// target=17,18
import {c as _c} from 'react-compiler-runtime';
```
Meta is a bit special in that react runtime and compiler are guaranteed
to be up-to-date and compatible. It also has its own bundling and module
resolution logic, which makes importing from `react/compiler-runtime`
tricky.
I'm also fine with implementing the alternative which adds an internal
stub for `react-compiler-runtime` and
[bundles](5b0ef217ef/scripts/rollup/bundles.js (L120))
the runtime for internal builds.
Adds a way to configure how we insert deps for experimental purposes.
```
[
{
module: 'react',
imported: 'useEffect',
numRequiredArgs: 1,
},
{
module: 'MyExperimentalEffectHooks',
imported: 'useExperimentalEffect',
numRequiredArgs: 2,
},
]
```
would insert dependencies for calls of `useEffect` imported from `react`
if they have 1 argument and calls of useExperimentalEffect` from
`MyExperimentalEffectHooks` if they have 2 arguments. The pushed dep
array is appended to the arg list.
This is for researching/prototyping, not a feature we are releasing
imminently.
Putting up an early version of inferring effect dependencies to get
feedback on the approach. We do not plan to ship this as-is, and may not
start by going after direct `useEffect` calls. Until we make that
decision, the heuristic I use to detect when to insert effect deps will
suffice for testing.
The approach is simple: when we see a useEffect call with no dep array
we insert the deps inferred for the lambda passed in. If the first
argument is not a lambda then we do not do anything.
This diff is the easy part. I think the harder part will be ensuring
that we can infer the deps even when we have to bail out of memoization.
We have no other features that *must* run regardless of rules of react
violations. Does anyone foresee any issues using the compiler passes to
infer reactive deps when there may be violations?
I have a few questions:
1. Will there ever be more than one instruction in a block containing a
useEffect? if no, I can get rid of the`addedInstrs` variable that I use
to make sure I insert the effect deps array temp creation at the right
spot.
2. Are there any cases for resolving the first argument beyond just
looking at the lvalue's identifier id that I'll need to take into
account? e.g., do I need to recursively resolve certain bindings?
---------
Co-authored-by: Mofei Zhang <feifei0@meta.com>
```
=> Found "hermes-parser@0.25.1"
info Reasons this module exists
- "_project_#prettier-plugin-hermes-parser" depends on it
- Hoisted from "_project_#prettier-plugin-hermes-parser#hermes-parser"
- Hoisted from "_project_#eslint-plugin-react-compiler#hermes-parser"
- Hoisted from "_project_#snap#hermes-parser"
- Hoisted from "_project_#snap#babel-plugin-syntax-hermes-parser#hermes-parser"
- Hoisted from "_project_#eslint-plugin-react-compiler#hermes-eslint#hermes-parser"
info Disk size without dependencies: "1.49MB"
info Disk size with unique dependencies: "1.82MB"
info Disk size with transitive dependencies: "1.82MB"
info Number of shared dependencies: 1
✨ Done in 0.81s.
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31586).
* __->__ #31586
* #31585
```
=> Found "react@0.0.0-experimental-4beb1fd8-20241118"
info Reasons this module exists
- "_project_#babel-plugin-react-compiler" depends on it
- Hoisted from "_project_#babel-plugin-react-compiler#react"
- Hoisted from "_project_#snap#react"
info Disk size without dependencies: "252KB"
info Disk size with unique dependencies: "252KB"
info Disk size with transitive dependencies: "252KB"
info Number of shared dependencies: 0
✨ Done in 0.60s.
```
```
=> Found "react-dom@0.0.0-experimental-4beb1fd8-20241118"
info Reasons this module exists
- "_project_#babel-plugin-react-compiler" depends on it
- Hoisted from "_project_#babel-plugin-react-compiler#react-dom"
- Hoisted from "_project_#snap#react-dom"
info Disk size without dependencies: "8.04MB"
info Disk size with unique dependencies: "8.17MB"
info Disk size with transitive dependencies: "8.17MB"
info Number of shared dependencies: 1
✨ Done in 0.56s.
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31585).
* #31586
* __->__ #31585
`JSXMemberExpression` is currently the only instruction (that I know of)
that directly references identifier lvalues without a corresponding
`LoadLocal`.
This has some side effects:
- deadcode elimination and constant propagation now reach
JSXMemberExpressions
- we can delete `LoweredFunction.dependencies` without dangling
references (previously, the only reference to JSXMemberExpression
objects in HIR was in function dependencies)
- JSXMemberExpression now is consistent with all other instructions
(e.g. has a rvalue-producing LoadLocal)
'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31201).
* #31202
* #31203
* __->__ #31201
* #31200
* #31521
We were previously filtering out `ref.current` dependencies in
propagateScopeDependencies:checkValidDependency`. This is incorrect.
Instead, we now always take a dependency on ref values (the outer box)
as they may be reactive. Pruning is done in
pruneNonReactiveDependencies.
This PR includes a small patch to `collectReactiveIdentifier`. Prior to
this, we conservatively assumed that pruned scopes always produced
reactive declarations. This assumption fixed a bug with non-reactivity,
but some of these declarations are `useRef` calls. Now we have special
handling for this case
```js
// This often produces a pruned scope
React.useRef(1);
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31521).
* #31202
* #31203
* #31201
* #31200
* __->__ #31521
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`.
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
JSX inlining is a prod-only optimization. We want to enforce this while
maintaining the same compiler output in DEV and PROD.
Here we add a conditional to the transform that only replaces JSX with
object literals outside of DEV. Then a later build step can handle DCE
based on the value of `__DEV__`