@samitouri / QOS-React-2 / commits / afb2c39ec3

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;