Add react-forget-runtime
This is a new module that holds: - the `useMemoCache` stub (hopefully to be deleted next week) - various helpers that can be imported by the compiler, e.g. the dispatcher guard `$startLazy` - skipped the implementation of `makeReadOnly` for now as there's already multiple copies and I wanted to avoid typescript in this file for now to make the build easier (i.e. no build)
Jan Kassens committed
Oct 17, 2022 at 13:08 UTC
90e189ae82022d5183f1711d44cc2d7ee02af1ab
8 files changed
+156
-53
compiler/forget/packages/react-forget-runtime/index.js
new
+86
@@ -0,0 +1,86 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its 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 React from "react";
9
+
10
+const {
11
+ __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
12
+ ReactCurrentDispatcher,
13
+ },
14
+ useRef,
15
+} = React;
16
+
17
+export const $empty = Symbol.for("react.usememocache_sentinel");
18
+
19
+export function unstable_useMemoCache(i) {
20
+ "use no forget";
21
+ const $ = new Array(i).fill($empty);
22
+ return useRef($).current;
23
+}
24
+
25
+export function $read(memoCache, index) {
26
+ const value = memoCache[index];
27
+ if (value === $empty) {
28
+ throw new Error("useMemoCache: read before write");
29
+ }
30
+ return value;
31
+}
32
+
33
+const LazyGuardDispatcher = {};
34
+[
35
+ "readContext",
36
+ "useCallback",
37
+ "useContext",
38
+ "useEffect",
39
+ "useImperativeHandle",
40
+ "useInsertionEffect",
41
+ "useLayoutEffect",
42
+ "useMemo",
43
+ "useReducer",
44
+ "useRef",
45
+ "useState",
46
+ "useDebugValue",
47
+ "useDeferredValue",
48
+ "useTransition",
49
+ "useMutableSource",
50
+ "useSyncExternalStore",
51
+ "useId",
52
+ "unstable_isNewReconciler",
53
+ "getCacheSignal",
54
+ "getCacheForType",
55
+ "useCacheRefresh",
56
+].forEach((name) => {
57
+ LazyGuardDispatcher[name] = () => {
58
+ throw new Error(`Cannot call ${name} within ReactForget lazy block.`);
59
+ };
60
+});
61
+
62
+let originalDispatcher = null;
63
+
64
+export function $startLazy() {
65
+ if (originalDispatcher !== null) {
66
+ throw new Error("unexpected startLazy with dispatcher set");
67
+ }
68
+ originalDispatcher = ReactCurrentDispatcher.current;
69
+ ReactCurrentDispatcher.current = LazyGuardDispatcher;
70
+}
71
+
72
+export function $endLazy() {
73
+ if (originalDispatcher === null) {
74
+ throw new Error("unexpected endLazy with dispatcher not set");
75
+ }
76
+ ReactCurrentDispatcher.current = originalDispatcher;
77
+ originalDispatcher = null;
78
+}
79
+
80
+export function $reset($) {
81
+ $.fill($empty);
82
+}
83
+
84
+export function $makeReadOnly() {
85
+ throw new Error("TODO: implement $makeReadOnly in react-forget-runtime");
86
+}
compiler/forget/packages/react-forget-runtime/package.json
new
+10
@@ -0,0 +1,10 @@
1
+{
2
+ "name": "react-forget-runtime",
3
+ "version": "0.0.1",
4
+ "description": "Runtime for React Forget",
5
+ "main": "index.js",
6
+ "license": "MIT",
7
+ "files": [
8
+ "index.js"
9
+ ]
10
+}
compiler/forget/scripts/jest/makeTransform.js
+22
-1
@@ -33,7 +33,28 @@ module.exports = (useForget) => {
33
},
34
"@babel/preset-react",
35
{
36
- plugins: ["@babel/plugin-transform-modules-commonjs"],
36
+ plugins: [
37
+ [
38
+ function BabelPluginRewriteRequirePath(babel) {
39
+ return {
40
+ visitor: {
41
+ CallExpression(path) {
42
+ if (path.node.callee.name === "require") {
43
+ const arg = path.node.arguments[0];
44
+ if (arg.type === "StringLiteral") {
45
+ if (arg.value === "react-forget-runtime") {
46
+ arg.value =
47
+ "../../../packages/react-forget-runtime";
48
+ }
49
+ }
50
+ }
51
+ },
52
+ },
53
+ };
54
+ },
55
+ ],
56
+ "@babel/plugin-transform-modules-commonjs",
57
+ ],
58
},
59
],
60
targets: {
compiler/forget/src/BackEnd/JS.ts
+9
-21
@@ -100,13 +100,13 @@ class MemoCache {
100
}
101
102
/**
103
- * `$[entry] == $._`
103
+ * `$[entry] == $empty`
104
*/
105
isEmpty(entry: LIR.MemoCache.Entry): t.BinaryExpression {
106
return t.binaryExpression(
107
"===",
108
this.#cacheMember(entry.index),
109
- t.memberExpression(this.id, t.identifier("_"))
109
+ t.identifier("$empty")
110
);
111
}
112
@@ -122,10 +122,10 @@ class MemoCache {
122
123
#readIndex(index: number): t.Expression {
124
if (this.#guardReads) {
125
- return t.callExpression(
126
- t.memberExpression(t.identifier("useMemoCache"), t.identifier("read")),
127
- [this.id, t.numericLiteral(index)]
128
- );
125
+ return t.callExpression(t.identifier("$read"), [
126
+ this.id,
127
+ t.numericLiteral(index),
128
+ ]);
129
} else {
130
return this.#cacheMember(index);
131
}
@@ -193,7 +193,7 @@ export class Func {
193
if (this.context.opts.flags.addFreeze === true) {
194
this.code.push(
195
t.expressionStatement(
196
- t.callExpression(t.identifier("useMemoCache.makeReadOnly"), [
196
+ t.callExpression(t.identifier("$makeReadOnly"), [
197
val.binding.identifier,
198
])
199
)
@@ -382,22 +382,10 @@ export class Func {
382
383
if (this.context.opts.flags.guardHooks) {
384
const startLazyCall = t.expressionStatement(
385
- t.callExpression(
386
- t.memberExpression(
387
- t.identifier("useMemoCache"),
388
- t.identifier("startLazy")
389
- ),
390
- []
391
- )
385
+ t.callExpression(t.identifier("$startLazy"), [])
386
);
387
const endLazyCall = t.expressionStatement(
394
- t.callExpression(
395
- t.memberExpression(
396
- t.identifier("useMemoCache"),
397
- t.identifier("endLazy")
398
- ),
399
- []
400
- )
388
+ t.callExpression(t.identifier("$endLazy"), [])
389
);
390
evalBody = [
391
t.tryStatement(
compiler/forget/src/BackEnd/JSGen.ts
+24
@@ -42,6 +42,30 @@ export function run(lirProg: LIR.Prog, context: CompilerContext) {
42
/*source*/ t.stringLiteral("react")
43
)
44
);
45
+ const utils = ["$empty"];
46
+ if (context.opts.flags.guardHooks) {
47
+ utils.push("$startLazy", "$endLazy");
48
+ }
49
+ if (context.opts.flags.guardReads) {
50
+ utils.push("$read");
51
+ }
52
+ if (context.opts.flags.addFreeze) {
53
+ utils.push("$makeReadOnly");
54
+ }
55
+ if (context.opts.flags.guardThrows) {
56
+ utils.push("$reset");
57
+ }
58
+ if (utils.length > 0) {
59
+ prog.unshiftContainer(
60
+ "body",
61
+ t.importDeclaration(
62
+ utils.map((name) =>
63
+ t.importSpecifier(t.identifier(name), t.identifier(name))
64
+ ),
65
+ t.stringLiteral("react-forget-runtime")
66
+ )
67
+ );
68
+ }
69
}
70
71
for (const [irFunc, lirFunc] of lirProg.funcs) {
compiler/forget/src/__tests__/e2e/hello.e2e.js
+2
-2
@@ -5,12 +5,12 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import { useMemoCacheStub } from "../test-utils/useMemoCacheStub";
8
import * as React from "react";
9
import { render } from "@testing-library/react";
10
import { expectLogsAndClear, log } from "./expectLogs";
11
13
-React.unstable_useMemoCache = useMemoCacheStub;
12
+import { unstable_useMemoCache } from "react-forget-runtime";
13
+React.unstable_useMemoCache = unstable_useMemoCache;
14
15
function Hello({ name }) {
16
"use forget";
compiler/forget/src/__tests__/e2e/update-button.e2e.js
+3
-3
@@ -5,11 +5,11 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import { render } from "@testing-library/react";
8
import * as React from "react";
10
-import { useMemoCacheStub } from "../test-utils/useMemoCacheStub";
9
+import { render } from "@testing-library/react";
10
12
-React.unstable_useMemoCache = useMemoCacheStub;
11
+import { unstable_useMemoCache } from "react-forget-runtime";
12
+React.unstable_useMemoCache = unstable_useMemoCache;
13
14
function Button({ label }) {
15
const theme = useTheme();
compiler/forget/src/__tests__/test-utils/useMemoCacheStub.ts
deleted
-26
@@ -1,26 +0,0 @@
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 { useRef } from "react";
9
-
10
-const REACT_MEMOCACHE_SENTINEL = Symbol.for("react.memocache_sentinel");
11
-
12
-/**
13
- * Stub for `React.useMemoCache` used in E2E tests until we have a React
14
- * version that supports it.
15
- *
16
- * @param i Cache size
17
- * @returns Memo cache
18
- */
19
-export function useMemoCacheStub(i: number) {
20
- "use no forget";
21
- const $ = new Array(i).fill(REACT_MEMOCACHE_SENTINEL);
22
- // @ts-ignore
23
- $._ = REACT_MEMOCACHE_SENTINEL;
24
- const ref = useRef($);
25
- return ref.current;
26
-}