Skip to content

Commit 008a6d4

Browse files
authored
[compiler] Don't emit spurious import { c as _c } for discarded functions (facebook#36500)
## What Codegen registers `_c` (the memo cache import) as a side effect whenever a function compiles with memo slots. The registration persists on `ProgramContext.imports` even if the function is later discarded (`'use no forget'`, `'use no memo'`, lint mode, validation errors). If other applied functions in the file compile to 0 memo slots, the stale `import { c as _c } from "react/compiler-runtime";` leaks into the output. ## Fix In `applyCompiledFunctions`, drop the memo cache import if no applied function uses memo slots. If `react/compiler-runtime` has no remaining specifiers, drop the module entry too so we don't emit a bare `import "react/compiler-runtime";`. ## Reproducer `use-no-forget-multiple-with-eslint-suppression.js`: ```js import {useRef} from 'react'; const useControllableState = options => {}; function NoopComponent() {} function Component() { 'use no forget'; const ref = useRef(null); // eslint-disable-next-line react-hooks/rules-of-hooks ref.current = 'bad'; return <button ref={ref} />; } ``` `NoopComponent` applies with 0 memo slots. `Component` is opted out, but codegen already registered `_c` for it. Before: ```js import { c as _c } from "react/compiler-runtime"; import { useRef } from "react"; ``` After: ```js import { useRef } from "react"; ``` ## Prior art TS counterpart to the Rust port's fix in 7e26eb8. That commit also added `no-cache-slots-no-import.js`, which codifies the "no memo slots, no import" rule. ## Test plan - `yarn snap`: 1719/1719 passing - Snapshot for `use-no-forget-multiple-with-eslint-suppression` loses its spurious `_c` import - `no-cache-slots-no-import` still passes
1 parent b91823e commit 008a6d4

3 files changed

Lines changed: 18 additions & 1 deletion

File tree

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,17 @@ export class ProgramContext {
151151
);
152152
}
153153

154+
removeMemoCacheImport(): void {
155+
const moduleImports = this.imports.get(this.reactRuntimeModule);
156+
if (moduleImports == null) {
157+
return;
158+
}
159+
moduleImports.delete('c');
160+
if (moduleImports.size === 0) {
161+
this.imports.delete(this.reactRuntimeModule);
162+
}
163+
}
164+
154165
/**
155166
*
156167
* @param externalFunction

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,13 @@ function applyCompiledFunctions(
775775

776776
// Forget compiled the component, we need to update existing imports of useMemoCache
777777
if (compiledFns.length > 0) {
778+
// Codegen may have registered `_c` for a function that was later discarded.
779+
const anyAppliedUsesMemo = compiledFns.some(
780+
result => result.compiledFn.memoSlotsUsed > 0,
781+
);
782+
if (!anyAppliedUsesMemo) {
783+
programContext.removeMemoCacheImport();
784+
}
778785
addImportsToProgram(program, programContext);
779786
}
780787
}

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-forget-multiple-with-eslint-suppression.expect.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export const FIXTURE_ENTRYPOINT = {
2525
## Code
2626

2727
```javascript
28-
import { c as _c } from "react/compiler-runtime";
2928
import { useRef } from "react";
3029

3130
const useControllableState = (options) => {};

0 commit comments

Comments
 (0)