[Babel] Add support for "use memo"

We want to start moving away from "Forget", so this PR adds support "use memo" 
and "use no memo" 

I've left "use forget" and "use no forget" directives unchanged for now, as we 
need to migrate existing users first and then come back and delete support for 
these directives.
This commit is contained in:
Sathya Gunasekaran
2024-02-19 17:48:49 +00:00
parent 530f2c293d
commit 770ca4ab3e
8 changed files with 167 additions and 7 deletions
@@ -36,20 +36,27 @@ export type CompilerPass = {
comments: (t.CommentBlock | t.CommentLine)[];
};
function findUseForgetDirective(directives: t.Directive[]): t.Directive | null {
function findDirectiveEnablingMemoization(
directives: t.Directive[]
): t.Directive | null {
for (const directive of directives) {
if (directive.value.value === "use forget") {
const directiveValue = directive.value.value;
if (directiveValue === "use forget" || directiveValue === "use memo") {
return directive;
}
}
return null;
}
function findUseNoForgetDirective(
function findDirectiveDisablingMemoization(
directives: t.Directive[]
): t.Directive | null {
for (const directive of directives) {
if (directive.value.value === "use no forget") {
const directiveValue = directive.value.value;
if (
directiveValue === "use no forget" ||
directiveValue === "use no memo"
) {
return directive;
}
}
@@ -189,7 +196,7 @@ export function compileProgram(
pass: CompilerPass
): void {
// Top level "use no forget", skip this file entirely
if (findUseNoForgetDirective(program.node.directives) != null) {
if (findDirectiveDisablingMemoization(program.node.directives) != null) {
return;
}
@@ -372,7 +379,9 @@ export function shouldVisitNode(fn: BabelFn, pass: CompilerPass): boolean {
}
if (fn.node.body.type === "BlockStatement") {
// Opt-outs disable compilation regardless of mode
const useNoForget = findUseNoForgetDirective(fn.node.body.directives);
const useNoForget = findDirectiveDisablingMemoization(
fn.node.body.directives
);
if (useNoForget != null) {
pass.opts.logger?.logEvent(pass.filename, {
kind: "CompileError",
@@ -387,7 +396,7 @@ export function shouldVisitNode(fn: BabelFn, pass: CompilerPass): boolean {
return false;
}
// Otherwise opt-ins enable compilation regardless of mode
if (findUseForgetDirective(fn.node.body.directives) != null) {
if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) {
return true;
}
}
@@ -0,0 +1,54 @@
## Input
```javascript
function Component(props) {
"use memo";
let x = [props.foo];
return <div x={x}>"foo"</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ foo: 1 }],
isComponent: true,
};
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(4);
let t0;
if ($[0] !== props.foo) {
t0 = [props.foo];
$[0] = props.foo;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
let t1;
if ($[2] !== x) {
t1 = <div x={x}>"foo"</div>;
$[2] = x;
$[3] = t1;
} else {
t1 = $[3];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ foo: 1 }],
isComponent: true,
};
```
### Eval output
(kind: ok) <div x="1">"foo"</div>
@@ -0,0 +1,11 @@
function Component(props) {
"use memo";
let x = [props.foo];
return <div x={x}>"foo"</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ foo: 1 }],
isComponent: true,
};
@@ -0,0 +1,29 @@
## Input
```javascript
"use no memo";
export default function foo(x, y) {
if (x) {
return foo(false, y);
}
return [y * 10];
}
```
## Code
```javascript
"use no memo";
export default function foo(x, y) {
if (x) {
return foo(false, y);
}
return [y * 10];
}
```
@@ -0,0 +1,8 @@
"use no memo";
export default function foo(x, y) {
if (x) {
return foo(false, y);
}
return [y * 10];
}
@@ -0,0 +1,37 @@
## Input
```javascript
function Component(props) {
"use no memo";
let x = [props.foo];
return <div x={x}>"foo"</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ foo: 1 }],
isComponent: true,
};
```
## Code
```javascript
function Component(props) {
"use no memo";
let x = [props.foo];
return <div x={x}>"foo"</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ foo: 1 }],
isComponent: true,
};
```
### Eval output
(kind: ok) <div x="1">"foo"</div>
@@ -0,0 +1,11 @@
function Component(props) {
"use no memo";
let x = [props.foo];
return <div x={x}>"foo"</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ foo: 1 }],
isComponent: true,
};
@@ -385,6 +385,7 @@ const skipFilter = new Set([
"useMemo-return-empty",
"useMemo-simple",
"use-no-forget-module-level",
"use-no-memo-module-level",
// defines multiple functions
"alias-while",
"babel-existing-react-import",