```
=> 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
Since `enableRefAsProp` shipped everywhere, the ReactElement
implementation on prod puts refs on both `element.ref` and
`element.props.ref`. Here we let the `ref` case fall through so its now
available on props, matching the JSX runtime.
Now that we rely on function context exclusively, let's clean up
`HIRFunction.context` after DCE. This PR is in preparation of #31204,
which would otherwise have unnecessary declarations (of context values
that become entirely DCE'd)
'
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31202).
* __->__ #31202
* #31203
* #31201
* #31200
* #31521
`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
Recursively visit inner function instructions to extract dependencies
instead of using `LoweredFunction.dependencies` directly.
This is currently gated by enableFunctionDependencyRewrite, which needs
to be removed before we delete `LoweredFunction.dependencies` altogether
(#31204).
Some nice side effects
- optional-chaining deps for inner functions
- full DCE and outlining for inner functions (see #31202)
- fewer extraneous instructions (see #31204)
-
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31200).
* #31202
* #31203
* #31201
* __->__ #31200
* #31521
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
## Summary
`@rollup/plugin-typescript` emits a warning while building, hinting that
`outputToFilesystem` defaults to true.
Although "noEmit" is set to `true` for the tsconfig, rollup writes a
`dist/.tsbuildinfo`. That file is then also shipped inside the npm
module and doesn't offer any benefit for library consumers. Setting this
option to false results in the file not being written and thus omitted
from the npm module.
## How did you test this change?
`dist/.tsbuildinfo` is not emitted any more.
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.
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.
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.
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
`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
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
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
`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
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__`
When resolving import specifiers from the react namespace (`import
{imported as local} from 'react'`), we were previously only checking if
the `imported` identifier was a hook if we didn't already have its
definition in the global registry. We also need to check if `local` is a
hook in the case of aliasing since there may be hook-like APIs in react
that don't start with `use` (eg they are experimental or unstable).
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31384).
* #31385
* __->__ #31384
* #31383
This PR loosens the restriction on the types of computed properties we
can handle.
Previously, we would disallow anything that is not an identifier because
non-identifiers could be mutating. But member expressions are not
mutating so we can treat them similar to identifiers.
<!--
Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.
Before submitting a pull request, please make sure the following is
done:
1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
10. If you haven't already, complete the CLA.
Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->
## Summary
Since the Babel plugin is bundled into a single file (except for
`@babel/types`
45804af18d/compiler/packages/babel-plugin-react-compiler/rollup.config.js (L18))
we can move these deps to `devDependencies`.
Main motivation is e.g. not installing ancient version of
`pretty-format` (asked in https://github.com/facebook/react/issues/29062
without getting a reason, but if consumers can just skip the deps
entirely that's even better).
<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->
## How did you test this change?
I tested by installing the plugin into an empty project, deleting
everything in `node_modules` _except_ for `babel-plugin-react-compiler`
and doing `require('babel-plugin-react-compiler')`. It still worked
fine, so it should work in other cases as well 😀
<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
If you leave this empty, your PR will very likely be closed.
-->
InlineJSXTransform wasn't traversing into function expressions or object
methods, so any JSX inside such functions wouldn't have gotten inlined.
This PR updates to traverse nested functions to transform all JSX within
a hook or component.
Note that this still doesn't transform JSX outside of components or
hooks, ie in standalone render helpers.
Currently, the react compiler can not compile within callbacks which can
potentially cause over rendering. Consider this example:
```jsx
function Component(countries, onDelete) {
const name = useFoo();
return countries.map(() => {
return (
<Foo>
<Bar name={name}/>
<Baz onclick={onDelete} />
</Foo>
);
});
}
```
In this case, there's no memoization of the nested jsx elements. But
instead if we were to manually refactor the nested jsx into separate
component like this:
```jsx
function Component(countries, onDelete) {
const name = useFoo();
return countries.map(() => {
return <Temp name={name} onDelete={onDelete} />;
});
}
function Temp({ name, onDelete }) {
return (
<Foo>
<Bar name={name} />
<Baz onclick={onDelete} />
</Foo>
);
}
```
The compiler can now optimise both these components:
```jsx
function Component(countries, onDelete) {
const $ = _c(4);
const name = useFoo();
let t0;
if ($[0] !== name || $[1] !== onDelete || $[2] !== countries) {
t0 = countries.map(() => <Temp name={name} onDelete={onDelete} />);
$[0] = name;
$[1] = onDelete;
$[2] = countries;
$[3] = t0;
} else {
t0 = $[3];
}
return t0;
}
function Temp(t0) {
const $ = _c(7);
const { name, onDelete } = t0;
let t1;
if ($[0] !== name) {
t1 = <Bar name={name} />;
$[0] = name;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== onDelete) {
t2 = <Baz onclick={onDelete} />;
$[2] = onDelete;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== t1 || $[5] !== t2) {
t3 = (
<Foo>
{t1}
{t2}
</Foo>
);
$[4] = t1;
$[5] = t2;
$[6] = t3;
} else {
t3 = $[6];
}
return t3;
}
```
Now, when `countries` is updated by adding one single value, only the
newly added value is re-rendered and not the entire list. Rather than
having to do this manually, this PR teaches the react compiler to do
this transformation.
This PR adds a new pass (`OutlineJsx`) to capture nested jsx statements
and outline them in a separate component. This newly outlined component
can then by memoized by the compiler, giving us more fine grained
rendering.
Summary:
With the previous PR we no longer need to mark identifiers as reactive in contexts where we don't have places. We already deleted most uses of markReactiveId; the last case was to track identifiers through loadlocals etc -- but we already use a disjoint alias map that accounts for loadlocals when setting reactivity.
ghstack-source-id: 69ce0a78b0
Pull Request resolved: https://github.com/facebook/react/pull/31178
Summary:
The official guidance for useRef notes an exception to the rule that refs cannot be accessed during render: to avoid recreating the ref's contents, you can test that the ref is uninitialized and then initialize it using an if statement:
```
if (ref.current == null) {
ref.current = SomeExpensiveOperation()
}
```
The compiler didn't recognize this exception, however, leading to code that obeyed all the official guidance for refs being rejected by the compiler. This PR fixes that, by extending the ref validation machinery with an awareness of guard operations that allow lazy initialization. We now understand `== null` and similar operations, when applied to a ref and consumed by an if terminal, as marking the consequent of the if as a block in which the ref can be safely written to. In order to do so we need to create a notion of ref ids, which link different usages of the same ref via both the ref and the ref value.
ghstack-source-id: d2729274f3
Pull Request resolved: https://github.com/facebook/react/pull/31188
Summary:
The fact that phis are identifiers rather than places is unfortunate in a few cases. In some later analyses, we might wish to know whether a phi is reactive, but we don't have an easy way to do that currently.
Most of the changes here is just replacing phi.id with phi.place.identifier and such. Interesting bits are EnterSSA (several functions now take places rather than identifiers, and InferReactivePlaces now needs to mark places as reactive explicitly.
ghstack-source-id: 5f4fb396cd
Pull Request resolved: https://github.com/facebook/react/pull/31171
Updates the compiler to always import from `react-compiler-runtime` by
default. The runtime then decides whether to use the official or
userspace implementation of useMemoCache.
When we added support for Reanimated, we didn't distinguish between true
globals (i.e. identifiers with no static resolutions), module types, and
imports #29188. For the past 3-4 months, Reanimated imports were not
being matched to the correct hook / function shape we match globals and
module imports against two different registries.
This PR fixes our support for Reanimated library functions imported
under `react-native-reanimated`. See test fixtures for details
Stack from [ghstack](https://github.com/ezyang/ghstack) (oldest at
bottom):
* __->__ #31066
* #31032
Prior to this PR, we consider all of a nested function's accessed paths
as 'hoistable' (to the basic block in which the function was defined).
Now, we traverse nested functions and find all paths hoistable to their
*entry block*.
Note that this only replaces the *hoisting* part of function
declarations, not dependencies. This realistically only affects optional
chains within functions, which always get truncated to its inner
non-optional path (see
[todo-infer-function-uncond-optionals-hoisted.tsx](576f3c0aa8/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/reduce-reactive-deps/todo-infer-function-uncond-optionals-hoisted.tsx))
See newly added test fixtures for details
Update: Note that toggling `enableTreatFunctionDepsAsConditional` makes
a non-trivial impact on granularity of inferred deps (i.e. we find that
function declarations uniquely identify some paths as hoistable).
Snapshot comparison of internal code shows ~2.5% of files get worse
dependencies ([internal
link](https://www.internalfb.com/phabricator/paste/view/P1625792186))
Stack from [ghstack](https://github.com/ezyang/ghstack) (oldest at
bottom):
* #31066
* __->__ #31032
Prior to this PR, we check whether the property load source (e.g. the
evaluation of `<base>` in `<base>.property`) is mutable + scoped to
determine whether the property load itself is eligible for hoisting.
This changes to check the base identifier of the load.
- This is needed for the next PR #31066. We want to evaluate whether the
base identifier is mutable within the context of the *outermost
function*. This is because all LoadLocals and PropertyLoads within a
nested function declaration have mutable-ranges within the context of
the function, but the base identifier is a context variable.
- A side effect is that we no longer infer loads from props / other
function arguments as mutable in edge cases (e.g. props escaping out of
try-blocks or being assigned to context variables)
Adds HIR version of `PropagateScopeDeps` to handle optional chaining.
Internally, this improves memoization on ~4% of compiled files (internal links: [1](https://www.internalfb.com/intern/paste/P1610406497/))
Summarizing the changes in this PR.
1. `CollectOptionalChainDependencies` recursively traverses optional blocks down to the base. From the base, we build up a set of `baseIdentifier.propertyA?.propertyB` mappings.
The tricky bit here is that optional blocks sometimes reference other optional blocks that are *not* part of the same chain e.g. a(c?.d)?.d. See code + comments in `traverseOptionalBlock` for how we avoid concatenating unrelated blocks.
2. Adding optional chains into non-null object calculation.
(Note that marking `a?.b` as 'non-null' means that `a?.b.c` is safe to evaluate, *not* `(a?.b).c`. Happy to rename this / reword comments accordingly if there's a better term)
This pass is split into two stages. (1) collecting non-null objects by block and (2) propagating non-null objects across blocks. The only significant change here was to (2). We add an extra reduce step `X=Reduce(Union(X, Intersect(X_neighbors)))` to merge optional and non-optional nodes (e.g. nonNulls=`{a, a?.b}` reduces to `{a, a.b}`)
3. Adding optional chains into dependency calculation.
This was the trickiest. We need to take the "maximal" property chain as a dependency. Prior to this PR, we avoided taking subpaths e.g. `a.b` of `a.b.c` as dependencies by only visiting non-PropertyLoad/LoadLocal instructions. This effectively only recorded the property-path at site-of-use.
Unfortunately, this *quite* doesn't work for optional chains for a few reasons:
- We would need to skip relevant `StoreLocal`/`Branch terminal` instructions (but only those within optional blocks that have been successfully read).
- Given an optional chain, either (1) only a subpath or (2) the entire path can be represented as a PropertyLoad. We cannot directly add the last hoistable optional-block as a dependency as MethodCalls are an edge case e.g. given a?.b.c(), we should depend on `a?.b`, not `a?.b.c`
This means that we add its dependency at either the innermost unhoistable optional-block or when encountering it within its phi-join.
4. Handle optional chains in DeriveMinimalDependenciesHIR.
This was also a bit tricky to formulate. Ideally, we would avoid a 2^3 case join (cond | uncond cfg, optional | not optional load, access | dependency). This PR attempts to simplify by building two trees
1. First add each hoistable path into a tree containing `Optional | NonOptional` nodes.
2. Then add each dependency into another tree containing `Optional | NonOptional`, `Access | Dependency` nodes, truncating the dependency at the earliest non-hoistable node (i.e. non-matching pair when walking the hoistable tree)
ghstack-source-id: a2170f2628
Pull Request resolved: https://github.com/facebook/react/pull/31037