mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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:
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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: {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user