fix(react-compiler): optimize components declared with arrow function and implicit return and `compilationMode: 'infer'` (#31792)
fixes https://github.com/facebook/react/issues/31601 https://github.com/facebook/react/issues/31639 cc @josephsavona
Dimitri POSTOLOV committed
Mar 22, 2025 at 00:46 UTC
6b1a2c1d81630a5f385c5be0f758365b63d92eae
3 files changed
+71
-17
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts
+25
-17
@@ -1008,31 +1008,39 @@ function callsHooksOrCreatesJsx(
1008
return invokesHooks || createsJsx;
1009
}
1010
1011
+function isNonNode(node?: t.Expression | null): boolean {
1012
+ if (!node) {
1013
+ return true;
1014
+ }
1015
+ switch (node.type) {
1016
+ case 'ObjectExpression':
1017
+ case 'ArrowFunctionExpression':
1018
+ case 'FunctionExpression':
1019
+ case 'BigIntLiteral':
1020
+ case 'ClassExpression':
1021
+ case 'NewExpression': // technically `new Array()` is legit, but unlikely
1022
+ return true;
1023
+ }
1024
+ return false;
1025
+}
1026
+
1027
function returnsNonNode(
1028
node: NodePath<
1029
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
1030
>,
1031
): boolean {
1016
- let hasReturn = false;
1032
let returnsNonNode = false;
1033
+ if (
1034
+ // node.traverse#ArrowFunctionExpression isn't called for the root node
1035
+ node.type === 'ArrowFunctionExpression' &&
1036
+ node.node.body.type !== 'BlockStatement'
1037
+ ) {
1038
+ returnsNonNode = isNonNode(node.node.body);
1039
+ }
1040
1041
node.traverse({
1042
ReturnStatement(ret) {
1021
- hasReturn = true;
1022
- const argument = ret.node.argument;
1023
- if (argument == null) {
1024
- returnsNonNode = true;
1025
- } else {
1026
- switch (argument.type) {
1027
- case 'ObjectExpression':
1028
- case 'ArrowFunctionExpression':
1029
- case 'FunctionExpression':
1030
- case 'BigIntLiteral':
1031
- case 'ClassExpression':
1032
- case 'NewExpression': // technically `new Array()` is legit, but unlikely
1033
- returnsNonNode = true;
1034
- }
1035
- }
1043
+ returnsNonNode = isNonNode(ret.node.argument);
1044
},
1045
// Skip traversing all nested functions and their return statements
1046
ArrowFunctionExpression: skipNestedFunctions(node),
@@ -1041,7 +1049,7 @@ function returnsNonNode(
1049
ObjectMethod: node => node.skip(),
1050
});
1051
1044
- return !hasReturn || returnsNonNode;
1052
+ return returnsNonNode;
1053
}
1054
1055
/*
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.expect.md
new
+39
@@ -0,0 +1,39 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @compilationMode(infer)
6
+const Test = () => <div />;
7
+
8
+export const FIXTURE_ENTRYPOINT = {
9
+ fn: Test,
10
+ params: [{}],
11
+};
12
+
13
+```
14
+
15
+## Code
16
+
17
+```javascript
18
+import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer)
19
+const Test = () => {
20
+ const $ = _c(1);
21
+ let t0;
22
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
23
+ t0 = <div />;
24
+ $[0] = t0;
25
+ } else {
26
+ t0 = $[0];
27
+ }
28
+ return t0;
29
+};
30
+
31
+export const FIXTURE_ENTRYPOINT = {
32
+ fn: Test,
33
+ params: [{}],
34
+};
35
+
36
+```
37
+
38
+### Eval output
39
+(kind: ok) <div></div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.js
new
+7
@@ -0,0 +1,7 @@
1
+// @compilationMode(infer)
2
+const Test = () => <div />;
3
+
4
+export const FIXTURE_ENTRYPOINT = {
5
+ fn: Test,
6
+ params: [{}],
7
+};