@samitouri / QOS-React-2 / commits / 90e189ae82

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 -}