[compiler] Support default imports for autodep config (#31657)
## Summary Allows us to add deps for things like `import useWrapperEffect from 'useWrapperEffect'` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/31657). * __->__ #31657 * #31652
Jordan Brown committed
Dec 3, 2024 at 07:42 UTC
1b1283ade72dfdfa8981b08cf4cc9669bbf905f0
7 files changed
+67
-4
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+9
@@ -660,6 +660,13 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = {
660
},
661
numRequiredArgs: 2,
662
},
663
+ {
664
+ function: {
665
+ source: 'useEffectWrapper',
666
+ importSpecifierName: 'default',
667
+ },
668
+ numRequiredArgs: 1,
669
+ },
670
],
671
};
672
@@ -1147,3 +1154,5 @@ export function tryParseExternalFunction(
1154
suggestions: null,
1155
});
1156
}
1157
+
1158
+export const DEFAULT_EXPORT = 'default';
compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts
+12
@@ -16,6 +16,7 @@ import {
16
Place,
17
ReactiveScopeDependencies,
18
} from '../HIR';
19
+import {DEFAULT_EXPORT} from '../HIR/Environment';
20
import {
21
createTemporaryPlace,
22
fixScopeAndIdentifierRanges,
@@ -97,6 +98,17 @@ export function inferEffectDependencies(fn: HIRFunction): void {
98
autodepFnLoads.set(lvalue.identifier.id, numRequiredArgs);
99
}
100
}
101
+ } else if (
102
+ value.kind === 'LoadGlobal' &&
103
+ value.binding.kind === 'ImportDefault'
104
+ ) {
105
+ const moduleTargets = autodepFnConfigs.get(value.binding.module);
106
+ if (moduleTargets != null) {
107
+ const numRequiredArgs = moduleTargets.get(DEFAULT_EXPORT);
108
+ if (numRequiredArgs != null) {
109
+ autodepFnLoads.set(lvalue.identifier.id, numRequiredArgs);
110
+ }
111
+ }
112
} else if (
113
/*
114
* TODO: Handle method calls
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md
+18
-1
@@ -4,6 +4,7 @@
4
```javascript
5
// @inferEffectDependencies
6
import {useEffect, useRef} from 'react';
7
+import useEffectWrapper from 'useEffectWrapper';
8
9
const moduleNonReactive = 0;
10
@@ -39,6 +40,10 @@ function Component({foo, bar}) {
40
41
// No inferred dep array, the argument is not a lambda
42
useEffect(f);
43
+
44
+ useEffectWrapper(() => {
45
+ console.log(foo);
46
+ });
47
}
48
49
```
@@ -48,11 +53,12 @@ function Component({foo, bar}) {
53
```javascript
54
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
55
import { useEffect, useRef } from "react";
56
+import useEffectWrapper from "useEffectWrapper";
57
58
const moduleNonReactive = 0;
59
60
function Component(t0) {
55
- const $ = _c(12);
61
+ const $ = _c(14);
62
const { foo, bar } = t0;
63
64
const ref = useRef(0);
@@ -125,6 +131,17 @@ function Component(t0) {
131
const f = t5;
132
133
useEffect(f);
134
+ let t6;
135
+ if ($[12] !== foo) {
136
+ t6 = () => {
137
+ console.log(foo);
138
+ };
139
+ $[12] = foo;
140
+ $[13] = t6;
141
+ } else {
142
+ t6 = $[13];
143
+ }
144
+ useEffectWrapper(t6, [foo]);
145
}
146
147
```
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js
+5
@@ -1,5 +1,6 @@
1
// @inferEffectDependencies
2
import {useEffect, useRef} from 'react';
3
+import useEffectWrapper from 'useEffectWrapper';
4
5
const moduleNonReactive = 0;
6
@@ -35,4 +36,8 @@ function Component({foo, bar}) {
36
37
// No inferred dep array, the argument is not a lambda
38
useEffect(f);
39
+
40
+ useEffectWrapper(() => {
41
+ console.log(foo);
42
+ });
43
}
compiler/packages/snap/src/compiler.ts
+2
@@ -297,6 +297,8 @@ function getEvaluatorPresets(
297
arg.value = './shared-runtime';
298
} else if (arg.value === 'ReactForgetFeatureFlag') {
299
arg.value = './ReactForgetFeatureFlag';
300
+ } else if (arg.value === 'useEffectWrapper') {
301
+ arg.value = './useEffectWrapper';
302
}
303
}
304
}
compiler/packages/snap/src/sprout/useEffectWrapper.ts
new
+18
@@ -0,0 +1,18 @@
1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+/* This file is used to test the effect auto-deps configuration, which
9
+ * allows you to specify functions that should have dependencies added to
10
+ * callsites.
11
+ */
12
+import {useEffect} from 'react';
13
+
14
+export default function useEffectWrapper(f: () => void | (() => void)): void {
15
+ useEffect(() => {
16
+ f();
17
+ }, [f]);
18
+}
compiler/packages/snap/src/types.d.ts
+3
-3
@@ -6,14 +6,14 @@
6
*/
7
8
// v0.17.1
9
-declare module "hermes-parser" {
9
+declare module 'hermes-parser' {
10
type HermesParserOptions = {
11
allowReturnOutsideFunction?: boolean;
12
babel?: boolean;
13
- flow?: "all" | "detect";
13
+ flow?: 'all' | 'detect';
14
enableExperimentalComponentSyntax?: boolean;
15
sourceFilename?: string;
16
- sourceType?: "module" | "script" | "unambiguous";
16
+ sourceType?: 'module' | 'script' | 'unambiguous';
17
tokens?: boolean;
18
};
19
export function parse(code: string, options: Partial<HermesParserOptions>);