Skip to content

Commit 12cea1e

Browse files
committed
[compiler][gating] Custom opt out directives (experimental option)
Adding an experimental / unstable compiler config to enable custom opt-out directives
1 parent 3e9db65 commit 12cea1e

File tree

5 files changed

+104
-21
lines changed

5 files changed

+104
-21
lines changed

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ const DynamicGatingOptionsSchema = z.object({
4141
source: z.string(),
4242
});
4343
export type DynamicGatingOptions = z.infer<typeof DynamicGatingOptionsSchema>;
44+
const CustomOptOutDirectiveSchema = z
45+
.nullable(z.array(z.string()))
46+
.default(null);
47+
type CustomOptOutDirective = z.infer<typeof CustomOptOutDirectiveSchema>;
4448

4549
export type PluginOptions = {
4650
environment: EnvironmentConfig;
@@ -132,6 +136,11 @@ export type PluginOptions = {
132136
*/
133137
ignoreUseNoForget: boolean;
134138

139+
/**
140+
* Unstable / do not use
141+
*/
142+
customOptOutDirectives: CustomOptOutDirective;
143+
135144
sources: Array<string> | ((filename: string) => boolean) | null;
136145

137146
/**
@@ -278,6 +287,7 @@ export const defaultOptions: PluginOptions = {
278287
return filename.indexOf('node_modules') === -1;
279288
},
280289
enableReanimatedCheck: true,
290+
customOptOutDirectives: null,
281291
target: '19',
282292
} as const;
283293

@@ -338,6 +348,21 @@ export function parsePluginOptions(obj: unknown): PluginOptions {
338348
}
339349
break;
340350
}
351+
case 'customOptOutDirectives': {
352+
const result = CustomOptOutDirectiveSchema.safeParse(value);
353+
if (result.success) {
354+
parsedOptions[key] = result.data;
355+
} else {
356+
CompilerError.throwInvalidConfig({
357+
reason:
358+
'Could not parse custom opt out directives. Update React Compiler config to fix the error',
359+
description: `${fromZodError(result.error)}`,
360+
loc: null,
361+
suggestions: null,
362+
});
363+
}
364+
break;
365+
}
341366
default: {
342367
parsedOptions[key] = value;
343368
}

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,16 @@ export function tryFindDirectiveEnablingMemoization(
6363

6464
export function findDirectiveDisablingMemoization(
6565
directives: Array<t.Directive>,
66+
{customOptOutDirectives}: PluginOptions,
6667
): t.Directive | null {
68+
if (customOptOutDirectives != null) {
69+
return (
70+
directives.find(
71+
directive =>
72+
customOptOutDirectives.indexOf(directive.value.value) !== -1,
73+
) ?? null
74+
);
75+
}
6776
return (
6877
directives.find(directive =>
6978
OPT_OUT_DIRECTIVES.has(directive.value.value),
@@ -394,7 +403,8 @@ export function compileProgram(
394403
code: pass.code,
395404
suppressions,
396405
hasModuleScopeOptOut:
397-
findDirectiveDisablingMemoization(program.node.directives) != null,
406+
findDirectiveDisablingMemoization(program.node.directives, pass.opts) !=
407+
null,
398408
});
399409

400410
const queue: Array<CompileSource> = findFunctionsToCompile(
@@ -571,7 +581,10 @@ function processFn(
571581
}
572582
directives = {
573583
optIn: optIn.unwrapOr(null),
574-
optOut: findDirectiveDisablingMemoization(fn.node.body.directives),
584+
optOut: findDirectiveDisablingMemoization(
585+
fn.node.body.directives,
586+
programContext.opts,
587+
),
575588
};
576589
}
577590

compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,21 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = {
9393
},
9494
],
9595
};
96+
97+
function* splitPragma(
98+
pragma: string,
99+
): Generator<{key: string; value: string | null}> {
100+
for (const entry of pragma.split('@')) {
101+
const keyVal = entry.trim();
102+
const valIdx = keyVal.indexOf(':');
103+
if (valIdx === -1) {
104+
yield {key: keyVal.split(' ', 1)[0], value: null};
105+
} else {
106+
yield {key: keyVal.slice(0, valIdx), value: keyVal.slice(valIdx + 1)};
107+
}
108+
}
109+
}
110+
96111
/**
97112
* For snap test fixtures and playground only.
98113
*/
@@ -101,19 +116,11 @@ function parseConfigPragmaEnvironmentForTest(
101116
): EnvironmentConfig {
102117
const maybeConfig: Partial<Record<keyof EnvironmentConfig, unknown>> = {};
103118

104-
for (const token of pragma.split(' ')) {
105-
if (!token.startsWith('@')) {
106-
continue;
107-
}
108-
const keyVal = token.slice(1);
109-
const valIdx = keyVal.indexOf(':');
110-
const key = valIdx === -1 ? keyVal : keyVal.slice(0, valIdx);
111-
const val = valIdx === -1 ? undefined : keyVal.slice(valIdx + 1);
112-
const isSet = val === undefined || val === 'true';
119+
for (const {key, value: val} of splitPragma(pragma)) {
113120
if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) {
114121
continue;
115122
}
116-
123+
const isSet = val == null || val === 'true';
117124
if (isSet && key in testComplexConfigDefaults) {
118125
maybeConfig[key] = testComplexConfigDefaults[key];
119126
} else if (isSet) {
@@ -176,18 +183,11 @@ export function parseConfigPragmaForTests(
176183
compilationMode: defaults.compilationMode,
177184
environment,
178185
};
179-
for (const token of pragma.split(' ')) {
180-
if (!token.startsWith('@')) {
181-
continue;
182-
}
183-
const keyVal = token.slice(1);
184-
const idx = keyVal.indexOf(':');
185-
const key = idx === -1 ? keyVal : keyVal.slice(0, idx);
186-
const val = idx === -1 ? undefined : keyVal.slice(idx + 1);
186+
for (const {key, value: val} of splitPragma(pragma)) {
187187
if (!hasOwnProperty(defaultOptions, key)) {
188188
continue;
189189
}
190-
const isSet = val === undefined || val === 'true';
190+
const isSet = val == null || val === 'true';
191191
if (isSet && key in testComplexPluginOptionDefaults) {
192192
options[key] = testComplexPluginOptionDefaults[key];
193193
} else if (isSet) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
## Input
3+
4+
```javascript
5+
// @customOptOutDirectives:["use todo memo"]
6+
function Component() {
7+
'use todo memo';
8+
return <div>hello world!</div>;
9+
}
10+
11+
export const FIXTURE_ENTRYPOINT = {
12+
fn: Component,
13+
params: [],
14+
};
15+
16+
```
17+
18+
## Code
19+
20+
```javascript
21+
// @customOptOutDirectives:["use todo memo"]
22+
function Component() {
23+
"use todo memo";
24+
return <div>hello world!</div>;
25+
}
26+
27+
export const FIXTURE_ENTRYPOINT = {
28+
fn: Component,
29+
params: [],
30+
};
31+
32+
```
33+
34+
### Eval output
35+
(kind: ok) <div>hello world!</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @customOptOutDirectives:["use todo memo"]
2+
function Component() {
3+
'use todo memo';
4+
return <div>hello world!</div>;
5+
}
6+
7+
export const FIXTURE_ENTRYPOINT = {
8+
fn: Component,
9+
params: [],
10+
};

0 commit comments

Comments
 (0)