Conversation
| return false; | ||
| } | ||
|
|
||
| function getExternalFunctions( |
There was a problem hiding this comment.
This function is the primary change in this PR. When collecting external functions imports to insert into the program, we also generate uids for each import.
In cases we cannot generate uids (e.g. program shadows global variables), we bail out without modifying the program (see error.emit-freeze-conflicting-global.js)
|
This makes sense but I wonder if there's something a bit simpler. An idea I had played around with is creating an ImportContext at the program level to track all imports we need to create, and then passing this context to each environment that we create to compile functions. The ImportContext would hold a reference to the program's Scope, such that it could be used to call Babel's helper to generate unique identifier ids. Individual passes can then do As an extension, we could also traverse top-level imports and pre-populate the import context map, so that we can reuse existing imports and don't have to recreate those. What do you think? |
Yeah that makes a lot of sense! That would also help clean up the logic in It could also generalize as a generated variable name registry -- one thing I didn't love about this approach was that it renames hook imports to class ProgramContext {
uids: Set<string> = new Set();
hookNameConfig: string;
scope: t.Scope;
constructor(program: NodePath<t.Program>, hookNameConfig: string) {
this.hookNameConfig = hookNameConfig;
this.scope = program.scope;
}
addImportSpecifier(module: string, specifier: string): NonLocalBinding {
if (isHookName(specifier, this.hookNameConfig)) {
let maybeName = specifier;
let i = 0;
while (scope.hasReference(maybeName)) {
maybeName = `${specifier}_${i++}`;
}
}
...
}
makeUid(nameHint?: string): string {
const name = .generateUid(nameHint);
}
} |
| this.scope.hasBinding(name) || | ||
| this.scope.hasGlobal(name) || | ||
| this.scope.hasReference(name) |
There was a problem hiding this comment.
this also prevents names that might be shadowed in local scopes, right?
There was a problem hiding this comment.
Yep, I took this from Babel's implementation (https://github.com/babel/babel/blob/main/packages/babel-traverse/src/scope/index.ts#L510-L513).
| 1 | // @enableEmitFreeze @instrumentForget | ||
| 2 | function useFoo(props) { | ||
| > 3 | const __DEV__ = 'conflicting global'; | ||
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Todo: Encountered conflicting global in generated program. Conflict from local binding __DEV__ (3:3) |
There was a problem hiding this comment.
why is this one an error rather than renaming?
There was a problem hiding this comment.
This one is trickier -- we could synthesize a read off of globalThis or something similar, but I'm not familiar with browser / node version support. Note that this is a normal bailout (not a build failure)
Clean up jest-e2e setup since #32663 and other features need program context (e.g. changing imports)
|
Updates:
|
Clean up jest-e2e setup since #32663 and other features need program context (e.g. changing imports) --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32706). * #32663 * __->__ #32706
…adow vars
Avoid failing builds when imported function specifiers conflict by using babel's `generateUid`. Failing a build is very disruptive, as it usually presents to developers similar to a javascript parse error.
```js
import {logRender as _logRender} from 'instrument-runtime';
const logRender = () => { /* local conflicting implementation */ }
function Component_optimized() {
_logRender(); // inserted by compiler
}
```
Currently, we fail builds (even in `panicThreshold:none` cases) when import specifiers are detected to conflict with existing local variables. The reason we destructively throw (instead of bailing out) is because (1) we first generate identifier references to the conflicting name in compiled functions, (2) replaced original functions with compiled functions, and then (3) finally check for conflicts.
When we finally check for conflicts, it's too late to bail out.
```js
// import {logRender} from 'instrument-runtime';
const logRender = () => { /* local conflicting implementation */ }
function Component_optimized() {
logRender(); // inserted by compiler
}
```
…adow vars (#32663) Avoid failing builds when imported function specifiers conflict by using babel's `generateUid`. Failing a build is very disruptive, as it usually presents to developers similar to a javascript parse error. ```js import {logRender as _logRender} from 'instrument-runtime'; const logRender = () => { /* local conflicting implementation */ } function Component_optimized() { _logRender(); // inserted by compiler } ``` Currently, we fail builds (even in `panicThreshold:none` cases) when import specifiers are detected to conflict with existing local variables. The reason we destructively throw (instead of bailing out) is because (1) we first generate identifier references to the conflicting name in compiled functions, (2) replaced original functions with compiled functions, and then (3) finally check for conflicts. When we finally check for conflicts, it's too late to bail out. ```js // import {logRender} from 'instrument-runtime'; const logRender = () => { /* local conflicting implementation */ } function Component_optimized() { logRender(); // inserted by compiler } ``` DiffTrain build for [c61e75b](c61e75b)
Avoid failing builds when imported function specifiers conflict by using babel's
generateUid. Failing a build is very disruptive, as it usually presents to developers similar to a javascript parse error.Currently, we fail builds (even in
panicThreshold:nonecases) when import specifiers are detected to conflict with existing local variables. The reason we destructively throw (instead of bailing out) is because (1) we first generate identifier references to the conflicting name in compiled functions, (2) replaced original functions with compiled functions, and then (3) finally check for conflicts.When we finally check for conflicts, it's too late to bail out.