From c07531f07123bb250f312e8b701336d812bb24f5 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 2 Dec 2024 16:17:57 -0500 Subject: [PATCH] [compiler] Add meta internal option for useMemoCache import Adds `target: 'donotuse_meta_internal'`, which inserts useMemoCache imports directly from `react`. Note that this is only valid for Meta bundles, as others do not [re-export the `c` function](https://github.com/facebook/react/blob/5b0ef217ef32333a8e56f39be04327c89efa346f/packages/react/index.fb.js#L68-L70). ```js // target=donotuse_meta_internal import {c as _c} from 'react'; // target=19 import {c as _c} from 'react/compiler-runtime'; // target=17,18 import {c as _c} from 'react-compiler-runtime'; ``` Meta is a bit special in that react runtime and compiler are guaranteed to be up-to-date and compatible. It also has its own bundling and module resolution logic, which makes importing from `react/compiler-runtime` tricky. I'm also fine with implementing the alternative which adds an internal stub for `react-compiler-runtime` and [bundles](https://github.com/facebook/react/blob/5b0ef217ef32333a8e56f39be04327c89efa346f/scripts/rollup/bundles.js#L120) the runtime for internal builds. --- .../src/Entrypoint/Options.ts | 17 +++++++- .../src/Entrypoint/Program.ts | 35 ++++++--------- .../target-flag-meta-internal.expect.md | 43 +++++++++++++++++++ .../compiler/target-flag-meta-internal.js | 11 +++++ .../packages/snap/src/SproutTodoFilter.ts | 1 + compiler/packages/snap/src/compiler.ts | 14 ++++-- 6 files changed, 96 insertions(+), 25 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 10bcebe44e976..72ed9e7c866ce 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -121,7 +121,22 @@ export type PluginOptions = { target: CompilerReactTarget; }; -const CompilerReactTargetSchema = z.enum(['17', '18', '19']); +const CompilerReactTargetSchema = z.union([ + z.literal('17'), + z.literal('18'), + z.literal('19'), + /** + * Used exclusively for Meta apps which are guaranteed to have compatible + * react runtime and compiler versions. Note that only the FB-internal bundles + * re-export useMemoCache (see + * https://github.com/facebook/react/blob/5b0ef217ef32333a8e56f39be04327c89efa346f/packages/react/index.fb.js#L68-L70), + * so this option is invalid / creates runtime errors for open-source users. + */ + z.object({ + kind: z.literal('donotuse_meta_internal'), + runtimeModule: z.string().default('react'), + }), +]); export type CompilerReactTarget = z.infer; const CompilationModeSchema = z.enum([ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index f5dbb324bf601..a6e09a1d061da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -1123,30 +1123,23 @@ function checkFunctionReferencedBeforeDeclarationAtTopLevel( return errors.details.length > 0 ? errors : null; } -type ReactCompilerRuntimeModule = - | 'react/compiler-runtime' // from react namespace - | 'react-compiler-runtime'; // npm package -function getReactCompilerRuntimeModule( - opts: PluginOptions, -): ReactCompilerRuntimeModule { - let moduleName: ReactCompilerRuntimeModule | null = null; - switch (opts.target) { - case '17': - case '18': { - moduleName = 'react-compiler-runtime'; - break; - } - case '19': { - moduleName = 'react/compiler-runtime'; - break; - } - default: - CompilerError.invariant(moduleName != null, { +function getReactCompilerRuntimeModule(opts: PluginOptions): string { + if (opts.target === '19') { + return 'react/compiler-runtime'; // from react namespace + } else if (opts.target === '17' || opts.target === '18') { + return 'react-compiler-runtime'; // npm package + } else { + CompilerError.invariant( + opts.target != null && + opts.target.kind === 'donotuse_meta_internal' && + typeof opts.target.runtimeModule === 'string', + { reason: 'Expected target to already be validated', description: null, loc: null, suggestions: null, - }); + }, + ); + return opts.target.runtimeModule; } - return moduleName; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md new file mode 100644 index 0000000000000..acf34a474cfac --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md @@ -0,0 +1,43 @@ + +## Input + +```javascript +// @target="donotuse_meta_internal" + +function Component() { + return
Hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; + +``` + +## Code + +```javascript +import { c as _c } from "react"; // @target="donotuse_meta_internal" + +function Component() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
Hello world
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.js new file mode 100644 index 0000000000000..02f71b841c063 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.js @@ -0,0 +1,11 @@ +// @target="donotuse_meta_internal" + +function Component() { + return
Hello world
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], + isComponent: true, +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index bb51ece12f28e..36b3e92f9636b 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -504,6 +504,7 @@ const skipFilter = new Set([ // Depends on external functions 'idx-method-no-outlining-wildcard', 'idx-method-no-outlining', + 'target-flag-meta-internal', // needs to be executed as a module 'meta-property', diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 95af40d62a880..68acdbc7900b7 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -18,6 +18,7 @@ import type { LoggerEvent, PanicThresholdOptions, PluginOptions, + CompilerReactTarget, } from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR'; import type { @@ -55,7 +56,7 @@ function makePluginOptions( let validatePreserveExistingMemoizationGuarantees = false; let customMacros: null | Array = null; let validateBlocklistedImports = null; - let target = '19' as const; + let target: CompilerReactTarget = '19'; if (firstLine.indexOf('@compilationMode(annotation)') !== -1) { assert( @@ -81,8 +82,15 @@ function makePluginOptions( const targetMatch = /@target="([^"]+)"/.exec(firstLine); if (targetMatch) { - // @ts-ignore - target = targetMatch[1]; + if (targetMatch[1] === 'donotuse_meta_internal') { + target = { + kind: targetMatch[1], + runtimeModule: 'react', + }; + } else { + // @ts-ignore + target = targetMatch[1]; + } } if (firstLine.includes('@panicThreshold(none)')) {