Skip to content

Commit

Permalink
Better NEXT_MAJOR support for RN flags (#28583)
Browse files Browse the repository at this point in the history
The `__NEXT_MAJOR__` value in the RN flags doesn't make sense because:

a) The flags are for the next RN major, since it only impacts the
renderers
b) The flags are off, so they're not currently in the next major, they
need enabled
c) the flag script didn't support it

This PR adds two aliases to the RN file:
- `__TODO_NEXT_RN_MAJOR__`: flags that need enabled before the next RN
major.
- `__NEXT_RN_MAJOR__`: flags that have been enabled since the last RN
major.

These values will need to be manually kept up to date when we cut a RN
version, but once RN switches to the canary build and aligns all the
flags, this entire file can be deleted.

## Script screen
Notably, I added a TODO value and a legend that prints at the end of the
script:

<img width="1078" alt="Screenshot 2024-03-18 at 8 11 27 PM"
src="https://github.com/facebook/react/assets/2440089/14da9066-f77d-437f-8188-830a31a843c5">
  • Loading branch information
rickhanlonii authored Mar 22, 2024
1 parent fa0efa1 commit fabd6d3
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 76 deletions.
113 changes: 62 additions & 51 deletions packages/shared/forks/ReactFeatureFlags.native-oss.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,98 +10,109 @@
import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
import typeof * as ExportsType from './ReactFeatureFlags.native-oss';

// TODO: Align these flags with canary and delete this file once RN ships from Canary.

// -----------------------------------------------------------------------------
// Ready for next major.
// TODO for next React Native major.
//
// Alias __NEXT_MAJOR__ to false for easier skimming.
// Alias __TODO_NEXT_RN_MAJOR__ to false for easier skimming.
// -----------------------------------------------------------------------------
const __NEXT_MAJOR__ = false;
const __TODO_NEXT_RN_MAJOR__ = false;
export const enableRefAsProp = __TODO_NEXT_RN_MAJOR__;
export const disableStringRefs = __TODO_NEXT_RN_MAJOR__;
export const disableLegacyMode = __TODO_NEXT_RN_MAJOR__;
export const enableBigIntSupport = __TODO_NEXT_RN_MAJOR__;
export const useModernStrictMode = __TODO_NEXT_RN_MAJOR__;
export const enableReactTestRendererWarning = __TODO_NEXT_RN_MAJOR__;
export const enableAsyncActions = __TODO_NEXT_RN_MAJOR__;
export const consoleManagedByDevToolsDuringStrictMode = __TODO_NEXT_RN_MAJOR__;
export const enableDeferRootSchedulingToMicrotask = __TODO_NEXT_RN_MAJOR__;
export const alwaysThrottleDisappearingFallbacks = __TODO_NEXT_RN_MAJOR__;
export const alwaysThrottleRetries = __TODO_NEXT_RN_MAJOR__;
export const enableInfiniteRenderLoopDetection = __TODO_NEXT_RN_MAJOR__;
export const enableComponentStackLocations = __TODO_NEXT_RN_MAJOR__;
export const disableModulePatternComponents = __TODO_NEXT_RN_MAJOR__;

// -----------------------------------------------------------------------------
// These are ready to flip after the next React npm release (or RN switches to
// Canary, but can't flip before then because of react/renderer mismatches.
// -----------------------------------------------------------------------------
export const enableCache = __TODO_NEXT_RN_MAJOR__;
export const enableRenderableContext = __TODO_NEXT_RN_MAJOR__;

// -----------------------------------------------------------------------------
// Already enabled for next React Native major.
// Hardcode these to true after the next RN major.
//
// Alias __NEXT_RN_MAJOR__ to true for easier skimming.
// -----------------------------------------------------------------------------
const __NEXT_RN_MAJOR__ = true;
export const disableClientCache = __NEXT_RN_MAJOR__;
export const disableLegacyContext = __NEXT_RN_MAJOR__;
export const enableCacheElement = __NEXT_RN_MAJOR__;
export const enableTaint = __NEXT_RN_MAJOR__;
export const enableUnifiedSyncLane = __NEXT_RN_MAJOR__;
export const enableFizzExternalRuntime = __NEXT_RN_MAJOR__; // DOM-only
export const disableJavaScriptURLs = __NEXT_RN_MAJOR__; // DOM-only
export const enableFormActions = __NEXT_RN_MAJOR__; // DOM-only
export const enableBinaryFlight = __NEXT_RN_MAJOR__; // DOM-only
export const enableCustomElementPropertySupport = __NEXT_RN_MAJOR__; // DOM-only
export const enableServerComponentKeys = __NEXT_RN_MAJOR__;
export const enableServerComponentLogs = __NEXT_RN_MAJOR__;

// DEV-only but enabled in the next RN Major.
// Not supported by flag script to avoid the special case.
export const debugRenderPhaseSideEffectsForStrictMode = __DEV__;

// TODO: decide on React 19
export const enableUseMemoCacheHook = false;
export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__;

// -----------------------------------------------------------------------------
// All other flags
// -----------------------------------------------------------------------------
export const enableCPUSuspense = false;
export const enableDebugTracing = false;
export const enableAsyncDebugInfo = false;
export const enableSchedulingProfiler = false;
export const enableProfilerTimer = __PROFILE__;
export const enableProfilerCommitHooks = __PROFILE__;
export const enableProfilerNestedUpdatePhase = __PROFILE__;
export const enableUpdaterTracking = __PROFILE__;
export const enableCache = __NEXT_MAJOR__;
export const enableLegacyCache = false;
export const enableCacheElement = true;
export const enableFetchInstrumentation = false;
export const enableFormActions = true; // Doesn't affect Native
export const enableBinaryFlight = true;
export const enableTaint = true;
export const enablePostpone = false;
export const disableJavaScriptURLs = true;
export const disableCommentsAsDOMContainers = true;
export const disableInputAttributeSyncing = false;
export const disableIEWorkarounds = true;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
export const enableSuspenseCallback = false;
export const disableLegacyContext = true;
export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = false;
export const disableModulePatternComponents = false;
export const enableSuspenseAvoidThisFallback = false;
export const enableSuspenseAvoidThisFallbackFizz = false;
export const enableCPUSuspense = false;
export const enableUseMemoCacheHook = false;
export const enableUseEffectEventHook = false;
export const enableClientRenderFallbackOnTextMismatch = true;
export const enableComponentStackLocations = false;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = true;
export const enableGetInspectorDataForInstanceInProduction = false;
export const enableRenderableContext = false;

export const enableRetryLaneExpiration = false;
export const retryLaneExpirationMs = 5000;
export const syncLaneExpirationMs = 250;
export const transitionLaneExpirationMs = 5000;

export const enableUseRefAccessWarning = false;

export const disableSchedulerTimeoutInWorkLoop = false;
export const enableLazyContextPropagation = false;
export const enableLegacyHidden = false;
export const forceConcurrentByDefaultForTesting = false;
export const enableUnifiedSyncLane = true;
export const allowConcurrentByDefault = false;
export const enableCustomElementPropertySupport = true;
export const enableNewBooleanProps = true;

export const consoleManagedByDevToolsDuringStrictMode = false;

export const enableTransitionTracing = false;

export const useModernStrictMode = false;
export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
export const enableFizzExternalRuntime = true;
export const enableDeferRootSchedulingToMicrotask = false;
export const enableInfiniteRenderLoopDetection = false;

export const enableAsyncActions = false;

export const alwaysThrottleDisappearingFallbacks = false;
export const alwaysThrottleRetries = false;

export const passChildrenWhenCloningPersistedNodes = false;
export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__;
export const disableClientCache = true;

export const enableServerComponentKeys = true;
export const enableServerComponentLogs = true;

// TODO: Should turn this on in next "major" RN release.
export const enableRefAsProp = false;
export const disableStringRefs = false;

export const enableReactTestRendererWarning = false;

export const enableBigIntSupport = false;
export const disableLegacyMode = false;
// Profiling Only
export const enableProfilerTimer = __PROFILE__;
export const enableProfilerCommitHooks = __PROFILE__;
export const enableProfilerNestedUpdatePhase = __PROFILE__;
export const enableUpdaterTracking = __PROFILE__;

// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
132 changes: 107 additions & 25 deletions scripts/flags/flags.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const argv = yargs
'www-modern',
'rn',
'rn-fb',
'rn-next',
'canary',
'next',
'experimental',
Expand All @@ -55,17 +56,18 @@ const argv = yargs
'www-modern',
'rn',
'rn-fb',
'rn-next',
'canary',
'next',
'experimental',
],
},
}).argv;

// Load ReactNativeFeatureFlags with __NEXT_MAJOR__ replace with 'next'.
// Load ReactFeatureFlags with __NEXT_MAJOR__ replaced with 'next'.
// We need to do string replace, since the __NEXT_MAJOR__ is assigned to __EXPERIMENTAL__.
function getReactNativeFeatureFlagsMajor() {
const virtualName = 'ReactNativeFeatureFlagsMajor.js';
function getReactFeatureFlagsMajor() {
const virtualName = 'ReactFeatureFlagsMajor.js';
const file = fs.readFileSync(
path.join(__dirname, '../../packages/shared/ReactFeatureFlags.js'),
'utf8'
Expand All @@ -89,6 +91,41 @@ function getReactNativeFeatureFlagsMajor() {
return m.exports;
}

// Load RN ReactFeatureFlags with __NEXT_RN_MAJOR__ replaced with 'next'.
// We need to do string replace, since the __NEXT_RN_MAJOR__ is assigned to false.
function getReactNativeFeatureFlagsMajor() {
const virtualName = 'ReactNativeFeatureFlagsMajor.js';
const file = fs.readFileSync(
path.join(
__dirname,
'../../packages/shared/forks/ReactFeatureFlags.native-oss.js'
),
'utf8'
);
const fileContent = transformSync(
file
.replace(
'const __NEXT_RN_MAJOR__ = true;',
'const __NEXT_RN_MAJOR__ = "next";'
)
.replace(
'const __TODO_NEXT_RN_MAJOR__ = false;',
'const __TODO_NEXT_RN_MAJOR__ = "next-todo";'
),
{
plugins: ['@babel/plugin-transform-modules-commonjs'],
}
).code;

const parent = module.parent;
const m = new Module(virtualName, parent);
m.filename = virtualName;

m._compile(fileContent, virtualName);

return m.exports;
}

// The RN and www Feature flag files import files that don't exist.
// Mock the imports with the dynamic flag values.
function mockDynamicallyFeatureFlags() {
Expand Down Expand Up @@ -119,15 +156,14 @@ mockDynamicallyFeatureFlags();
const ReactFeatureFlags = require('../../packages/shared/ReactFeatureFlags.js');
const ReactFeatureFlagsWWW = require('../../packages/shared/forks/ReactFeatureFlags.www.js');
const ReactFeatureFlagsNativeFB = require('../../packages/shared/forks/ReactFeatureFlags.native-fb.js');
const ReactFeatureFlagsNativeOSS = require('../../packages/shared/forks/ReactFeatureFlags.native-oss.js');
const ReactFeatureFlagsMajor = getReactNativeFeatureFlagsMajor();
const ReactFeatureFlagsMajor = getReactFeatureFlagsMajor();
const ReactNativeFeatureFlagsMajor = getReactNativeFeatureFlagsMajor();

const allFlagsUniqueFlags = Array.from(
new Set([
...Object.keys(ReactFeatureFlags),
...Object.keys(ReactFeatureFlagsWWW),
...Object.keys(ReactFeatureFlagsNativeFB),
...Object.keys(ReactFeatureFlagsNativeOSS),
])
).sort();

Expand Down Expand Up @@ -223,11 +259,37 @@ function getWWWClassicFlagValue(flag) {
}
}

function getRNNextMajorFlagValue(flag) {
const value = ReactNativeFeatureFlagsMajor[flag];
if (value === true || value === 'next') {
return '✅';
} else if (value === 'next-todo') {
return '📋';
} else if (value === false || value === 'experimental') {
return '❌';
} else if (value === 'profile') {
return '📊';
} else if (value === 'dev') {
return '💻';
} else if (value === 'gk') {
return '🧪';
} else if (typeof value === 'number') {
return value;
} else {
throw new Error(`Unexpected RN OSS value ${value} for flag ${flag}`);
}
}

function getRNOSSFlagValue(flag) {
const value = ReactFeatureFlagsNativeOSS[flag];
const value = ReactNativeFeatureFlagsMajor[flag];
if (value === true) {
return '✅';
} else if (value === false || value === 'experimental' || value === 'next') {
} else if (
value === false ||
value === 'experimental' ||
value === 'next' ||
value === 'next-todo'
) {
return '❌';
} else if (value === 'profile') {
return '📊';
Expand Down Expand Up @@ -271,6 +333,8 @@ function argToHeader(arg) {
return 'RN OSS';
case 'rn-fb':
return 'RN FB';
case 'rn-next':
return 'RN Next Major';
case 'canary':
return 'OSS Canary';
case 'next':
Expand All @@ -282,20 +346,28 @@ function argToHeader(arg) {
}
}

const FLAG_CONFIG = {
'OSS Next Major': getNextMajorFlagValue,
'OSS Canary': getOSSCanaryFlagValue,
'OSS Experimental': getOSSExperimentalFlagValue,
'WWW Classic': getWWWClassicFlagValue,
'WWW Modern': getWWWModernFlagValue,
'RN FB': getRNFBFlagValue,
'RN OSS': getRNOSSFlagValue,
'RN Next Major': getRNNextMajorFlagValue,
};

const FLAG_COLUMNS = Object.keys(FLAG_CONFIG);

// Build the table with the value for each flag.
const isDiff = argv.diff != null && argv.diff.length > 1;
const table = {};
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const flag of allFlagsUniqueFlags) {
const values = {
'OSS Next Major': getNextMajorFlagValue(flag),
'OSS Canary': getOSSCanaryFlagValue(flag),
'OSS Experimental': getOSSExperimentalFlagValue(flag),
'WWW Classic': getWWWClassicFlagValue(flag),
'WWW Modern': getWWWModernFlagValue(flag),
'RN FB': getRNFBFlagValue(flag),
'RN OSS': getRNOSSFlagValue(flag),
};
const values = FLAG_COLUMNS.reduce((acc, key) => {
acc[key] = FLAG_CONFIG[key](flag);
return acc;
}, {});

if (!isDiff) {
table[flag] = values;
Expand Down Expand Up @@ -330,17 +402,18 @@ if (isDiff || argv.sort) {
}

if (argv.csv) {
const csvHeader =
'Flag name, WWW Classic, RN FB, OSS Canary, OSS Experimental, WWW Modern, RN OSS\n';
const csvRows = Object.keys(sorted).map(flag => {
const row = sorted[flag];
return `${flag}, ${row['WWW Classic']}, ${row['RN FB']}, ${row['OSS Canary']}, ${row['OSS Experimental']}, ${row['WWW Modern']}, ${row['RN OSS']}`;
});
fs.writeFile('./flags.csv', csvHeader + csvRows.join('\n'), function (err) {
const csvRows = [
`Flag name, ${FLAG_COLUMNS.join(', ')}`,
...Object.keys(table).map(flag => {
const row = sorted[flag];
return `${flag}, ${FLAG_COLUMNS.map(col => row[col]).join(', ')}`;
}),
];
fs.writeFile('./flags.csv', csvRows.join('\n'), function (err) {
if (err) {
return console.log(err);
}
console.log('The file was saved!');
console.log('The file was saved to ./flags.csv');
});
}

Expand All @@ -354,3 +427,12 @@ Object.keys(sorted).forEach(key => {

// print table with formatting
console.table(padded);
console.log(`
Legend:
✅ On
❌ Off
💻 DEV
📋 TODO
📊 Profiling
🧪 Experiment
`);

0 comments on commit fabd6d3

Please sign in to comment.