@samitouri / QOS-React / commits / 770ca4ab3e

[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.

Sathya Gunasekaran committed Feb 19, 2024 at 17:48 UTC 770ca4ab3ee173ab975d61d5022728f0cef5c67f
8 files changed +167 -7
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts
+16 -7
@@ -36,20 +36,27 @@ export type CompilerPass = {
36 comments: (t.CommentBlock | t.CommentLine)[];
37 };
38
39 -function findUseForgetDirective(directives: t.Directive[]): t.Directive | null {
39 +function findDirectiveEnablingMemoization(
40 + directives: t.Directive[]
41 +): t.Directive | null {
42 for (const directive of directives) {
41 - if (directive.value.value === "use forget") {
43 + const directiveValue = directive.value.value;
44 + if (directiveValue === "use forget" || directiveValue === "use memo") {
45 return directive;
46 }
47 }
48 return null;
49 }
50
48 -function findUseNoForgetDirective(
51 +function findDirectiveDisablingMemoization(
52 directives: t.Directive[]
53 ): t.Directive | null {
54 for (const directive of directives) {
52 - if (directive.value.value === "use no forget") {
55 + const directiveValue = directive.value.value;
56 + if (
57 + directiveValue === "use no forget" ||
58 + directiveValue === "use no memo"
59 + ) {
60 return directive;
61 }
62 }
@@ -189,7 +196,7 @@ export function compileProgram(
196 pass: CompilerPass
197 ): void {
198 // Top level "use no forget", skip this file entirely
192 - if (findUseNoForgetDirective(program.node.directives) != null) {
199 + if (findDirectiveDisablingMemoization(program.node.directives) != null) {
200 return;
201 }
202
@@ -372,7 +379,9 @@ export function shouldVisitNode(fn: BabelFn, pass: CompilerPass): boolean {
379 }
380 if (fn.node.body.type === "BlockStatement") {
381 // Opt-outs disable compilation regardless of mode
375 - const useNoForget = findUseNoForgetDirective(fn.node.body.directives);
382 + const useNoForget = findDirectiveDisablingMemoization(
383 + fn.node.body.directives
384 + );
385 if (useNoForget != null) {
386 pass.opts.logger?.logEvent(pass.filename, {
387 kind: "CompileError",
@@ -387,7 +396,7 @@ export function shouldVisitNode(fn: BabelFn, pass: CompilerPass): boolean {
396 return false;
397 }
398 // Otherwise opt-ins enable compilation regardless of mode
390 - if (findUseForgetDirective(fn.node.body.directives) != null) {
399 + if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) {
400 return true;
401 }
402 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/use-memo-simple.expect.md new
+54
@@ -0,0 +1,54 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + "use memo";
7 + let x = [props.foo];
8 + return <div x={x}>"foo"</div>;
9 +}
10 +
11 +export const FIXTURE_ENTRYPOINT = {
12 + fn: Component,
13 + params: [{ foo: 1 }],
14 + isComponent: true,
15 +};
16 +
17 +```
18 +
19 +## Code
20 +
21 +```javascript
22 +import { unstable_useMemoCache as useMemoCache } from "react";
23 +function Component(props) {
24 + const $ = useMemoCache(4);
25 + let t0;
26 + if ($[0] !== props.foo) {
27 + t0 = [props.foo];
28 + $[0] = props.foo;
29 + $[1] = t0;
30 + } else {
31 + t0 = $[1];
32 + }
33 + const x = t0;
34 + let t1;
35 + if ($[2] !== x) {
36 + t1 = <div x={x}>"foo"</div>;
37 + $[2] = x;
38 + $[3] = t1;
39 + } else {
40 + t1 = $[3];
41 + }
42 + return t1;
43 +}
44 +
45 +export const FIXTURE_ENTRYPOINT = {
46 + fn: Component,
47 + params: [{ foo: 1 }],
48 + isComponent: true,
49 +};
50 +
51 +```
52 +
53 +### Eval output
54 +(kind: ok) <div x="1">"foo"</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/use-memo-simple.js new
+11
@@ -0,0 +1,11 @@
1 +function Component(props) {
2 + "use memo";
3 + let x = [props.foo];
4 + return <div x={x}>"foo"</div>;
5 +}
6 +
7 +export const FIXTURE_ENTRYPOINT = {
8 + fn: Component,
9 + params: [{ foo: 1 }],
10 + isComponent: true,
11 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/use-no-memo-module-level.expect.md new
+29
@@ -0,0 +1,29 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +"use no memo";
6 +
7 +export default function foo(x, y) {
8 + if (x) {
9 + return foo(false, y);
10 + }
11 + return [y * 10];
12 +}
13 +
14 +```
15 +
16 +## Code
17 +
18 +```javascript
19 +"use no memo";
20 +
21 +export default function foo(x, y) {
22 + if (x) {
23 + return foo(false, y);
24 + }
25 + return [y * 10];
26 +}
27 +
28 +```
29 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/use-no-memo-module-level.js new
+8
@@ -0,0 +1,8 @@
1 +"use no memo";
2 +
3 +export default function foo(x, y) {
4 + if (x) {
5 + return foo(false, y);
6 + }
7 + return [y * 10];
8 +}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/use-no-memo-simple.expect.md new
+37
@@ -0,0 +1,37 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + "use no memo";
7 + let x = [props.foo];
8 + return <div x={x}>"foo"</div>;
9 +}
10 +
11 +export const FIXTURE_ENTRYPOINT = {
12 + fn: Component,
13 + params: [{ foo: 1 }],
14 + isComponent: true,
15 +};
16 +
17 +```
18 +
19 +## Code
20 +
21 +```javascript
22 +function Component(props) {
23 + "use no memo";
24 + let x = [props.foo];
25 + return <div x={x}>"foo"</div>;
26 +}
27 +
28 +export const FIXTURE_ENTRYPOINT = {
29 + fn: Component,
30 + params: [{ foo: 1 }],
31 + isComponent: true,
32 +};
33 +
34 +```
35 +
36 +### Eval output
37 +(kind: ok) <div x="1">"foo"</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/use-no-memo-simple.js new
+11
@@ -0,0 +1,11 @@
1 +function Component(props) {
2 + "use no memo";
3 + let x = [props.foo];
4 + return <div x={x}>"foo"</div>;
5 +}
6 +
7 +export const FIXTURE_ENTRYPOINT = {
8 + fn: Component,
9 + params: [{ foo: 1 }],
10 + isComponent: true,
11 +};
compiler/packages/sprout/src/SproutTodoFilter.ts
+1
@@ -385,6 +385,7 @@ const skipFilter = new Set([
385 "useMemo-return-empty",
386 "useMemo-simple",
387 "use-no-forget-module-level",
388 + "use-no-memo-module-level",
389 // defines multiple functions
390 "alias-while",
391 "babel-existing-react-import",