compiler: fixtures for fast-refresh mode (w todos)
ghstack-source-id: 65dd14fe9b37328bd60fe791b23dde54da10b285 Pull Request resolved: https://github.com/facebook/react/pull/29175
Joe Savona committed
May 20, 2024 at 18:20 UTC
afb2c39ec36d40fff362be465e2a310661469630
8 files changed
+302
-8
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts
+11
-8
@@ -81,18 +81,21 @@ export function codegenFunction(
81
);
82
83
/**
84
- * Hot-module reloading reuses component instances at runtime even as the source of the component changes.
84
+ * Fast Refresh reuses component instances at runtime even as the source of the component changes.
85
* The generated code needs to prevent values from one version of the code being reused after a code cange.
86
* If HMR detection is enabled and we know the source code of the component, assign a cache slot to track
87
* the source hash, and later, emit code to check for source changes and reset the cache on source changes.
88
*/
89
- let hotModuleReloadState: { cacheIndex: number; hash: string } | null = null;
89
+ let fastRefreshState: {
90
+ cacheIndex: number;
91
+ hash: string;
92
+ } | null = null;
93
if (
94
fn.env.config.enableResetCacheOnSourceFileChanges &&
95
fn.env.code !== null
96
) {
97
const hash = createHmac("sha256", fn.env.code).digest("hex");
95
- hotModuleReloadState = {
98
+ fastRefreshState = {
99
cacheIndex: cx.nextCacheIndex,
100
hash,
101
};
@@ -131,7 +134,7 @@ export function codegenFunction(
134
),
135
])
136
);
134
- if (hotModuleReloadState !== null) {
137
+ if (fastRefreshState !== null) {
138
// HMR detection is enabled, emit code to reset the memo cache on source changes
139
const index = cx.synthesizeName("$i");
140
preface.push(
@@ -140,10 +143,10 @@ export function codegenFunction(
143
"!==",
144
t.memberExpression(
145
t.identifier(cx.synthesizeName("$")),
143
- t.numericLiteral(hotModuleReloadState.cacheIndex),
146
+ t.numericLiteral(fastRefreshState.cacheIndex),
147
true
148
),
146
- t.stringLiteral(hotModuleReloadState.hash)
149
+ t.stringLiteral(fastRefreshState.hash)
150
),
151
t.blockStatement([
152
t.forStatement(
@@ -185,10 +188,10 @@ export function codegenFunction(
188
"=",
189
t.memberExpression(
190
t.identifier(cx.synthesizeName("$")),
188
- t.numericLiteral(hotModuleReloadState.cacheIndex),
191
+ t.numericLiteral(fastRefreshState.cacheIndex),
192
true
193
),
191
- t.stringLiteral(hotModuleReloadState.hash)
194
+ t.stringLiteral(fastRefreshState.hash)
195
)
196
),
197
])
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md
new
+104
@@ -0,0 +1,104 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @compilationMode(infer)
6
+import { useEffect, useMemo, useState } from "react";
7
+import { ValidateMemoization } from "shared-runtime";
8
+
9
+let pretendConst = 0;
10
+
11
+function unsafeResetConst() {
12
+ pretendConst = 0;
13
+}
14
+
15
+function unsafeUpdateConst() {
16
+ pretendConst += 1;
17
+}
18
+
19
+function Component() {
20
+ useState(() => {
21
+ // unsafe: reset the constant when first rendering the instance
22
+ unsafeResetConst();
23
+ });
24
+ // UNSAFE! changing a module variable that is read by a component is normally
25
+ // unsafe, but in this case we're simulating a fast refresh between each render
26
+ unsafeUpdateConst();
27
+
28
+ // In production mode (no @enableResetCacheOnSourceFileChanges) memo caches are not
29
+ // reset unless the deps change
30
+ const value = useMemo(() => [{ pretendConst }], []);
31
+
32
+ return <ValidateMemoization inputs={[]} output={value} />;
33
+}
34
+
35
+export const FIXTURE_ENTRYPOINT = {
36
+ fn: Component,
37
+ params: [{}],
38
+ sequentialRenders: [{}, {}],
39
+};
40
+
41
+```
42
+
43
+## Code
44
+
45
+```javascript
46
+import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer)
47
+import { useEffect, useMemo, useState } from "react";
48
+import { ValidateMemoization } from "shared-runtime";
49
+
50
+let pretendConst = 0;
51
+
52
+function unsafeResetConst() {
53
+ pretendConst = 0;
54
+}
55
+
56
+function unsafeUpdateConst() {
57
+ pretendConst += 1;
58
+}
59
+
60
+function Component() {
61
+ const $ = _c(3);
62
+ let t0;
63
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
64
+ t0 = () => {
65
+ unsafeResetConst();
66
+ };
67
+ $[0] = t0;
68
+ } else {
69
+ t0 = $[0];
70
+ }
71
+ useState(t0);
72
+
73
+ unsafeUpdateConst();
74
+ let t1;
75
+ let t2;
76
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
77
+ t2 = [{ pretendConst }];
78
+ $[1] = t2;
79
+ } else {
80
+ t2 = $[1];
81
+ }
82
+ t1 = t2;
83
+ const value = t1;
84
+ let t3;
85
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
86
+ t3 = <ValidateMemoization inputs={[]} output={value} />;
87
+ $[2] = t3;
88
+ } else {
89
+ t3 = $[2];
90
+ }
91
+ return t3;
92
+}
93
+
94
+export const FIXTURE_ENTRYPOINT = {
95
+ fn: Component,
96
+ params: [{}],
97
+ sequentialRenders: [{}, {}],
98
+};
99
+
100
+```
101
+
102
+### Eval output
103
+(kind: ok) <div>{"inputs":[],"output":[{"pretendConst":1}]}</div>
104
+<div>{"inputs":[],"output":[{"pretendConst":1}]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js
new
+35
@@ -0,0 +1,35 @@
1
+// @compilationMode(infer)
2
+import { useEffect, useMemo, useState } from "react";
3
+import { ValidateMemoization } from "shared-runtime";
4
+
5
+let pretendConst = 0;
6
+
7
+function unsafeResetConst() {
8
+ pretendConst = 0;
9
+}
10
+
11
+function unsafeUpdateConst() {
12
+ pretendConst += 1;
13
+}
14
+
15
+function Component() {
16
+ useState(() => {
17
+ // unsafe: reset the constant when first rendering the instance
18
+ unsafeResetConst();
19
+ });
20
+ // UNSAFE! changing a module variable that is read by a component is normally
21
+ // unsafe, but in this case we're simulating a fast refresh between each render
22
+ unsafeUpdateConst();
23
+
24
+ // In production mode (no @enableResetCacheOnSourceFileChanges) memo caches are not
25
+ // reset unless the deps change
26
+ const value = useMemo(() => [{ pretendConst }], []);
27
+
28
+ return <ValidateMemoization inputs={[]} output={value} />;
29
+}
30
+
31
+export const FIXTURE_ENTRYPOINT = {
32
+ fn: Component,
33
+ params: [{}],
34
+ sequentialRenders: [{}, {}],
35
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md
new
+112
@@ -0,0 +1,112 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @compilationMode(infer) @enableResetCacheOnSourceFileChanges
6
+import { useEffect, useMemo, useState } from "react";
7
+import { ValidateMemoization } from "shared-runtime";
8
+
9
+let pretendConst = 0;
10
+
11
+function unsafeResetConst() {
12
+ pretendConst = 0;
13
+}
14
+
15
+function unsafeUpdateConst() {
16
+ pretendConst += 1;
17
+}
18
+
19
+function Component() {
20
+ useState(() => {
21
+ // unsafe: reset the constant when first rendering the instance
22
+ unsafeResetConst();
23
+ });
24
+ // UNSAFE! changing a module variable that is read by a component is normally
25
+ // unsafe, but in this case we're simulating a fast refresh between each render
26
+ unsafeUpdateConst();
27
+
28
+ // TODO: In fast refresh mode (@enableResetCacheOnSourceFileChanges) Forget should
29
+ // reset on changes to globals that impact the component/hook, effectively memoizing
30
+ // as if value was reactive. However, we don't want to actually treat globals as
31
+ // reactive (though that would be trivial) since it could change compilation too much
32
+ // btw dev and prod. Instead, we should reset the cache via a secondary mechanism.
33
+ const value = useMemo(() => [{ pretendConst }], [pretendConst]);
34
+
35
+ return <ValidateMemoization inputs={[pretendConst]} output={value} />;
36
+}
37
+
38
+export const FIXTURE_ENTRYPOINT = {
39
+ fn: Component,
40
+ params: [{}],
41
+ sequentialRenders: [{}, {}],
42
+};
43
+
44
+```
45
+
46
+## Code
47
+
48
+```javascript
49
+import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) @enableResetCacheOnSourceFileChanges
50
+import { useEffect, useMemo, useState } from "react";
51
+import { ValidateMemoization } from "shared-runtime";
52
+
53
+let pretendConst = 0;
54
+
55
+function unsafeResetConst() {
56
+ pretendConst = 0;
57
+}
58
+
59
+function unsafeUpdateConst() {
60
+ pretendConst += 1;
61
+}
62
+
63
+function Component() {
64
+ const $ = _c(4);
65
+ if (
66
+ $[0] !== "4bf230b116dd95f382060ad17350e116395e41ed757e51fd074ea0b4ed281272"
67
+ ) {
68
+ for (let $i = 0; $i < 4; $i += 1) {
69
+ $[$i] = Symbol.for("react.memo_cache_sentinel");
70
+ }
71
+ $[0] = "4bf230b116dd95f382060ad17350e116395e41ed757e51fd074ea0b4ed281272";
72
+ }
73
+ let t0;
74
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
75
+ t0 = () => {
76
+ unsafeResetConst();
77
+ };
78
+ $[1] = t0;
79
+ } else {
80
+ t0 = $[1];
81
+ }
82
+ useState(t0);
83
+
84
+ unsafeUpdateConst();
85
+ let t1;
86
+ let t2;
87
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
88
+ t2 = [{ pretendConst }];
89
+ $[2] = t2;
90
+ } else {
91
+ t2 = $[2];
92
+ }
93
+ t1 = t2;
94
+ const value = t1;
95
+ let t3;
96
+ if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
97
+ t3 = <ValidateMemoization inputs={[pretendConst]} output={value} />;
98
+ $[3] = t3;
99
+ } else {
100
+ t3 = $[3];
101
+ }
102
+ return t3;
103
+}
104
+
105
+export const FIXTURE_ENTRYPOINT = {
106
+ fn: Component,
107
+ params: [{}],
108
+ sequentialRenders: [{}, {}],
109
+};
110
+
111
+```
112
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js
new
+38
@@ -0,0 +1,38 @@
1
+// @compilationMode(infer) @enableResetCacheOnSourceFileChanges
2
+import { useEffect, useMemo, useState } from "react";
3
+import { ValidateMemoization } from "shared-runtime";
4
+
5
+let pretendConst = 0;
6
+
7
+function unsafeResetConst() {
8
+ pretendConst = 0;
9
+}
10
+
11
+function unsafeUpdateConst() {
12
+ pretendConst += 1;
13
+}
14
+
15
+function Component() {
16
+ useState(() => {
17
+ // unsafe: reset the constant when first rendering the instance
18
+ unsafeResetConst();
19
+ });
20
+ // UNSAFE! changing a module variable that is read by a component is normally
21
+ // unsafe, but in this case we're simulating a fast refresh between each render
22
+ unsafeUpdateConst();
23
+
24
+ // TODO: In fast refresh mode (@enableResetCacheOnSourceFileChanges) Forget should
25
+ // reset on changes to globals that impact the component/hook, effectively memoizing
26
+ // as if value was reactive. However, we don't want to actually treat globals as
27
+ // reactive (though that would be trivial) since it could change compilation too much
28
+ // btw dev and prod. Instead, we should reset the cache via a secondary mechanism.
29
+ const value = useMemo(() => [{ pretendConst }], [pretendConst]);
30
+
31
+ return <ValidateMemoization inputs={[pretendConst]} output={value} />;
32
+}
33
+
34
+export const FIXTURE_ENTRYPOINT = {
35
+ fn: Component,
36
+ params: [{}],
37
+ sequentialRenders: [{}, {}],
38
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md
renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.js
renamed
compiler/packages/snap/src/SproutTodoFilter.ts
+2
@@ -493,6 +493,8 @@ const skipFilter = new Set([
493
494
// 'react-compiler-runtime' not yet supported
495
"flag-enable-emit-hook-guards",
496
+
497
+ "fast-refresh-refresh-on-const-changes-dev",
498
]);
499
500
export default skipFilter;