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 96cce887d8655..0c23ceb345296 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -41,6 +41,10 @@ const DynamicGatingOptionsSchema = z.object({ source: z.string(), }); export type DynamicGatingOptions = z.infer; +const CustomOptOutDirectiveSchema = z + .nullable(z.array(z.string())) + .default(null); +type CustomOptOutDirective = z.infer; export type PluginOptions = { environment: EnvironmentConfig; @@ -132,6 +136,11 @@ export type PluginOptions = { */ ignoreUseNoForget: boolean; + /** + * Unstable / do not use + */ + customOptOutDirectives: CustomOptOutDirective; + sources: Array | ((filename: string) => boolean) | null; /** @@ -278,6 +287,7 @@ export const defaultOptions: PluginOptions = { return filename.indexOf('node_modules') === -1; }, enableReanimatedCheck: true, + customOptOutDirectives: null, target: '19', } as const; @@ -338,6 +348,21 @@ export function parsePluginOptions(obj: unknown): PluginOptions { } break; } + case 'customOptOutDirectives': { + const result = CustomOptOutDirectiveSchema.safeParse(value); + if (result.success) { + parsedOptions[key] = result.data; + } else { + CompilerError.throwInvalidConfig({ + reason: + 'Could not parse custom opt out directives. Update React Compiler config to fix the error', + description: `${fromZodError(result.error)}`, + loc: null, + suggestions: null, + }); + } + break; + } default: { parsedOptions[key] = value; } 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 cb57bd2c49d89..de8d16fb12a6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -63,7 +63,16 @@ export function tryFindDirectiveEnablingMemoization( export function findDirectiveDisablingMemoization( directives: Array, + {customOptOutDirectives}: PluginOptions, ): t.Directive | null { + if (customOptOutDirectives != null) { + return ( + directives.find( + directive => + customOptOutDirectives.indexOf(directive.value.value) !== -1, + ) ?? null + ); + } return ( directives.find(directive => OPT_OUT_DIRECTIVES.has(directive.value.value), @@ -394,7 +403,8 @@ export function compileProgram( code: pass.code, suppressions, hasModuleScopeOptOut: - findDirectiveDisablingMemoization(program.node.directives) != null, + findDirectiveDisablingMemoization(program.node.directives, pass.opts) != + null, }); const queue: Array = findFunctionsToCompile( @@ -571,7 +581,10 @@ function processFn( } directives = { optIn: optIn.unwrapOr(null), - optOut: findDirectiveDisablingMemoization(fn.node.body.directives), + optOut: findDirectiveDisablingMemoization( + fn.node.body.directives, + programContext.opts, + ), }; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts index b4484331dec80..6c2cfd5d07490 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts @@ -93,6 +93,21 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = { }, ], }; + +function* splitPragma( + pragma: string, +): Generator<{key: string; value: string | null}> { + for (const entry of pragma.split('@')) { + const keyVal = entry.trim(); + const valIdx = keyVal.indexOf(':'); + if (valIdx === -1) { + yield {key: keyVal.split(' ', 1)[0], value: null}; + } else { + yield {key: keyVal.slice(0, valIdx), value: keyVal.slice(valIdx + 1)}; + } + } +} + /** * For snap test fixtures and playground only. */ @@ -101,19 +116,11 @@ function parseConfigPragmaEnvironmentForTest( ): EnvironmentConfig { const maybeConfig: Partial> = {}; - for (const token of pragma.split(' ')) { - if (!token.startsWith('@')) { - continue; - } - const keyVal = token.slice(1); - const valIdx = keyVal.indexOf(':'); - const key = valIdx === -1 ? keyVal : keyVal.slice(0, valIdx); - const val = valIdx === -1 ? undefined : keyVal.slice(valIdx + 1); - const isSet = val === undefined || val === 'true'; + for (const {key, value: val} of splitPragma(pragma)) { if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) { continue; } - + const isSet = val == null || val === 'true'; if (isSet && key in testComplexConfigDefaults) { maybeConfig[key] = testComplexConfigDefaults[key]; } else if (isSet) { @@ -176,18 +183,11 @@ export function parseConfigPragmaForTests( compilationMode: defaults.compilationMode, environment, }; - for (const token of pragma.split(' ')) { - if (!token.startsWith('@')) { - continue; - } - const keyVal = token.slice(1); - const idx = keyVal.indexOf(':'); - const key = idx === -1 ? keyVal : keyVal.slice(0, idx); - const val = idx === -1 ? undefined : keyVal.slice(idx + 1); + for (const {key, value: val} of splitPragma(pragma)) { if (!hasOwnProperty(defaultOptions, key)) { continue; } - const isSet = val === undefined || val === 'true'; + const isSet = val == null || val === 'true'; if (isSet && key in testComplexPluginOptionDefaults) { options[key] = testComplexPluginOptionDefaults[key]; } else if (isSet) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md new file mode 100644 index 0000000000000..7875137a88f55 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +// @customOptOutDirectives:["use todo memo"] +function Component() { + 'use todo memo'; + return
hello world!
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +## Code + +```javascript +// @customOptOutDirectives:["use todo memo"] +function Component() { + "use todo memo"; + return
hello world!
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +### Eval output +(kind: ok)
hello world!
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx new file mode 100644 index 0000000000000..225559618386f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/custom-opt-out-directive.tsx @@ -0,0 +1,10 @@ +// @customOptOutDirectives:["use todo memo"] +function Component() { + 'use todo memo'; + return
hello world!
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +};