Skip to content

Commit 8b4c569

Browse files
committed
[compiler][gating] Experimental directive based gating (facebook#33149)
Adds `dynamicGating` as an experimental option for testing rollout DX at Meta. If specified, this enables dynamic gating which matches `use memo if(...)` directives. #### Example usage Input file ```js // @dynamicGating:{"source":"myModule"} export function MyComponent() { 'use memo if(isEnabled)'; return <div>...</div>; } ``` Compiler output ```js import {isEnabled} from 'myModule'; export const MyComponent = isEnabled() ? <optimized version> : <original version>; ``` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33149). * __->__ facebook#33149 * facebook#33148 DiffTrain build for [459a2c4](facebook@459a2c4)
1 parent 37d6fb7 commit 8b4c569

37 files changed

+967
-741
lines changed

compiled/eslint-plugin-react-hooks/index.js

Lines changed: 124 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -55944,14 +55944,78 @@ function suppressionsToCompilerError(suppressionRanges) {
5594455944

5594555945
const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']);
5594655946
const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']);
55947-
function findDirectiveEnablingMemoization(directives) {
55948-
var _a;
55949-
return ((_a = directives.find(directive => OPT_IN_DIRECTIVES.has(directive.value.value))) !== null && _a !== void 0 ? _a : null);
55947+
const DYNAMIC_GATING_DIRECTIVE = new RegExp('^use memo if\\(([^\\)]*)\\)$');
55948+
function tryFindDirectiveEnablingMemoization(directives, opts) {
55949+
var _a, _b;
55950+
const optIn = directives.find(directive => OPT_IN_DIRECTIVES.has(directive.value.value));
55951+
if (optIn != null) {
55952+
return Ok(optIn);
55953+
}
55954+
const dynamicGating = findDirectivesDynamicGating(directives, opts);
55955+
if (dynamicGating.isOk()) {
55956+
return Ok((_b = (_a = dynamicGating.unwrap()) === null || _a === void 0 ? void 0 : _a.directive) !== null && _b !== void 0 ? _b : null);
55957+
}
55958+
else {
55959+
return Err(dynamicGating.unwrapErr());
55960+
}
5595055961
}
5595155962
function findDirectiveDisablingMemoization(directives) {
5595255963
var _a;
5595355964
return ((_a = directives.find(directive => OPT_OUT_DIRECTIVES.has(directive.value.value))) !== null && _a !== void 0 ? _a : null);
5595455965
}
55966+
function findDirectivesDynamicGating(directives, opts) {
55967+
var _a, _b;
55968+
if (opts.dynamicGating === null) {
55969+
return Ok(null);
55970+
}
55971+
const errors = new CompilerError();
55972+
const result = [];
55973+
for (const directive of directives) {
55974+
const maybeMatch = DYNAMIC_GATING_DIRECTIVE.exec(directive.value.value);
55975+
if (maybeMatch != null && maybeMatch[1] != null) {
55976+
if (libExports$1.isValidIdentifier(maybeMatch[1])) {
55977+
result.push({ directive, match: maybeMatch[1] });
55978+
}
55979+
else {
55980+
errors.push({
55981+
reason: `Dynamic gating directive is not a valid JavaScript identifier`,
55982+
description: `Found '${directive.value.value}'`,
55983+
severity: ErrorSeverity.InvalidReact,
55984+
loc: (_a = directive.loc) !== null && _a !== void 0 ? _a : null,
55985+
suggestions: null,
55986+
});
55987+
}
55988+
}
55989+
}
55990+
if (errors.hasErrors()) {
55991+
return Err(errors);
55992+
}
55993+
else if (result.length > 1) {
55994+
const error = new CompilerError();
55995+
error.push({
55996+
reason: `Multiple dynamic gating directives found`,
55997+
description: `Expected a single directive but found [${result
55998+
.map(r => r.directive.value.value)
55999+
.join(', ')}]`,
56000+
severity: ErrorSeverity.InvalidReact,
56001+
loc: (_b = result[0].directive.loc) !== null && _b !== void 0 ? _b : null,
56002+
suggestions: null,
56003+
});
56004+
return Err(error);
56005+
}
56006+
else if (result.length === 1) {
56007+
return Ok({
56008+
gating: {
56009+
source: opts.dynamicGating.source,
56010+
importSpecifierName: result[0].match,
56011+
},
56012+
directive: result[0].directive,
56013+
});
56014+
}
56015+
else {
56016+
return Ok(null);
56017+
}
56018+
}
5595556019
function isCriticalError(err) {
5595656020
return !(err instanceof CompilerError) || err.isCritical();
5595756021
}
@@ -56184,25 +56248,33 @@ function findFunctionsToCompile(program, pass, programContext) {
5618456248
return queue;
5618556249
}
5618656250
function processFn(fn, fnType, programContext) {
56187-
var _a, _b, _c, _d, _e, _f, _g;
56251+
var _a, _b, _c, _d, _e, _f, _g, _h;
5618856252
let directives;
5618956253
if (fn.node.body.type !== 'BlockStatement') {
56190-
directives = { optIn: null, optOut: null };
56254+
directives = {
56255+
optIn: null,
56256+
optOut: null,
56257+
};
5619156258
}
5619256259
else {
56260+
const optIn = tryFindDirectiveEnablingMemoization(fn.node.body.directives, programContext.opts);
56261+
if (optIn.isErr()) {
56262+
handleError(optIn.unwrapErr(), programContext, (_a = fn.node.loc) !== null && _a !== void 0 ? _a : null);
56263+
return null;
56264+
}
5619356265
directives = {
56194-
optIn: findDirectiveEnablingMemoization(fn.node.body.directives),
56266+
optIn: optIn.unwrapOr(null),
5619556267
optOut: findDirectiveDisablingMemoization(fn.node.body.directives),
5619656268
};
5619756269
}
5619856270
let compiledFn;
5619956271
const compileResult = tryCompileFunction(fn, fnType, programContext);
5620056272
if (compileResult.kind === 'error') {
5620156273
if (directives.optOut != null) {
56202-
logError(compileResult.error, programContext, (_a = fn.node.loc) !== null && _a !== void 0 ? _a : null);
56274+
logError(compileResult.error, programContext, (_b = fn.node.loc) !== null && _b !== void 0 ? _b : null);
5620356275
}
5620456276
else {
56205-
handleError(compileResult.error, programContext, (_b = fn.node.loc) !== null && _b !== void 0 ? _b : null);
56277+
handleError(compileResult.error, programContext, (_c = fn.node.loc) !== null && _c !== void 0 ? _c : null);
5620656278
}
5620756279
const retryResult = retryCompileFunction(fn, fnType, programContext);
5620856280
if (retryResult == null) {
@@ -56217,16 +56289,16 @@ function processFn(fn, fnType, programContext) {
5621756289
directives.optOut != null) {
5621856290
programContext.logEvent({
5621956291
kind: 'CompileSkip',
56220-
fnLoc: (_c = fn.node.body.loc) !== null && _c !== void 0 ? _c : null,
56292+
fnLoc: (_d = fn.node.body.loc) !== null && _d !== void 0 ? _d : null,
5622156293
reason: `Skipped due to '${directives.optOut.value}' directive.`,
56222-
loc: (_d = directives.optOut.loc) !== null && _d !== void 0 ? _d : null,
56294+
loc: (_e = directives.optOut.loc) !== null && _e !== void 0 ? _e : null,
5622356295
});
5622456296
return null;
5622556297
}
5622656298
programContext.logEvent({
5622756299
kind: 'CompileSuccess',
56228-
fnLoc: (_e = fn.node.loc) !== null && _e !== void 0 ? _e : null,
56229-
fnName: (_g = (_f = compiledFn.id) === null || _f === void 0 ? void 0 : _f.name) !== null && _g !== void 0 ? _g : null,
56300+
fnLoc: (_f = fn.node.loc) !== null && _f !== void 0 ? _f : null,
56301+
fnName: (_h = (_g = compiledFn.id) === null || _g === void 0 ? void 0 : _g.name) !== null && _h !== void 0 ? _h : null,
5623056302
memoSlots: compiledFn.memoSlotsUsed,
5623156303
memoBlocks: compiledFn.memoBlocks,
5623256304
memoValues: compiledFn.memoValues,
@@ -56290,19 +56362,23 @@ function retryCompileFunction(fn, fnType, programContext) {
5629056362
}
5629156363
}
5629256364
function applyCompiledFunctions(program, compiledFns, pass, programContext) {
56293-
const referencedBeforeDeclared = pass.opts.gating != null
56294-
? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns)
56295-
: null;
56365+
var _a, _b;
56366+
let referencedBeforeDeclared = null;
5629656367
for (const result of compiledFns) {
5629756368
const { kind, originalFn, compiledFn } = result;
5629856369
const transformedFn = createNewFunctionNode(originalFn, compiledFn);
5629956370
programContext.alreadyCompiled.add(transformedFn);
56300-
if (referencedBeforeDeclared != null && kind === 'original') {
56301-
CompilerError.invariant(pass.opts.gating != null, {
56302-
reason: "Expected 'gating' import to be present",
56303-
loc: null,
56304-
});
56305-
insertGatedFunctionDeclaration(originalFn, transformedFn, programContext, pass.opts.gating, referencedBeforeDeclared.has(result));
56371+
let dynamicGating = null;
56372+
if (originalFn.node.body.type === 'BlockStatement') {
56373+
const result = findDirectivesDynamicGating(originalFn.node.body.directives, pass.opts);
56374+
if (result.isOk()) {
56375+
dynamicGating = (_b = (_a = result.unwrap()) === null || _a === void 0 ? void 0 : _a.gating) !== null && _b !== void 0 ? _b : null;
56376+
}
56377+
}
56378+
const functionGating = dynamicGating !== null && dynamicGating !== void 0 ? dynamicGating : pass.opts.gating;
56379+
if (kind === 'original' && functionGating != null) {
56380+
referencedBeforeDeclared !== null && referencedBeforeDeclared !== void 0 ? referencedBeforeDeclared : (referencedBeforeDeclared = getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns));
56381+
insertGatedFunctionDeclaration(originalFn, transformedFn, programContext, functionGating, referencedBeforeDeclared.has(result));
5630656382
}
5630756383
else {
5630856384
originalFn.replaceWith(transformedFn);
@@ -56338,8 +56414,10 @@ function getReactFunctionType(fn, pass) {
5633856414
var _a, _b;
5633956415
const hookPattern = pass.opts.environment.hookPattern;
5634056416
if (fn.node.body.type === 'BlockStatement') {
56341-
if (findDirectiveEnablingMemoization(fn.node.body.directives) != null)
56417+
const optInDirectives = tryFindDirectiveEnablingMemoization(fn.node.body.directives, pass.opts);
56418+
if (optInDirectives.unwrapOr(null) != null) {
5634256419
return (_a = getComponentOrHookLike(fn, hookPattern)) !== null && _a !== void 0 ? _a : 'Other';
56420+
}
5634356421
}
5634456422
let componentSyntaxType = null;
5634556423
if (fn.isFunctionDeclaration()) {
@@ -56854,6 +56932,9 @@ zod.z.enum([
5685456932
'critical_errors',
5685556933
'none',
5685656934
]);
56935+
const DynamicGatingOptionsSchema = zod.z.object({
56936+
source: zod.z.string(),
56937+
});
5685756938
const CompilerReactTargetSchema = zod.z.union([
5685856939
zod.z.literal('17'),
5685956940
zod.z.literal('18'),
@@ -56876,6 +56957,7 @@ const defaultOptions = {
5687656957
logger: null,
5687756958
gating: null,
5687856959
noEmit: false,
56960+
dynamicGating: null,
5687956961
eslintSuppressionRules: null,
5688056962
flowSuppressions: true,
5688156963
ignoreUseNoForget: false,
@@ -56922,6 +57004,26 @@ function parsePluginOptions(obj) {
5692257004
}
5692357005
break;
5692457006
}
57007+
case 'dynamicGating': {
57008+
if (value == null) {
57009+
parsedOptions[key] = null;
57010+
}
57011+
else {
57012+
const result = DynamicGatingOptionsSchema.safeParse(value);
57013+
if (result.success) {
57014+
parsedOptions[key] = result.data;
57015+
}
57016+
else {
57017+
CompilerError.throwInvalidConfig({
57018+
reason: 'Could not parse dynamic gating. Update React Compiler config to fix the error',
57019+
description: `${zodValidationError.fromZodError(result.error)}`,
57020+
loc: null,
57021+
suggestions: null,
57022+
});
57023+
}
57024+
}
57025+
break;
57026+
}
5692557027
default: {
5692657028
parsedOptions[key] = value;
5692757029
}

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3710c4d4f9ffb6aa07e291b822e4ec7d69ed5a32
1+
459a2c4298187cb0ee45605e2575ff35f4a81183
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3710c4d4f9ffb6aa07e291b822e4ec7d69ed5a32
1+
459a2c4298187cb0ee45605e2575ff35f4a81183

compiled/facebook-www/React-dev.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1537,7 +1537,7 @@ __DEV__ &&
15371537
exports.useTransition = function () {
15381538
return resolveDispatcher().useTransition();
15391539
};
1540-
exports.version = "19.2.0-www-classic-3710c4d4-20250521";
1540+
exports.version = "19.2.0-www-classic-459a2c42-20250521";
15411541
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
15421542
"function" ===
15431543
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-dev.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1537,7 +1537,7 @@ __DEV__ &&
15371537
exports.useTransition = function () {
15381538
return resolveDispatcher().useTransition();
15391539
};
1540-
exports.version = "19.2.0-www-modern-3710c4d4-20250521";
1540+
exports.version = "19.2.0-www-modern-459a2c42-20250521";
15411541
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
15421542
"function" ===
15431543
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-prod.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,4 +635,4 @@ exports.useSyncExternalStore = function (
635635
exports.useTransition = function () {
636636
return ReactSharedInternals.H.useTransition();
637637
};
638-
exports.version = "19.2.0-www-classic-3710c4d4-20250521";
638+
exports.version = "19.2.0-www-classic-459a2c42-20250521";

compiled/facebook-www/React-prod.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,4 +635,4 @@ exports.useSyncExternalStore = function (
635635
exports.useTransition = function () {
636636
return ReactSharedInternals.H.useTransition();
637637
};
638-
exports.version = "19.2.0-www-modern-3710c4d4-20250521";
638+
exports.version = "19.2.0-www-modern-459a2c42-20250521";

compiled/facebook-www/React-profiling.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ exports.useSyncExternalStore = function (
639639
exports.useTransition = function () {
640640
return ReactSharedInternals.H.useTransition();
641641
};
642-
exports.version = "19.2.0-www-classic-3710c4d4-20250521";
642+
exports.version = "19.2.0-www-classic-459a2c42-20250521";
643643
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
644644
"function" ===
645645
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-profiling.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ exports.useSyncExternalStore = function (
639639
exports.useTransition = function () {
640640
return ReactSharedInternals.H.useTransition();
641641
};
642-
exports.version = "19.2.0-www-modern-3710c4d4-20250521";
642+
exports.version = "19.2.0-www-modern-459a2c42-20250521";
643643
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
644644
"function" ===
645645
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/ReactART-dev.classic.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19080,10 +19080,10 @@ __DEV__ &&
1908019080
(function () {
1908119081
var internals = {
1908219082
bundleType: 1,
19083-
version: "19.2.0-www-classic-3710c4d4-20250521",
19083+
version: "19.2.0-www-classic-459a2c42-20250521",
1908419084
rendererPackageName: "react-art",
1908519085
currentDispatcherRef: ReactSharedInternals,
19086-
reconcilerVersion: "19.2.0-www-classic-3710c4d4-20250521"
19086+
reconcilerVersion: "19.2.0-www-classic-459a2c42-20250521"
1908719087
};
1908819088
internals.overrideHookState = overrideHookState;
1908919089
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -19117,7 +19117,7 @@ __DEV__ &&
1911719117
exports.Shape = Shape;
1911819118
exports.Surface = Surface;
1911919119
exports.Text = Text;
19120-
exports.version = "19.2.0-www-classic-3710c4d4-20250521";
19120+
exports.version = "19.2.0-www-classic-459a2c42-20250521";
1912119121
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
1912219122
"function" ===
1912319123
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/ReactART-dev.modern.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18852,10 +18852,10 @@ __DEV__ &&
1885218852
(function () {
1885318853
var internals = {
1885418854
bundleType: 1,
18855-
version: "19.2.0-www-modern-3710c4d4-20250521",
18855+
version: "19.2.0-www-modern-459a2c42-20250521",
1885618856
rendererPackageName: "react-art",
1885718857
currentDispatcherRef: ReactSharedInternals,
18858-
reconcilerVersion: "19.2.0-www-modern-3710c4d4-20250521"
18858+
reconcilerVersion: "19.2.0-www-modern-459a2c42-20250521"
1885918859
};
1886018860
internals.overrideHookState = overrideHookState;
1886118861
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -18889,7 +18889,7 @@ __DEV__ &&
1888918889
exports.Shape = Shape;
1889018890
exports.Surface = Surface;
1889118891
exports.Text = Text;
18892-
exports.version = "19.2.0-www-modern-3710c4d4-20250521";
18892+
exports.version = "19.2.0-www-modern-459a2c42-20250521";
1889318893
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
1889418894
"function" ===
1889518895
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/ReactART-prod.classic.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11392,10 +11392,10 @@ var slice = Array.prototype.slice,
1139211392
})(React.Component);
1139311393
var internals$jscomp$inline_1624 = {
1139411394
bundleType: 0,
11395-
version: "19.2.0-www-classic-3710c4d4-20250521",
11395+
version: "19.2.0-www-classic-459a2c42-20250521",
1139611396
rendererPackageName: "react-art",
1139711397
currentDispatcherRef: ReactSharedInternals,
11398-
reconcilerVersion: "19.2.0-www-classic-3710c4d4-20250521"
11398+
reconcilerVersion: "19.2.0-www-classic-459a2c42-20250521"
1139911399
};
1140011400
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
1140111401
var hook$jscomp$inline_1625 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -11421,4 +11421,4 @@ exports.RadialGradient = RadialGradient;
1142111421
exports.Shape = TYPES.SHAPE;
1142211422
exports.Surface = Surface;
1142311423
exports.Text = Text;
11424-
exports.version = "19.2.0-www-classic-3710c4d4-20250521";
11424+
exports.version = "19.2.0-www-classic-459a2c42-20250521";

compiled/facebook-www/ReactART-prod.modern.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11104,10 +11104,10 @@ var slice = Array.prototype.slice,
1110411104
})(React.Component);
1110511105
var internals$jscomp$inline_1597 = {
1110611106
bundleType: 0,
11107-
version: "19.2.0-www-modern-3710c4d4-20250521",
11107+
version: "19.2.0-www-modern-459a2c42-20250521",
1110811108
rendererPackageName: "react-art",
1110911109
currentDispatcherRef: ReactSharedInternals,
11110-
reconcilerVersion: "19.2.0-www-modern-3710c4d4-20250521"
11110+
reconcilerVersion: "19.2.0-www-modern-459a2c42-20250521"
1111111111
};
1111211112
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
1111311113
var hook$jscomp$inline_1598 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -11133,4 +11133,4 @@ exports.RadialGradient = RadialGradient;
1113311133
exports.Shape = TYPES.SHAPE;
1113411134
exports.Surface = Surface;
1113511135
exports.Text = Text;
11136-
exports.version = "19.2.0-www-modern-3710c4d4-20250521";
11136+
exports.version = "19.2.0-www-modern-459a2c42-20250521";

0 commit comments

Comments
 (0)