Enable hook syntax support
Updates the compiler to understand Flow hook syntax. Like component syntax, in infer mode hooks are compiled by default unless opted out. Looking ahead, i can imagine splitting up our compilation modes as follows: * Annotations: opt-in explicitly * Declarations: annotations + component/hook declarations * Infer: annotations, component/hook declarations, + component/hook-like functions This also suggest an alternative annotation strategy: "use react" (or "use component" / "use hook") as a general way to tell the compiler that a function is intended for React. Then opting out of memoization could do "use react(nomemo)".
Joe Savona committed
Feb 17, 2024 at 17:31 UTC
ca3d16c5eff02672fb818639716f7dd7c3f961c9
8 files changed
+75
-14
compiler/packages/babel-plugin-react-forget/package.json
+1
-1
@@ -55,7 +55,7 @@
55
"babel-plugin-syntax-hermes-parser": "^0.15.1",
56
"eslint": "8.27.0",
57
"glob": "^7.1.6",
58
- "hermes-parser": "^0.18.2",
58
+ "hermes-parser": "^0.19.1",
59
"jest": "^29.0.3",
60
"jest-environment-jsdom": "^29.0.3",
61
"prettier": "2.8.8",
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts
+4
-2
@@ -19,6 +19,7 @@ import {
19
} from "../HIR/Environment";
20
import { CodegenFunction } from "../ReactiveScopes";
21
import { isComponentDeclaration } from "../Utils/ComponentDeclaration";
22
+import { isHookDeclaration } from "../Utils/HookDeclaration";
23
import { assertExhaustive } from "../Utils/utils";
24
import { insertGatedFunctionDeclaration } from "./Gating";
25
import { addImportsToProgram, updateUseMemoCacheImport } from "./Imports";
@@ -408,8 +409,9 @@ export function shouldVisitNode(fn: BabelFn, pass: CompilerPass): boolean {
409
case "infer": {
410
const hookPattern = pass.opts.environment?.hookPattern ?? null;
411
return (
411
- // Component declarations are known components
412
- (fn.isFunctionDeclaration() && isComponentDeclaration(fn.node)) ||
412
+ // Component and hook declarations are known components/hooks
413
+ (fn.isFunctionDeclaration() &&
414
+ (isComponentDeclaration(fn.node) || isHookDeclaration(fn.node))) ||
415
// Otherwise check if this is a component or hook-like function
416
isComponentOrHookLike(fn, hookPattern)
417
);
compiler/packages/babel-plugin-react-forget/src/Utils/HookDeclaration.ts
new
+24
@@ -0,0 +1,24 @@
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
+import * as t from "@babel/types";
9
+
10
+export type HookDeclaration = t.FunctionDeclaration & {
11
+ __hookDeclaration: boolean;
12
+};
13
+
14
+export function isHookDeclaration(
15
+ node: t.FunctionDeclaration
16
+): node is HookDeclaration {
17
+ return Object.prototype.hasOwnProperty.call(node, "__hookDeclaration");
18
+}
19
+
20
+export function parseHookDeclaration(
21
+ node: t.FunctionDeclaration
22
+): HookDeclaration | null {
23
+ return isHookDeclaration(node) ? node : null;
24
+}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.expect.md
new
+30
@@ -0,0 +1,30 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @flow @compilationMode(infer)
6
+export default hook useFoo(bar: number) {
7
+ return [bar];
8
+}
9
+
10
+```
11
+
12
+## Code
13
+
14
+```javascript
15
+import { unstable_useMemoCache as useMemoCache } from "react";
16
+function useFoo(bar) {
17
+ const $ = useMemoCache(2);
18
+ let t0;
19
+ if ($[0] !== bar) {
20
+ t0 = [bar];
21
+ $[0] = bar;
22
+ $[1] = t0;
23
+ } else {
24
+ t0 = $[1];
25
+ }
26
+ return t0;
27
+}
28
+
29
+```
30
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.js
new
+4
@@ -0,0 +1,4 @@
1
+// @flow @compilationMode(infer)
2
+export default hook useFoo(bar: number) {
3
+ return [bar];
4
+}
compiler/packages/eslint-plugin-react-compiler/package.json
+1
-1
@@ -10,7 +10,7 @@
10
"@babel/core": "^7.19.1",
11
"@babel/plugin-proposal-private-methods": "^7.18.6",
12
"babel-plugin-react-forget": "*",
13
- "hermes-parser": "^0.17.1"
13
+ "hermes-parser": "^0.19.1"
14
},
15
"devDependencies": {
16
"@babel/preset-env": "^7.22.4",
compiler/packages/sprout/src/SproutTodoFilter.ts
+1
@@ -420,6 +420,7 @@ const skipFilter = new Set([
420
421
// TODO: we should be able to support these
422
"component-declaration-basic.flow",
423
+ "hook-declaration-basic.flow",
424
"nested-function-with-param-as-captured-dep",
425
"readonly-object-method-calls",
426
"readonly-object-method-calls-mutable-lambda",
compiler/yarn.lock
+10
-10
@@ -6410,10 +6410,10 @@ hermes-estree@0.17.1:
6410
resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.17.1.tgz#902806a900c185720424ffcf958027821d23c051"
6411
integrity sha512-EdUJms+eRE40OQxysFlPr1mPpvUbbMi7uDAKlScBw8o3tQY22BZ5yx56OYyp1bVaBm+7Cjc3NQz24sJEFXkPxg==
6412
6413
-hermes-estree@0.18.2:
6414
- version "0.18.2"
6415
- resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.18.2.tgz#fd450fa1659cf074ceaa2ddeeb21674f3b2342f3"
6416
- integrity sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==
6413
+hermes-estree@0.19.1:
6414
+ version "0.19.1"
6415
+ resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.19.1.tgz#d5924f5fac2bf0532547ae9f506d6db8f3c96392"
6416
+ integrity sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g==
6417
6418
hermes-parser@0.14.0:
6419
version "0.14.0"
@@ -6429,19 +6429,19 @@ hermes-parser@0.15.1:
6429
dependencies:
6430
hermes-estree "0.15.1"
6431
6432
-hermes-parser@0.17.1, hermes-parser@^0.17.1:
6432
+hermes-parser@0.17.1:
6433
version "0.17.1"
6434
resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.17.1.tgz#8b5cbaff235fed28487812ad718f9c7182d0db0f"
6435
integrity sha512-yErtFLMEL6490fFJPurNn23OI2ciGAtaUfKUg9VPdcde9CmItCjOVQkJt1Xzawv5kuRzeIx0RE2E2Q9TbIgdzA==
6436
dependencies:
6437
hermes-estree "0.17.1"
6438
6439
-hermes-parser@^0.18.2:
6440
- version "0.18.2"
6441
- resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.18.2.tgz#50f15e2fcd559a48c68cd7af259d4292298bd14d"
6442
- integrity sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==
6439
+hermes-parser@^0.19.1:
6440
+ version "0.19.1"
6441
+ resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.19.1.tgz#1044348097165b7c93dc198a80b04ed5130d6b1a"
6442
+ integrity sha512-Vp+bXzxYJWrpEuJ/vXxUsLnt0+y4q9zyi4zUlkLqD8FKv4LjIfOvP69R/9Lty3dCyKh0E2BU7Eypqr63/rKT/A==
6443
dependencies:
6444
- hermes-estree "0.18.2"
6444
+ hermes-estree "0.19.1"
6445
6446
hmac-drbg@^1.0.1:
6447
version "1.0.1"