diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts
index 7120e2b29589c..dafd7bc11f53c 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts
@@ -81,18 +81,21 @@ export function codegenFunction(
);
/**
- * Hot-module reloading reuses component instances at runtime even as the source of the component changes.
+ * Fast Refresh reuses component instances at runtime even as the source of the component changes.
* The generated code needs to prevent values from one version of the code being reused after a code cange.
* If HMR detection is enabled and we know the source code of the component, assign a cache slot to track
* the source hash, and later, emit code to check for source changes and reset the cache on source changes.
*/
- let hotModuleReloadState: { cacheIndex: number; hash: string } | null = null;
+ let fasthRefreshResetState: {
+ cacheIndex: number;
+ hash: string;
+ } | null = null;
if (
fn.env.config.enableResetCacheOnSourceFileChanges &&
fn.env.code !== null
) {
const hash = createHmac("sha256", fn.env.code).digest("hex");
- hotModuleReloadState = {
+ fasthRefreshResetState = {
cacheIndex: cx.nextCacheIndex,
hash,
};
@@ -131,7 +134,7 @@ export function codegenFunction(
),
])
);
- if (hotModuleReloadState !== null) {
+ if (fasthRefreshResetState !== null) {
// HMR detection is enabled, emit code to reset the memo cache on source changes
const index = cx.synthesizeName("$i");
preface.push(
@@ -140,10 +143,10 @@ export function codegenFunction(
"!==",
t.memberExpression(
t.identifier(cx.synthesizeName("$")),
- t.numericLiteral(hotModuleReloadState.cacheIndex),
+ t.numericLiteral(fasthRefreshResetState.cacheIndex),
true
),
- t.stringLiteral(hotModuleReloadState.hash)
+ t.stringLiteral(fasthRefreshResetState.hash)
),
t.blockStatement([
t.forStatement(
@@ -185,10 +188,10 @@ export function codegenFunction(
"=",
t.memberExpression(
t.identifier(cx.synthesizeName("$")),
- t.numericLiteral(hotModuleReloadState.cacheIndex),
+ t.numericLiteral(fasthRefreshResetState.cacheIndex),
true
),
- t.stringLiteral(hotModuleReloadState.hash)
+ t.stringLiteral(fasthRefreshResetState.hash)
)
),
])
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md
new file mode 100644
index 0000000000000..87084a14028c9
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md
@@ -0,0 +1,104 @@
+
+## Input
+
+```javascript
+// @compilationMode(infer)
+import { useEffect, useMemo, useState } from "react";
+import { ValidateMemoization } from "shared-runtime";
+
+let pretendConst = 0;
+
+function unsafeResetConst() {
+ pretendConst = 0;
+}
+
+function unsafeUpdateConst() {
+ pretendConst += 1;
+}
+
+function Component() {
+ useState(() => {
+ // unsafe: reset the constant when first rendering the instance
+ unsafeResetConst();
+ });
+ // UNSAFE! changing a module variable that is read by a component is normally
+ // unsafe, but in this case we're simulating a fast refresh between each render
+ unsafeUpdateConst();
+
+ // In production mode (no @enableResetCacheOnSourceFileChanges) memo caches are not
+ // reset unless the deps change
+ const value = useMemo(() => [{ pretendConst }], []);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{}],
+ sequentialRenders: [{}, {}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer)
+import { useEffect, useMemo, useState } from "react";
+import { ValidateMemoization } from "shared-runtime";
+
+let pretendConst = 0;
+
+function unsafeResetConst() {
+ pretendConst = 0;
+}
+
+function unsafeUpdateConst() {
+ pretendConst += 1;
+}
+
+function Component() {
+ const $ = _c(3);
+ let t0;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t0 = () => {
+ unsafeResetConst();
+ };
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ useState(t0);
+
+ unsafeUpdateConst();
+ let t1;
+ let t2;
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
+ t2 = [{ pretendConst }];
+ $[1] = t2;
+ } else {
+ t2 = $[1];
+ }
+ t1 = t2;
+ const value = t1;
+ let t3;
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
+ t3 = ;
+ $[2] = t3;
+ } else {
+ t3 = $[2];
+ }
+ return t3;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{}],
+ sequentialRenders: [{}, {}],
+};
+
+```
+
+### Eval output
+(kind: ok)
{"inputs":[],"output":[{"pretendConst":1}]}
+{"inputs":[],"output":[{"pretendConst":1}]}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js
new file mode 100644
index 0000000000000..6c4ed19e78cf0
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js
@@ -0,0 +1,35 @@
+// @compilationMode(infer)
+import { useEffect, useMemo, useState } from "react";
+import { ValidateMemoization } from "shared-runtime";
+
+let pretendConst = 0;
+
+function unsafeResetConst() {
+ pretendConst = 0;
+}
+
+function unsafeUpdateConst() {
+ pretendConst += 1;
+}
+
+function Component() {
+ useState(() => {
+ // unsafe: reset the constant when first rendering the instance
+ unsafeResetConst();
+ });
+ // UNSAFE! changing a module variable that is read by a component is normally
+ // unsafe, but in this case we're simulating a fast refresh between each render
+ unsafeUpdateConst();
+
+ // In production mode (no @enableResetCacheOnSourceFileChanges) memo caches are not
+ // reset unless the deps change
+ const value = useMemo(() => [{ pretendConst }], []);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{}],
+ sequentialRenders: [{}, {}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md
new file mode 100644
index 0000000000000..19317de47e2bd
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md
@@ -0,0 +1,112 @@
+
+## Input
+
+```javascript
+// @compilationMode(infer) @enableResetCacheOnSourceFileChanges
+import { useEffect, useMemo, useState } from "react";
+import { ValidateMemoization } from "shared-runtime";
+
+let pretendConst = 0;
+
+function unsafeResetConst() {
+ pretendConst = 0;
+}
+
+function unsafeUpdateConst() {
+ pretendConst += 1;
+}
+
+function Component() {
+ useState(() => {
+ // unsafe: reset the constant when first rendering the instance
+ unsafeResetConst();
+ });
+ // UNSAFE! changing a module variable that is read by a component is normally
+ // unsafe, but in this case we're simulating a fast refresh between each render
+ unsafeUpdateConst();
+
+ // TODO: In fast refresh mode (@enableResetCacheOnSourceFileChanges) Forget should
+ // reset on changes to globals that impact the component/hook, effectively memoizing
+ // as if value was reactive. However, we don't want to actually treat globals as
+ // reactive (though that would be trivial) since it could change compilation too much
+ // btw dev and prod. Instead, we should reset the cache via a secondary mechanism.
+ const value = useMemo(() => [{ pretendConst }], [pretendConst]);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{}],
+ sequentialRenders: [{}, {}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) @enableResetCacheOnSourceFileChanges
+import { useEffect, useMemo, useState } from "react";
+import { ValidateMemoization } from "shared-runtime";
+
+let pretendConst = 0;
+
+function unsafeResetConst() {
+ pretendConst = 0;
+}
+
+function unsafeUpdateConst() {
+ pretendConst += 1;
+}
+
+function Component() {
+ const $ = _c(4);
+ if (
+ $[0] !== "4bf230b116dd95f382060ad17350e116395e41ed757e51fd074ea0b4ed281272"
+ ) {
+ for (let $i = 0; $i < 4; $i += 1) {
+ $[$i] = Symbol.for("react.memo_cache_sentinel");
+ }
+ $[0] = "4bf230b116dd95f382060ad17350e116395e41ed757e51fd074ea0b4ed281272";
+ }
+ let t0;
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
+ t0 = () => {
+ unsafeResetConst();
+ };
+ $[1] = t0;
+ } else {
+ t0 = $[1];
+ }
+ useState(t0);
+
+ unsafeUpdateConst();
+ let t1;
+ let t2;
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
+ t2 = [{ pretendConst }];
+ $[2] = t2;
+ } else {
+ t2 = $[2];
+ }
+ t1 = t2;
+ const value = t1;
+ let t3;
+ if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
+ t3 = ;
+ $[3] = t3;
+ } else {
+ t3 = $[3];
+ }
+ return t3;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{}],
+ sequentialRenders: [{}, {}],
+};
+
+```
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js
new file mode 100644
index 0000000000000..413ff7d1f97fc
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js
@@ -0,0 +1,38 @@
+// @compilationMode(infer) @enableResetCacheOnSourceFileChanges
+import { useEffect, useMemo, useState } from "react";
+import { ValidateMemoization } from "shared-runtime";
+
+let pretendConst = 0;
+
+function unsafeResetConst() {
+ pretendConst = 0;
+}
+
+function unsafeUpdateConst() {
+ pretendConst += 1;
+}
+
+function Component() {
+ useState(() => {
+ // unsafe: reset the constant when first rendering the instance
+ unsafeResetConst();
+ });
+ // UNSAFE! changing a module variable that is read by a component is normally
+ // unsafe, but in this case we're simulating a fast refresh between each render
+ unsafeUpdateConst();
+
+ // TODO: In fast refresh mode (@enableResetCacheOnSourceFileChanges) Forget should
+ // reset on changes to globals that impact the component/hook, effectively memoizing
+ // as if value was reactive. However, we don't want to actually treat globals as
+ // reactive (though that would be trivial) since it could change compilation too much
+ // btw dev and prod. Instead, we should reset the cache via a secondary mechanism.
+ const value = useMemo(() => [{ pretendConst }], [pretendConst]);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{}],
+ sequentialRenders: [{}, {}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hot-module-reloading.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md
similarity index 100%
rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hot-module-reloading.expect.md
rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.expect.md
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hot-module-reloading.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.js
similarity index 100%
rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hot-module-reloading.js
rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-reloading.js
diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts
index d026cc294318f..1596f4b60adc5 100644
--- a/compiler/packages/snap/src/SproutTodoFilter.ts
+++ b/compiler/packages/snap/src/SproutTodoFilter.ts
@@ -493,6 +493,8 @@ const skipFilter = new Set([
// 'react-compiler-runtime' not yet supported
"flag-enable-emit-hook-guards",
+
+ "fast-refresh-refresh-on-const-changes-dev",
]);
export default skipFilter;