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)
This commit is contained in:
Jan Kassens
2022-10-17 13:08:34 -04:00
parent 8f42eda432
commit 90e189ae82
8 changed files with 156 additions and 53 deletions
+86
View File
@@ -0,0 +1,86 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import * as React from "react";
const {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
ReactCurrentDispatcher,
},
useRef,
} = React;
export const $empty = Symbol.for("react.usememocache_sentinel");
export function unstable_useMemoCache(i) {
"use no forget";
const $ = new Array(i).fill($empty);
return useRef($).current;
}
export function $read(memoCache, index) {
const value = memoCache[index];
if (value === $empty) {
throw new Error("useMemoCache: read before write");
}
return value;
}
const LazyGuardDispatcher = {};
[
"readContext",
"useCallback",
"useContext",
"useEffect",
"useImperativeHandle",
"useInsertionEffect",
"useLayoutEffect",
"useMemo",
"useReducer",
"useRef",
"useState",
"useDebugValue",
"useDeferredValue",
"useTransition",
"useMutableSource",
"useSyncExternalStore",
"useId",
"unstable_isNewReconciler",
"getCacheSignal",
"getCacheForType",
"useCacheRefresh",
].forEach((name) => {
LazyGuardDispatcher[name] = () => {
throw new Error(`Cannot call ${name} within ReactForget lazy block.`);
};
});
let originalDispatcher = null;
export function $startLazy() {
if (originalDispatcher !== null) {
throw new Error("unexpected startLazy with dispatcher set");
}
originalDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = LazyGuardDispatcher;
}
export function $endLazy() {
if (originalDispatcher === null) {
throw new Error("unexpected endLazy with dispatcher not set");
}
ReactCurrentDispatcher.current = originalDispatcher;
originalDispatcher = null;
}
export function $reset($) {
$.fill($empty);
}
export function $makeReadOnly() {
throw new Error("TODO: implement $makeReadOnly in react-forget-runtime");
}
@@ -0,0 +1,10 @@
{
"name": "react-forget-runtime",
"version": "0.0.1",
"description": "Runtime for React Forget",
"main": "index.js",
"license": "MIT",
"files": [
"index.js"
]
}
+22 -1
View File
@@ -33,7 +33,28 @@ module.exports = (useForget) => {
},
"@babel/preset-react",
{
plugins: ["@babel/plugin-transform-modules-commonjs"],
plugins: [
[
function BabelPluginRewriteRequirePath(babel) {
return {
visitor: {
CallExpression(path) {
if (path.node.callee.name === "require") {
const arg = path.node.arguments[0];
if (arg.type === "StringLiteral") {
if (arg.value === "react-forget-runtime") {
arg.value =
"../../../packages/react-forget-runtime";
}
}
}
},
},
};
},
],
"@babel/plugin-transform-modules-commonjs",
],
},
],
targets: {
+9 -21
View File
@@ -100,13 +100,13 @@ class MemoCache {
}
/**
* `$[entry] == $._`
* `$[entry] == $empty`
*/
isEmpty(entry: LIR.MemoCache.Entry): t.BinaryExpression {
return t.binaryExpression(
"===",
this.#cacheMember(entry.index),
t.memberExpression(this.id, t.identifier("_"))
t.identifier("$empty")
);
}
@@ -122,10 +122,10 @@ class MemoCache {
#readIndex(index: number): t.Expression {
if (this.#guardReads) {
return t.callExpression(
t.memberExpression(t.identifier("useMemoCache"), t.identifier("read")),
[this.id, t.numericLiteral(index)]
);
return t.callExpression(t.identifier("$read"), [
this.id,
t.numericLiteral(index),
]);
} else {
return this.#cacheMember(index);
}
@@ -193,7 +193,7 @@ export class Func {
if (this.context.opts.flags.addFreeze === true) {
this.code.push(
t.expressionStatement(
t.callExpression(t.identifier("useMemoCache.makeReadOnly"), [
t.callExpression(t.identifier("$makeReadOnly"), [
val.binding.identifier,
])
)
@@ -382,22 +382,10 @@ export class Func {
if (this.context.opts.flags.guardHooks) {
const startLazyCall = t.expressionStatement(
t.callExpression(
t.memberExpression(
t.identifier("useMemoCache"),
t.identifier("startLazy")
),
[]
)
t.callExpression(t.identifier("$startLazy"), [])
);
const endLazyCall = t.expressionStatement(
t.callExpression(
t.memberExpression(
t.identifier("useMemoCache"),
t.identifier("endLazy")
),
[]
)
t.callExpression(t.identifier("$endLazy"), [])
);
evalBody = [
t.tryStatement(
+24
View File
@@ -42,6 +42,30 @@ export function run(lirProg: LIR.Prog, context: CompilerContext) {
/*source*/ t.stringLiteral("react")
)
);
const utils = ["$empty"];
if (context.opts.flags.guardHooks) {
utils.push("$startLazy", "$endLazy");
}
if (context.opts.flags.guardReads) {
utils.push("$read");
}
if (context.opts.flags.addFreeze) {
utils.push("$makeReadOnly");
}
if (context.opts.flags.guardThrows) {
utils.push("$reset");
}
if (utils.length > 0) {
prog.unshiftContainer(
"body",
t.importDeclaration(
utils.map((name) =>
t.importSpecifier(t.identifier(name), t.identifier(name))
),
t.stringLiteral("react-forget-runtime")
)
);
}
}
for (const [irFunc, lirFunc] of lirProg.funcs) {
@@ -5,12 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
import { useMemoCacheStub } from "../test-utils/useMemoCacheStub";
import * as React from "react";
import { render } from "@testing-library/react";
import { expectLogsAndClear, log } from "./expectLogs";
React.unstable_useMemoCache = useMemoCacheStub;
import { unstable_useMemoCache } from "react-forget-runtime";
React.unstable_useMemoCache = unstable_useMemoCache;
function Hello({ name }) {
"use forget";
@@ -5,11 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import { render } from "@testing-library/react";
import * as React from "react";
import { useMemoCacheStub } from "../test-utils/useMemoCacheStub";
import { render } from "@testing-library/react";
React.unstable_useMemoCache = useMemoCacheStub;
import { unstable_useMemoCache } from "react-forget-runtime";
React.unstable_useMemoCache = unstable_useMemoCache;
function Button({ label }) {
const theme = useTheme();
@@ -1,26 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { useRef } from "react";
const REACT_MEMOCACHE_SENTINEL = Symbol.for("react.memocache_sentinel");
/**
* Stub for `React.useMemoCache` used in E2E tests until we have a React
* version that supports it.
*
* @param i Cache size
* @returns Memo cache
*/
export function useMemoCacheStub(i: number) {
"use no forget";
const $ = new Array(i).fill(REACT_MEMOCACHE_SENTINEL);
// @ts-ignore
$._ = REACT_MEMOCACHE_SENTINEL;
const ref = useRef($);
return ref.current;
}