diff --git a/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Imports.ts index 9b2b47653c..46d111f742 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Imports.ts @@ -145,3 +145,21 @@ export function insertUseMemoCacheImportDeclaration( ) ); } + +export function insertUserspaceUseMemoCacheImportDeclaration( + program: NodePath, + moduleName: string +): void { + program.unshiftContainer( + "body", + t.importDeclaration( + [ + t.importSpecifier( + t.identifier("useMemoCache"), + t.identifier("unstable_useMemoCache") + ), + ], + t.stringLiteral(moduleName) + ) + ); +} diff --git a/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Options.ts index fb9a62a1c9..2f7c2649db 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Options.ts @@ -109,6 +109,17 @@ export type PluginOptions = { * ``` */ compilationMode: CompilationMode; + + /** + * If specified, Forget will import `useMemoCache` from this module instead of React. Use this if + * you are for whatever reason unable to use an experimental version of React. + * + * ``` + * // If specified: + * import {unstable_useMemoCache} from 'useMemoCache_DO_NOT_USE'; + * ``` + */ + useMemoCacheSource: string | null; }; export type CompilationMode = @@ -156,6 +167,7 @@ export const defaultOptions: PluginOptions = { gating: null, instrumentForget: null, noEmit: false, + useMemoCacheSource: null, } as const; export function parsePluginOptions(obj: unknown): PluginOptions { diff --git a/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts index 5806b76fa8..acd6b1cec8 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts @@ -21,10 +21,11 @@ import { addImportsToProgram, findExistingImports, insertUseMemoCacheImportDeclaration, + insertUserspaceUseMemoCacheImportDeclaration, updateExistingReactImportDeclaration, } from "./Imports"; import { addInstrumentForget } from "./Instrumentation"; -import { PluginOptions, parsePluginOptions } from "./Options"; +import { ExternalFunction, PluginOptions, parsePluginOptions } from "./Options"; import { compileFn } from "./Pipeline"; export type CompilerPass = { @@ -325,18 +326,25 @@ export function compileProgram( // `import {unstable_useMemoCache as useMemoCache} from 'react'` and rename // `React.unstable_useMemoCache(n)` to `useMemoCache(n)`; if (didInsertUseMemoCache) { - if (hasExistingReactImport) { - const didUpdateImport = updateExistingReactImportDeclaration(program); - if (didUpdateImport === false) { - throw new Error( - "Expected an ImportDeclaration of react in order to update ImportSpecifiers with useMemoCache" - ); + if (options.useMemoCacheSource === null) { + if (hasExistingReactImport) { + const didUpdateImport = updateExistingReactImportDeclaration(program); + if (didUpdateImport === false) { + throw new Error( + "Expected an ImportDeclaration of react in order to update ImportSpecifiers with useMemoCache" + ); + } + } else { + insertUseMemoCacheImportDeclaration(program); } - } else { - insertUseMemoCacheImportDeclaration(program); + } else if (typeof options.useMemoCacheSource === "string") { + insertUserspaceUseMemoCacheImportDeclaration( + program, + options.useMemoCacheSource + ); } } - const externalFunctions = []; + const externalFunctions: ExternalFunction[] = []; // TODO: check for duplicate import specifiers if (options.gating != null) { externalFunctions.push(options.gating); diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/userspace-use-memo-cache.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/userspace-use-memo-cache.expect.md new file mode 100644 index 0000000000..7ea5828444 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/userspace-use-memo-cache.expect.md @@ -0,0 +1,73 @@ + +## Input + +```javascript +// @useMemoCacheSource +function Component(props) { + const [x, setX] = useState(1); + let y; + if (props.cond) { + y = x * 2; + } + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [true], + isComponent: true, +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "shared-runtime"; // @useMemoCacheSource +function Component(props) { + const $ = useMemoCache(5); + const [x, setX] = useState(1); + let y; + if ($[0] !== props.cond || $[1] !== x) { + if (props.cond) { + y = x * 2; + } + $[0] = props.cond; + $[1] = x; + $[2] = y; + } else { + y = $[2]; + } + + const t0 = y; + let t1; + if ($[3] !== t0) { + t1 = ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [true], + isComponent: true, +}; diff --git a/compiler/packages/fixture-test-utils/src/compiler-utils.ts b/compiler/packages/fixture-test-utils/src/compiler-utils.ts index 295a690093..6a928076ff 100644 --- a/compiler/packages/fixture-test-utils/src/compiler-utils.ts +++ b/compiler/packages/fixture-test-utils/src/compiler-utils.ts @@ -24,6 +24,7 @@ export function transformFixtureInput( let instrumentForget = null; let enableEmitFreeze = null; let compilationMode: CompilationMode = "all"; + let useMemoCacheSource = null; if (firstLine.indexOf("@compilationMode(annotation)") !== -1) { assert( @@ -58,6 +59,9 @@ export function transformFixtureInput( importSpecifierName: "makeReadOnly", }; } + if (firstLine.includes("@useMemoCacheSource")) { + useMemoCacheSource = "shared-runtime"; + } const config = parseConfigPragmaFn(firstLine); const result = pluginFn( input, @@ -104,6 +108,7 @@ export function transformFixtureInput( instrumentForget, panicThreshold: "ALL_ERRORS", noEmit: false, + useMemoCacheSource, }, includeAst ); diff --git a/compiler/packages/sprout/src/shared-runtime.ts b/compiler/packages/sprout/src/shared-runtime.ts index e30e72e967..d74d46a661 100644 --- a/compiler/packages/sprout/src/shared-runtime.ts +++ b/compiler/packages/sprout/src/shared-runtime.ts @@ -191,3 +191,13 @@ export const ObjectWithHooks = { return 0; }, }; + +const $empty = Symbol.for("react.memo_cache_sentinel"); +export function unstable_useMemoCache(size: number) { + "use no forget"; + const $ = new Array(size); + for (let ii = 0; ii < size; ii++) { + $[ii] = $empty; + } + return React.useRef($).current; +}