Compare commits

...

1 Commits

Author SHA1 Message Date
Mofei Zhang
51620ac79c [compiler][gating] Custom opt out directives (experimental option)
Adding an experimental / unstable compiler config to enable custom opt-out directives
2025-05-23 13:12:02 -04:00
5 changed files with 104 additions and 21 deletions

View File

@@ -41,6 +41,10 @@ const DynamicGatingOptionsSchema = z.object({
source: z.string(), source: z.string(),
}); });
export type DynamicGatingOptions = z.infer<typeof DynamicGatingOptionsSchema>; export type DynamicGatingOptions = z.infer<typeof DynamicGatingOptionsSchema>;
const CustomOptOutDirectiveSchema = z
.nullable(z.array(z.string()))
.default(null);
type CustomOptOutDirective = z.infer<typeof CustomOptOutDirectiveSchema>;
export type PluginOptions = { export type PluginOptions = {
environment: EnvironmentConfig; environment: EnvironmentConfig;
@@ -132,6 +136,11 @@ export type PluginOptions = {
*/ */
ignoreUseNoForget: boolean; ignoreUseNoForget: boolean;
/**
* Unstable / do not use
*/
customOptOutDirectives: CustomOptOutDirective;
sources: Array<string> | ((filename: string) => boolean) | null; sources: Array<string> | ((filename: string) => boolean) | null;
/** /**
@@ -278,6 +287,7 @@ export const defaultOptions: PluginOptions = {
return filename.indexOf('node_modules') === -1; return filename.indexOf('node_modules') === -1;
}, },
enableReanimatedCheck: true, enableReanimatedCheck: true,
customOptOutDirectives: null,
target: '19', target: '19',
} as const; } as const;
@@ -338,6 +348,21 @@ export function parsePluginOptions(obj: unknown): PluginOptions {
} }
break; 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: { default: {
parsedOptions[key] = value; parsedOptions[key] = value;
} }

View File

@@ -63,7 +63,16 @@ export function tryFindDirectiveEnablingMemoization(
export function findDirectiveDisablingMemoization( export function findDirectiveDisablingMemoization(
directives: Array<t.Directive>, directives: Array<t.Directive>,
{customOptOutDirectives}: PluginOptions,
): t.Directive | null { ): t.Directive | null {
if (customOptOutDirectives != null) {
return (
directives.find(
directive =>
customOptOutDirectives.indexOf(directive.value.value) !== -1,
) ?? null
);
}
return ( return (
directives.find(directive => directives.find(directive =>
OPT_OUT_DIRECTIVES.has(directive.value.value), OPT_OUT_DIRECTIVES.has(directive.value.value),
@@ -394,7 +403,8 @@ export function compileProgram(
code: pass.code, code: pass.code,
suppressions, suppressions,
hasModuleScopeOptOut: hasModuleScopeOptOut:
findDirectiveDisablingMemoization(program.node.directives) != null, findDirectiveDisablingMemoization(program.node.directives, pass.opts) !=
null,
}); });
const queue: Array<CompileSource> = findFunctionsToCompile( const queue: Array<CompileSource> = findFunctionsToCompile(
@@ -571,7 +581,10 @@ function processFn(
} }
directives = { directives = {
optIn: optIn.unwrapOr(null), optIn: optIn.unwrapOr(null),
optOut: findDirectiveDisablingMemoization(fn.node.body.directives), optOut: findDirectiveDisablingMemoization(
fn.node.body.directives,
programContext.opts,
),
}; };
} }

View File

@@ -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. * For snap test fixtures and playground only.
*/ */
@@ -101,19 +116,11 @@ function parseConfigPragmaEnvironmentForTest(
): EnvironmentConfig { ): EnvironmentConfig {
const maybeConfig: Partial<Record<keyof EnvironmentConfig, unknown>> = {}; const maybeConfig: Partial<Record<keyof EnvironmentConfig, unknown>> = {};
for (const token of pragma.split(' ')) { for (const {key, value: val} of splitPragma(pragma)) {
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';
if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) { if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) {
continue; continue;
} }
const isSet = val == null || val === 'true';
if (isSet && key in testComplexConfigDefaults) { if (isSet && key in testComplexConfigDefaults) {
maybeConfig[key] = testComplexConfigDefaults[key]; maybeConfig[key] = testComplexConfigDefaults[key];
} else if (isSet) { } else if (isSet) {
@@ -176,18 +183,11 @@ export function parseConfigPragmaForTests(
compilationMode: defaults.compilationMode, compilationMode: defaults.compilationMode,
environment, environment,
}; };
for (const token of pragma.split(' ')) { for (const {key, value: val} of splitPragma(pragma)) {
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);
if (!hasOwnProperty(defaultOptions, key)) { if (!hasOwnProperty(defaultOptions, key)) {
continue; continue;
} }
const isSet = val === undefined || val === 'true'; const isSet = val == null || val === 'true';
if (isSet && key in testComplexPluginOptionDefaults) { if (isSet && key in testComplexPluginOptionDefaults) {
options[key] = testComplexPluginOptionDefaults[key]; options[key] = testComplexPluginOptionDefaults[key];
} else if (isSet) { } else if (isSet) {

View File

@@ -0,0 +1,35 @@
## Input
```javascript
// @customOptOutDirectives:["use todo memo"]
function Component() {
'use todo memo';
return <div>hello world!</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
};
```
## Code
```javascript
// @customOptOutDirectives:["use todo memo"]
function Component() {
"use todo memo";
return <div>hello world!</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
};
```
### Eval output
(kind: ok) <div>hello world!</div>

View File

@@ -0,0 +1,10 @@
// @customOptOutDirectives:["use todo memo"]
function Component() {
'use todo memo';
return <div>hello world!</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
};