-
Notifications
You must be signed in to change notification settings - Fork 49.6k
[compiler] Option to infer names for anonymous functions #34410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Note that we prune useMemo/useCallback, which means if you have eg |
I think we had a similar transform in WWW (maybe?) that is a bit more nuanced — it was more general (worked for any callbacks) and gave names like |
|
You could also imagine this convention being nested, e.g. |
|
I see a point though that maybe parens are already enough to distinguish it, and otherwise |
|
Interesting. For the nested case I could imagine something like |
114c30c to
0ca5a23
Compare
| const inner = { "Component[named > inner]": () => props.named }[ | ||
| "Component[named > inner]" | ||
| ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nested anonymous functions assigned to variables
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that if the inner function was originally named, then we wouldn't change that name. So if you have
useEffect(() => {
function foo() {}
}, [...]);
We won't rename foo or wrap it in the object+property access structure. The case highlighted here for named > inner is happening because both of those are actually anonymous functions that are assigned to a variable.
| const methodCall = SharedRuntime.identity(t2); | ||
| let t3; | ||
| if ($[6] !== props.call) { | ||
| t3 = { "Component[identity(arg0)]": () => props.call }[ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
argument indices for anonymous functions passed to calls
| "Component[useEffect(arg0) > JSON.stringify(arg2)]": () => | ||
| props.useEffect, | ||
| }["Component[useEffect(arg0) > JSON.stringify(arg2)]"], | ||
| ); | ||
| const g = { "Component[useEffect(arg0) > g]": () => props.useEffect }[ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
more cases of nested anonymous functions
|
@gaearon done, what do you think? |
|
Ah sweet, I think this is nice! Hard to say how it'll feel without looking at some traces "from the real world" so I'd suggest giving it a try with some real code next to see if it's helpful. |
0ca5a23 to
017184e
Compare
|
Yeah I think we need real-world feedback on this one. The feature is off by default so we can ship, collect feedback, and iterate. |
|
Okay this is like my favorite thing I've seen in years, imagining the amount of time I would have saved by having anything to search for in situations like this and it's incredible to think about. To massively expand scope, could this plausibly evolve into a TC39 proposal? |
I don’t think we’re likely to pursue a TC39 proposal. It would certainly be nice to not need an object + property lookup just to set a name hint, but we can do this in dev mode via bundler integrations. We’ll start there. |
| const namedElementAttr = t5; | ||
| let t6; | ||
| if ($[12] !== props.hookArgument) { | ||
| t6 = { "Component[useIdentity(arg0)]": () => props.hookArgument }[ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is similar to the case for existing memoization. E.g. const onClick = useCallback(() => fn(), []). Any chance we can special case the React Hooks to get more accurate names? Next.js would've produced Component.useCallback[onClick] here instead of Component[useCallback(arg0)]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, that doesn't really make sense for arbitrary hooks, useIdentity is a contrived function for testing. Note that the compiler strips out useCallback(), so your example would just become Component[onClick]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was under the impression that it only does that for equivalent memoization not always?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of two things will happen:
- File is compiled. This only happens if the compiler can guarantee it preserved all the existing memoization. In this mode, any useMemo/useCallback are removed+rewritten, so the naming pass would see eg
const onClick = () => {...}and name itComponent[onClick]. - File is not compiled, code is unmodified. We don't add any names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that works perfectly then. Thanks for explaining.
...bel-plugin-react-compiler/src/__tests__/fixtures/compiler/name-anonymous-functions.expect.md
Outdated
Show resolved
Hide resolved
017184e to
9bfd990
Compare
Adds a `@enableNameAnonymousFunctions` feature to infer helpful names for anonymous functions within components and hooks. The logic is inspired by a custom Next.js transform, flagged to us by @eps1lon, that does something similar. Implementing this transform within React Compiler means that all React (Compiler) users can benefit from more helpful names when debugging. The idea builds on the fact that JS engines try to infer helpful names for anonymous functions (in stack traces) when those functions are accessed through an object property lookup: ```js ({'a[xyz]': () => { throw new Error('hello!') } }['a[xyz]'])() // Stack trace: Uncaught Error: hello! at a[xyz] (<anonymous>:1:26) // <-- note the name here at <anonymous>:1:60 ``` The new NameAnonymousFunctions transform is gated by the above flag, which is off by default. It attemps to infer names for functions as follows: First, determine a "local" name: * Assigning a function to a named variable uses the variable name. `const f = () => {}` gets the name "f". * Passing the function as an argument to a function gets the name of the function, ie `foo(() => ...)` get the name "foo()", `foo.bar(() => ...)` gets the name "foo.bar()". Note the parenthesis to help understand that it was part of a call. * Passing the function to a known hook uses the name of the hook, `useEffect(() => ...)` uses "useEffect()". * Passing the function as a JSX prop uses the element and attr name, eg `<div onClick={() => ...}` uses "<div>.onClick". Second, the local name is combined with the name of the outer component/hook, so the final names will be strings like `Component[f]` or `useMyHook[useEffect()]`.
9bfd990 to
7455473
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀
Adds a `@enableNameAnonymousFunctions` feature to infer helpful names for anonymous functions within components and hooks. The logic is inspired by a custom Next.js transform, flagged to us by @eps1lon, that does something similar. Implementing this transform within React Compiler means that all React (Compiler) users can benefit from more helpful names when debugging. The idea builds on the fact that JS engines try to infer helpful names for anonymous functions (in stack traces) when those functions are accessed through an object property lookup: ```js ({'a[xyz]': () => { throw new Error('hello!') } }['a[xyz]'])() // Stack trace: Uncaught Error: hello! at a[xyz] (<anonymous>:1:26) // <-- note the name here at <anonymous>:1:60 ``` The new NameAnonymousFunctions transform is gated by the above flag, which is off by default. It attemps to infer names for functions as follows: First, determine a "local" name: * Assigning a function to a named variable uses the variable name. `const f = () => {}` gets the name "f". * Passing the function as an argument to a function gets the name of the function, ie `foo(() => ...)` get the name "foo()", `foo.bar(() => ...)` gets the name "foo.bar()". Note the parenthesis to help understand that it was part of a call. * Passing the function to a known hook uses the name of the hook, `useEffect(() => ...)` uses "useEffect()". * Passing the function as a JSX prop uses the element and attr name, eg `<div onClick={() => ...}` uses "<div>.onClick". Second, the local name is combined with the name of the outer component/hook, so the final names will be strings like `Component[f]` or `useMyHook[useEffect()]`. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34410). * #34434 * __->__ #34410 DiffTrain build for [a9410fb](a9410fb)
Adds a `@enableNameAnonymousFunctions` feature to infer helpful names for anonymous functions within components and hooks. The logic is inspired by a custom Next.js transform, flagged to us by @eps1lon, that does something similar. Implementing this transform within React Compiler means that all React (Compiler) users can benefit from more helpful names when debugging. The idea builds on the fact that JS engines try to infer helpful names for anonymous functions (in stack traces) when those functions are accessed through an object property lookup: ```js ({'a[xyz]': () => { throw new Error('hello!') } }['a[xyz]'])() // Stack trace: Uncaught Error: hello! at a[xyz] (<anonymous>:1:26) // <-- note the name here at <anonymous>:1:60 ``` The new NameAnonymousFunctions transform is gated by the above flag, which is off by default. It attemps to infer names for functions as follows: First, determine a "local" name: * Assigning a function to a named variable uses the variable name. `const f = () => {}` gets the name "f". * Passing the function as an argument to a function gets the name of the function, ie `foo(() => ...)` get the name "foo()", `foo.bar(() => ...)` gets the name "foo.bar()". Note the parenthesis to help understand that it was part of a call. * Passing the function to a known hook uses the name of the hook, `useEffect(() => ...)` uses "useEffect()". * Passing the function as a JSX prop uses the element and attr name, eg `<div onClick={() => ...}` uses "<div>.onClick". Second, the local name is combined with the name of the outer component/hook, so the final names will be strings like `Component[f]` or `useMyHook[useEffect()]`. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34410). * #34434 * __->__ #34410 DiffTrain build for [a9410fb](a9410fb)
Adds a
@enableNameAnonymousFunctionsfeature to infer helpful names for anonymous functions within components and hooks. The logic is inspired by a custom Next.js transform, flagged to us by @eps1lon, that does something similar. Implementing this transform within React Compiler means that all React (Compiler) users can benefit from more helpful names when debugging.The idea builds on the fact that JS engines try to infer helpful names for anonymous functions (in stack traces) when those functions are accessed through an object property lookup:
The new NameAnonymousFunctions transform is gated by the above flag, which is off by default. It attemps to infer names for functions as follows:
First, determine a "local" name:
const f = () => {}gets the name "f".foo(() => ...)get the name "foo()",foo.bar(() => ...)gets the name "foo.bar()". Note the parenthesis to help understand that it was part of a call.useEffect(() => ...)uses "useEffect()".<div onClick={() => ...}uses "Second, the local name is combined with the name of the outer component/hook, so the final names will be strings like
Component[f]oruseMyHook[useEffect()].Stack created with Sapling. Best reviewed with ReviewStack.