Add prerelease staging API for iOS (#49650)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49650

- Added a new template to React Native's Feature Flag's script for Canary and Experimental prerelease stages iOS
- Overload initWithDelegate to add optional releaseLevel parameter
- Add Obj-C enum for Release Level
- Use static variables to keep the context on whether a ReactNative Factory has been created and with which flags it has been created.
- Crash in case we try to create multiple factories with different feature flags.
- Creating multiple factories with the same feature flags is allowed

Changelog: [iOS] [Added] - On `RCTReactNativeFactory` add `initWithDelegate` overload with argument to specify release level for an application

Reviewed By: cipolleschi

Differential Revision: D70106210

fbshipit-source-id: 14fe4c8571621a75a7064f1ffd02a07270cea43a
This commit is contained in:
Jorge Cabiedes Acosta
2025-03-03 09:52:26 -08:00
committed by Facebook GitHub Bot
parent 5f0c857c0e
commit df282a0538
7 changed files with 402 additions and 26 deletions
@@ -27,6 +27,8 @@
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable };
@protocol RCTReactNativeFactoryDelegate <
RCTBridgeDelegate,
RCTUIConfiguratorProtocol,
@@ -86,6 +88,8 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithDelegate:(id<RCTReactNativeFactoryDelegate>)delegate;
- (instancetype)initWithDelegate:(id<RCTReactNativeFactoryDelegate>)delegate releaseLevel:(RCTReleaseLevel)releaseLevel;
- (void)startReactNativeWithModuleName:(NSString *)moduleName inWindow:(UIWindow *_Nullable)window;
- (void)startReactNativeWithModuleName:(NSString *)moduleName
@@ -13,8 +13,9 @@
#import <React/RCTUtils.h>
#import <ReactCommon/RCTHost.h>
#import <objc/runtime.h>
#import <react/featureflags/ReactNativeFeatureFlags.h>
#import <react/featureflags/ReactNativeFeatureFlagsDefaults.h>
#import <react/featureflags/ReactNativeFeatureFlagsOverridesOSSCanary.h>
#import <react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h>
#import <react/featureflags/ReactNativeFeatureFlagsOverridesOSSStable.h>
#import <react/renderer/graphics/ColorComponents.h>
#import "RCTAppSetupUtils.h"
@@ -41,10 +42,15 @@ using namespace facebook::react;
@implementation RCTReactNativeFactory
- (instancetype)initWithDelegate:(id<RCTReactNativeFactoryDelegate>)delegate
{
return [self initWithDelegate:delegate releaseLevel:Stable];
}
- (instancetype)initWithDelegate:(id<RCTReactNativeFactoryDelegate>)delegate releaseLevel:(RCTReleaseLevel)releaseLevel
{
if (self = [super init]) {
self.delegate = delegate;
[self _setUpFeatureFlags];
[self _setUpFeatureFlags:releaseLevel];
auto newArchEnabled = [self newArchEnabled];
auto fabricEnabled = [self fabricEnabled];
@@ -287,32 +293,36 @@ using namespace facebook::react;
#pragma mark - Feature Flags
class RCTAppDelegateBridgelessFeatureFlags : public ReactNativeFeatureFlagsDefaults {
public:
bool enableBridgelessArchitecture() override
{
return true;
}
bool enableFabricRenderer() override
{
return true;
}
bool useTurboModules() override
{
return true;
}
bool useNativeViewConfigsInBridgelessMode() override
{
return true;
}
};
- (void)_setUpFeatureFlags
- (void)_setUpFeatureFlags:(RCTReleaseLevel)releaseLevel
{
static BOOL initialized = NO;
static RCTReleaseLevel chosenReleaseLevel;
NSLog(@"_setUpFeatureFlags called with release level %li", releaseLevel);
if (!initialized) {
chosenReleaseLevel = releaseLevel;
initialized = YES;
} else if (chosenReleaseLevel != releaseLevel) {
[NSException
raise:@"RCTReactNativeFactory::_setUpFeatureFlags releaseLevel mismatch between React Native instances"
format:@"The releaseLevel (%li) of the new instance does not match the previous instance's releaseLevel (%li)",
releaseLevel,
chosenReleaseLevel];
}
static dispatch_once_t setupFeatureFlagsToken;
dispatch_once(&setupFeatureFlagsToken, ^{
if ([self bridgelessEnabled]) {
ReactNativeFeatureFlags::override(std::make_unique<RCTAppDelegateBridgelessFeatureFlags>());
switch (releaseLevel) {
case Stable:
if ([self bridgelessEnabled]) {
ReactNativeFeatureFlags::override(std::make_unique<ReactNativeFeatureFlagsOverridesOSSStable>());
}
break;
case Canary:
ReactNativeFeatureFlags::override(std::make_unique<ReactNativeFeatureFlagsOverridesOSSCanary>());
break;
case Experimental:
ReactNativeFeatureFlags::override(std::make_unique<ReactNativeFeatureFlagsOverridesOSSExperimental>());
break;
}
});
}
@@ -0,0 +1,129 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<cdbf77da1fa54e05f35bfe140addf39b>>
*/
/**
* IMPORTANT: Do NOT modify this file directly.
*
* To change the definition of the flags, edit
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js.
*
* To regenerate this code, run the following script from the repo root:
* yarn featureflags --update
*/
#pragma once
#include <react/featureflags/ReactNativeFeatureFlagsOverridesOSSStable.h>
namespace facebook::react {
class ReactNativeFeatureFlagsOverridesOSSCanary : public ReactNativeFeatureFlagsOverridesOSSStable {
public:
ReactNativeFeatureFlagsOverridesOSSCanary() = default;
bool enableBridgelessArchitecture() override {
return true;
}
bool enableFabricRenderer() override {
return true;
}
bool useFabricInterop() override {
return true;
}
bool useNativeViewConfigsInBridgelessMode() override {
return true;
}
bool useTurboModuleInterop() override {
return true;
}
bool useTurboModules() override {
return true;
}
};
} // namespace facebook::react
@@ -0,0 +1,117 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<c19b9e1f879cc986fe114829c3a79df5>>
*/
/**
* IMPORTANT: Do NOT modify this file directly.
*
* To change the definition of the flags, edit
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js.
*
* To regenerate this code, run the following script from the repo root:
* yarn featureflags --update
*/
#pragma once
#include <react/featureflags/ReactNativeFeatureFlagsOverridesOSSCanary.h>
namespace facebook::react {
class ReactNativeFeatureFlagsOverridesOSSExperimental : public ReactNativeFeatureFlagsOverridesOSSCanary {
public:
ReactNativeFeatureFlagsOverridesOSSExperimental() = default;
};
} // namespace facebook::react
@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/featureflags/ReactNativeFeatureFlagsDefaults.h>
namespace facebook::react {
class ReactNativeFeatureFlagsOverridesOSSStable
: public ReactNativeFeatureFlagsDefaults {
public:
bool enableBridgelessArchitecture() override {
return true;
}
bool enableFabricRenderer() override {
return true;
}
bool useTurboModules() override {
return true;
}
bool useNativeViewConfigsInBridgelessMode() override {
return true;
}
};
} // namespace facebook::react
@@ -16,6 +16,7 @@ import ReactNativeFeatureFlagsAccessorCPP from './templates/common-cxx/ReactNati
import ReactNativeFeatureFlagsAccessorH from './templates/common-cxx/ReactNativeFeatureFlagsAccessor.h-template';
import ReactNativeFeatureFlagsDefaultsH from './templates/common-cxx/ReactNativeFeatureFlagsDefaults.h-template';
import ReactNativeFeatureFlagsDynamicProviderH from './templates/common-cxx/ReactNativeFeatureFlagsDynamicProvider.h-template';
import ReactNativeFeatureFlagsOverrides from './templates/common-cxx/ReactNativeFeatureFlagsOverridesOSS_Stage_.h-template';
import ReactNativeFeatureFlagsProviderH from './templates/common-cxx/ReactNativeFeatureFlagsProvider.h-template';
import path from 'path';
@@ -35,6 +36,15 @@ export default function generateCommonCxxModules(
ReactNativeFeatureFlagsAccessorCPP(featureFlagDefinitions),
[path.join(commonCxxPath, 'ReactNativeFeatureFlagsDefaults.h')]:
ReactNativeFeatureFlagsDefaultsH(featureFlagDefinitions),
[path.join(
commonCxxPath,
'ReactNativeFeatureFlagsOverridesOSSExperimental.h',
)]: ReactNativeFeatureFlagsOverrides(
featureFlagDefinitions,
'experimental',
),
[path.join(commonCxxPath, 'ReactNativeFeatureFlagsOverridesOSSCanary.h')]:
ReactNativeFeatureFlagsOverrides(featureFlagDefinitions, 'canary'),
[path.join(commonCxxPath, 'ReactNativeFeatureFlagsProvider.h')]:
ReactNativeFeatureFlagsProviderH(featureFlagDefinitions),
[path.join(commonCxxPath, 'ReactNativeFeatureFlagsDynamicProvider.h')]:
@@ -0,0 +1,76 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
import type {FeatureFlagDefinitions, OSSReleaseStageValue} from '../../types';
import {DO_NOT_MODIFY_COMMENT, getCxxTypeFromDefaultValue} from '../../utils';
import signedsource from 'signedsource';
function getClassName(ossReleaseStage: OSSReleaseStageValue): string {
if (ossReleaseStage === 'experimental') {
return 'ReactNativeFeatureFlagsOverridesOSSExperimental';
} else if (ossReleaseStage === 'canary') {
return 'ReactNativeFeatureFlagsOverridesOSSCanary';
}
return 'ReactNativeFeatureFlagsOverridesOSSStable';
}
function getParentClassName(ossReleaseStage: OSSReleaseStageValue): string {
if (ossReleaseStage === 'experimental') {
return 'ReactNativeFeatureFlagsOverridesOSSCanary';
} else if (ossReleaseStage === 'canary') {
return 'ReactNativeFeatureFlagsOverridesOSSStable';
}
return 'ReactNativeFeatureFlagsProvider';
}
export default function (
definitions: FeatureFlagDefinitions,
ossReleaseStage: OSSReleaseStageValue,
): string {
return signedsource.signFile(`/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* ${signedsource.getSigningToken()}
*/
${DO_NOT_MODIFY_COMMENT}
#pragma once
#include <react/featureflags/${getParentClassName(ossReleaseStage)}.h>
namespace facebook::react {
class ${getClassName(ossReleaseStage)} : public ${getParentClassName(ossReleaseStage)} {
public:
${getClassName(ossReleaseStage)}() = default;
${Object.entries(definitions.common)
.map(([flagName, flagConfig]) => {
if (flagConfig.ossReleaseStage === ossReleaseStage) {
return ` ${getCxxTypeFromDefaultValue(
flagConfig.metadata.expectedReleaseValue,
)} ${flagName}() override {
return ${JSON.stringify(flagConfig.metadata.expectedReleaseValue)};
}`;
}
})
.join('\n\n')}
};
} // namespace facebook::react
`);
}