mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Implement new feature flag system (#42430)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/42430 This PR creates a new internal feature flags system for React Native. This is only meant to be used internally within the framework, but we might expose it externally in some form in the future to allow customizing specific feature flags in frameworks and applications. Features: * 2 types of flags: * Common: can be overridden from native and are accessible from all layers of the stack (Objective-C/Swift, Java/Kotlin, C++ and JavaScript). * JS-only: flags that can only be defined and accessed from JS (to allow things like hot reloading without a native build). * 1 source of truth for each flag. * Feature flags are application/process scoped (using C++ singletons). See the `README.md` file in this PR for additional information. This also adds modifies `run-ci-javascript-tests` to run a new check to make sure that the generate files are in sync with the JSON file that contains the definitions. Changelog: [internal] Reviewed By: huntie Differential Revision: D52806730 fbshipit-source-id: 0ba95803f61ec2f05266ee535921321bf6d3dc6a
This commit is contained in:
committed by
Facebook GitHub Bot
parent
dc2ce9e66e
commit
705c675d51
@@ -21,6 +21,8 @@
|
||||
"flow": "flow",
|
||||
"format-check": "prettier --list-different \"./**/*.{js,md,yml,ts,tsx}\"",
|
||||
"format": "npm run prettier && npm run clang-format",
|
||||
"featureflags-check": "cd packages/react-native && yarn featureflags-check",
|
||||
"featureflags-update": "cd packages/react-native && yarn featureflags-update",
|
||||
"lint-ci": "./scripts/circleci/analyze_code.sh && yarn shellcheck",
|
||||
"lint-java": "node ./scripts/lint-java.js",
|
||||
"lint": "eslint .",
|
||||
|
||||
@@ -129,6 +129,7 @@ Pod::Spec.new do |s|
|
||||
s.dependency "React-jsi"
|
||||
s.dependency "React-jsiexecutor"
|
||||
s.dependency "React-utils"
|
||||
s.dependency "React-featureflags"
|
||||
s.dependency "SocketRocket", socket_rocket_version
|
||||
s.dependency "React-runtimescheduler"
|
||||
s.dependency "Yoga"
|
||||
|
||||
@@ -526,6 +526,7 @@ android {
|
||||
"runtimeexecutor",
|
||||
"react_codegen_rncore",
|
||||
"react_debug",
|
||||
"react_featureflags",
|
||||
"react_utils",
|
||||
"react_render_componentregistry",
|
||||
"react_newarchdefaults",
|
||||
@@ -540,6 +541,7 @@ android {
|
||||
"jsi",
|
||||
"glog",
|
||||
"fabricjni",
|
||||
"featureflagsjni",
|
||||
"react_render_mapbuffer",
|
||||
"yoga",
|
||||
"folly_runtime",
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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<<c201b2b577ab4aa4bf6071d6b99b43c9>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
package com.facebook.react.internal.featureflags
|
||||
|
||||
/**
|
||||
* This object provides access to internal React Native feature flags.
|
||||
*
|
||||
* All the methods are thread-safe if you handle `override` correctly.
|
||||
*/
|
||||
object ReactNativeFeatureFlags {
|
||||
private var accessorProvider: () -> ReactNativeFeatureFlagsAccessor = { ReactNativeFeatureFlagsCxxAccessor() }
|
||||
private var accessor: ReactNativeFeatureFlagsAccessor = accessorProvider()
|
||||
|
||||
/**
|
||||
* Common flag for testing. Do NOT modify.
|
||||
*/
|
||||
fun commonTestFlag() = accessor.commonTestFlag()
|
||||
|
||||
/**
|
||||
* Overrides the feature flags with the ones provided by the given provider
|
||||
* (generally one that extends `ReactNativeFeatureFlagsDefaults`).
|
||||
*
|
||||
* This method must be called before you initialize the React Native runtime.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```
|
||||
* ReactNativeFeatureFlags.override(object : ReactNativeFeatureFlagsDefaults() {
|
||||
* override fun someFlag(): Boolean = true // or a dynamic value
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
fun override(provider: ReactNativeFeatureFlagsProvider) = accessor.override(provider)
|
||||
|
||||
/**
|
||||
* Removes the overridden feature flags and makes the API return default
|
||||
* values again.
|
||||
*
|
||||
* This should only be called if you destroy the React Native runtime and
|
||||
* need to create a new one with different overrides. In that case,
|
||||
* call `dangerouslyReset` after destroying the runtime and `override`
|
||||
* again before initializing the new one.
|
||||
*/
|
||||
fun dangerouslyReset() {
|
||||
// This is necessary when the accessor interops with C++ and we need to
|
||||
// remove the overrides set there.
|
||||
accessor.dangerouslyReset()
|
||||
|
||||
// This discards the cached values and the overrides set in the JVM.
|
||||
accessor = accessorProvider()
|
||||
}
|
||||
|
||||
/**
|
||||
* This is just used to replace the default ReactNativeFeatureFlagsCxxAccessor
|
||||
* that uses JNI with a version that doesn't, to simplify testing.
|
||||
*/
|
||||
internal fun setAccessorProvider(newAccessorProvider: () -> ReactNativeFeatureFlagsAccessor) {
|
||||
accessorProvider = newAccessorProvider
|
||||
accessor = accessorProvider()
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.internal.featureflags
|
||||
|
||||
interface ReactNativeFeatureFlagsAccessor : ReactNativeFeatureFlagsProvider {
|
||||
fun override(provider: ReactNativeFeatureFlagsProvider)
|
||||
|
||||
fun dangerouslyReset()
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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<<961f1fb0a7ad802a492437f15b1f2dcb>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
package com.facebook.react.internal.featureflags
|
||||
|
||||
class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccessor {
|
||||
private var commonTestFlagCache: Boolean? = null
|
||||
|
||||
override fun commonTestFlag(): Boolean {
|
||||
var cached = commonTestFlagCache
|
||||
if (cached == null) {
|
||||
cached = ReactNativeFeatureFlagsCxxInterop.commonTestFlag()
|
||||
commonTestFlagCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun override(provider: ReactNativeFeatureFlagsProvider) =
|
||||
ReactNativeFeatureFlagsCxxInterop.override(provider as Any)
|
||||
|
||||
override fun dangerouslyReset() = ReactNativeFeatureFlagsCxxInterop.dangerouslyReset()
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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<<c2e41ac8c3d9471b4cb79f6147cc2bf2>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
package com.facebook.react.internal.featureflags
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip
|
||||
import com.facebook.soloader.SoLoader
|
||||
|
||||
@DoNotStrip
|
||||
object ReactNativeFeatureFlagsCxxInterop {
|
||||
init {
|
||||
SoLoader.loadLibrary("reactfeatureflagsjni")
|
||||
}
|
||||
|
||||
@DoNotStrip @JvmStatic external fun commonTestFlag(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic external fun override(provider: Any)
|
||||
|
||||
@DoNotStrip @JvmStatic external fun dangerouslyReset()
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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<<aa1eaeee7b715e5b1d3cbcf9b7a7062e>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
package com.facebook.react.internal.featureflags
|
||||
|
||||
open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvider {
|
||||
// We could use JNI to get the defaults from C++,
|
||||
// but that is more expensive than just duplicating the defaults here.
|
||||
|
||||
override fun commonTestFlag(): Boolean = false
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.internal.featureflags
|
||||
|
||||
object ReactNativeFeatureFlagsForTests {
|
||||
fun setUp() {
|
||||
ReactNativeFeatureFlags.setAccessorProvider({ ReactNativeFeatureFlagsLocalAccessor() })
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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<<8bbd7e8cc2c50cfbf44ba6671d095f23>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
package com.facebook.react.internal.featureflags
|
||||
|
||||
class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAccessor {
|
||||
private var currentProvider: ReactNativeFeatureFlagsProvider = ReactNativeFeatureFlagsDefaults()
|
||||
|
||||
private val accessedFeatureFlags = mutableSetOf<String>()
|
||||
|
||||
private var commonTestFlagCache: Boolean? = null
|
||||
|
||||
override fun commonTestFlag(): Boolean {
|
||||
var cached = commonTestFlagCache
|
||||
if (cached == null) {
|
||||
cached = currentProvider.commonTestFlag()
|
||||
accessedFeatureFlags.add("commonTestFlag")
|
||||
commonTestFlagCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun override(provider: ReactNativeFeatureFlagsProvider) {
|
||||
if (accessedFeatureFlags.isNotEmpty()) {
|
||||
val accessedFeatureFlagsStr = accessedFeatureFlags.joinToString(separator = ", ") { it }
|
||||
throw IllegalStateException(
|
||||
"Feature flags were accessed before being overridden: $accessedFeatureFlagsStr")
|
||||
}
|
||||
currentProvider = provider
|
||||
}
|
||||
|
||||
override fun dangerouslyReset() {
|
||||
// We don't need to do anything here because `ReactNativeFeatureFlags` will
|
||||
// just create a new instance of this class.
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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<<babb2b7b32c88b1767ac53ae97dddf10>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
package com.facebook.react.internal.featureflags
|
||||
|
||||
interface ReactNativeFeatureFlagsProvider {
|
||||
fun commonTestFlag(): Boolean
|
||||
}
|
||||
@@ -70,6 +70,7 @@ add_react_common_subdir(hermes/inspector-modern)
|
||||
add_react_common_subdir(react/renderer/runtimescheduler)
|
||||
add_react_common_subdir(react/debug)
|
||||
add_react_common_subdir(react/config)
|
||||
add_react_common_subdir(react/featureflags)
|
||||
add_react_common_subdir(react/renderer/animations)
|
||||
add_react_common_subdir(react/renderer/attributedstring)
|
||||
add_react_common_subdir(react/renderer/componentregistry)
|
||||
@@ -118,6 +119,7 @@ add_react_android_subdir(src/main/jni/react/uimanager)
|
||||
add_react_android_subdir(src/main/jni/react/mapbuffer)
|
||||
add_react_android_subdir(src/main/jni/react/reactnativeblob)
|
||||
add_react_android_subdir(src/main/jni/react/fabric)
|
||||
add_react_android_subdir(src/main/jni/react/featureflags)
|
||||
add_react_android_subdir(src/main/jni/react/newarchdefaults)
|
||||
add_react_android_subdir(src/main/jni/react/hermes/reactexecutor)
|
||||
add_react_android_subdir(src/main/jni/react/hermes/instrumentation/)
|
||||
|
||||
@@ -26,6 +26,7 @@ target_link_libraries(
|
||||
mapbufferjni
|
||||
react_codegen_rncore
|
||||
react_debug
|
||||
react_featureflags
|
||||
react_render_animations
|
||||
react_render_attributedstring
|
||||
react_render_componentregistry
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# 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.
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
file(GLOB featureflagsjni_SRCS CONFIGURE_DEPENDS *.cpp)
|
||||
|
||||
add_library(
|
||||
featureflagsjni
|
||||
SHARED
|
||||
${featureflagsjni_SRCS}
|
||||
)
|
||||
|
||||
target_include_directories(featureflagsjni PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
target_link_libraries(
|
||||
featureflagsjni
|
||||
fb
|
||||
fbjni
|
||||
react_featureflags
|
||||
reactnativejni
|
||||
)
|
||||
|
||||
target_compile_options(
|
||||
featureflagsjni
|
||||
PRIVATE
|
||||
-DLOG_TAG=\"ReactNative\"
|
||||
-fexceptions
|
||||
-frtti
|
||||
-std=c++20
|
||||
-Wall
|
||||
)
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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<<ad6d2d1c24334995ba197c7cc0a74dc9>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
#include "JReactNativeFeatureFlagsCxxInterop.h"
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlagsProviderHolder.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
bool JReactNativeFeatureFlagsCxxInterop::commonTestFlag(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
|
||||
return ReactNativeFeatureFlags::commonTestFlag();
|
||||
}
|
||||
|
||||
void JReactNativeFeatureFlagsCxxInterop::override(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/,
|
||||
jni::alias_ref<jobject> provider) {
|
||||
ReactNativeFeatureFlags::override(
|
||||
std::make_unique<ReactNativeFeatureFlagsProviderHolder>(provider));
|
||||
}
|
||||
|
||||
void JReactNativeFeatureFlagsCxxInterop::dangerouslyReset(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
|
||||
ReactNativeFeatureFlags::dangerouslyReset();
|
||||
}
|
||||
|
||||
void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
|
||||
javaClassLocal()->registerNatives({
|
||||
makeNativeMethod(
|
||||
"override", JReactNativeFeatureFlagsCxxInterop::override),
|
||||
makeNativeMethod("dangerouslyReset", JReactNativeFeatureFlagsCxxInterop::dangerouslyReset),
|
||||
makeNativeMethod(
|
||||
"commonTestFlag",
|
||||
JReactNativeFeatureFlagsCxxInterop::commonTestFlag),
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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<<c759720f6c5f9b884f3eee8f3c104526>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fbjni/fbjni.h>
|
||||
#include <jni.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class JReactNativeFeatureFlagsCxxInterop
|
||||
: public jni::JavaClass<JReactNativeFeatureFlagsCxxInterop> {
|
||||
public:
|
||||
constexpr static auto kJavaDescriptor =
|
||||
"Lcom/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop;";
|
||||
|
||||
static bool commonTestFlag(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
static void override(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>,
|
||||
jni::alias_ref<jobject> provider);
|
||||
|
||||
static void dangerouslyReset(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
static void registerNatives();
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <fbjni/fbjni.h>
|
||||
|
||||
#include "JReactNativeFeatureFlagsCxxInterop.h"
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
|
||||
return facebook::jni::initialize(vm, [] {
|
||||
facebook::react::JReactNativeFeatureFlagsCxxInterop::registerNatives();
|
||||
});
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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<<7a019bd967a22f93cd9e2e0ddb5201e3>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
#include "ReactNativeFeatureFlagsProviderHolder.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
bool ReactNativeFeatureFlagsProviderHolder::commonTestFlag() {
|
||||
static const auto method =
|
||||
facebook::jni::findClassStatic(
|
||||
"com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider")
|
||||
->getMethod<jboolean()>("commonTestFlag");
|
||||
return method(javaProvider_);
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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<<9a2d162cbd83f3b5122d0eb86f6f9177>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fbjni/fbjni.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
/**
|
||||
* Implementation of ReactNativeFeatureFlagsProvider that wraps a
|
||||
* ReactNativeFeatureFlagsProvider Java object.
|
||||
*/
|
||||
class ReactNativeFeatureFlagsProviderHolder
|
||||
: public ReactNativeFeatureFlagsProvider {
|
||||
public:
|
||||
explicit ReactNativeFeatureFlagsProviderHolder(
|
||||
jni::alias_ref<jobject> javaProvider)
|
||||
: javaProvider_(make_global(javaProvider)){};
|
||||
|
||||
bool commonTestFlag() override;
|
||||
|
||||
private:
|
||||
jni::global_ref<jobject> javaProvider_;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
@@ -17,6 +17,7 @@ target_include_directories(react_newarchdefaults PUBLIC .)
|
||||
target_link_libraries(react_newarchdefaults
|
||||
fbjni
|
||||
fabricjni
|
||||
featureflagsjni
|
||||
react_nativemodule_core
|
||||
react_codegen_rncore
|
||||
react_cxxreactpackage
|
||||
|
||||
@@ -23,6 +23,7 @@ target_include_directories(rninstance PUBLIC .)
|
||||
target_link_libraries(
|
||||
rninstance
|
||||
fabricjni
|
||||
featureflagsjni
|
||||
turbomodulejsijni
|
||||
fb
|
||||
jsi
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# 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.
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
set(CMAKE_VERBOSE_MAKEFILE on)
|
||||
|
||||
add_compile_options(
|
||||
-fexceptions
|
||||
-frtti
|
||||
-std=c++20
|
||||
-Wall
|
||||
-Wpedantic
|
||||
-DLOG_TAG=\"ReactNative\")
|
||||
|
||||
file(GLOB react_featureflags_SRC CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(react_featureflags SHARED ${react_featureflags_SRC})
|
||||
|
||||
target_include_directories(react_featureflags PUBLIC ${REACT_COMMON_DIR})
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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.
|
||||
|
||||
require "json"
|
||||
|
||||
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "package.json")))
|
||||
version = package['version']
|
||||
|
||||
source = { :git => 'https://github.com/facebook/react-native.git' }
|
||||
if version == '1000.0.0'
|
||||
# This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in.
|
||||
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
|
||||
else
|
||||
source[:tag] = "v#{version}"
|
||||
end
|
||||
|
||||
header_search_paths = []
|
||||
|
||||
if ENV['USE_FRAMEWORKS']
|
||||
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../..\"" # this is needed to allow the feature flags access its own files
|
||||
end
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "React-featureflags"
|
||||
s.version = version
|
||||
s.summary = "React Native internal feature flags"
|
||||
s.homepage = "https://reactnative.dev/"
|
||||
s.license = package["license"]
|
||||
s.author = "Meta Platforms, Inc. and its affiliates"
|
||||
s.platforms = min_supported_versions
|
||||
s.source = source
|
||||
s.source_files = "*.{cpp,h}"
|
||||
s.header_dir = "react/featureflags"
|
||||
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
|
||||
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
|
||||
"DEFINES_MODULE" => "YES" }
|
||||
|
||||
if ENV['USE_FRAMEWORKS']
|
||||
s.module_name = "React_featureflags"
|
||||
s.header_mappings_dir = "../.."
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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<<85960128b845159e7de70d0e85910dd9>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
#include "ReactNativeFeatureFlags.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
bool ReactNativeFeatureFlags::commonTestFlag() {
|
||||
return getAccessor().commonTestFlag();
|
||||
}
|
||||
|
||||
void ReactNativeFeatureFlags::override(
|
||||
std::unique_ptr<ReactNativeFeatureFlagsProvider> provider) {
|
||||
getAccessor().override(std::move(provider));
|
||||
}
|
||||
|
||||
void ReactNativeFeatureFlags::dangerouslyReset() {
|
||||
getAccessor(true);
|
||||
}
|
||||
|
||||
ReactNativeFeatureFlagsAccessor& ReactNativeFeatureFlags::getAccessor(
|
||||
bool reset) {
|
||||
static std::unique_ptr<ReactNativeFeatureFlagsAccessor> accessor;
|
||||
if (accessor == nullptr || reset) {
|
||||
accessor = std::make_unique<ReactNativeFeatureFlagsAccessor>();
|
||||
}
|
||||
return *accessor;
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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<<a4d59ac4d6a845349ffff6ba463fdf80>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <react/featureflags/ReactNativeFeatureFlagsAccessor.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlagsProvider.h>
|
||||
#include <memory>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
/**
|
||||
* This class provides access to internal React Native feature flags.
|
||||
*
|
||||
* All the methods are thread-safe if you handle `override` correctly.
|
||||
*/
|
||||
class ReactNativeFeatureFlags {
|
||||
public:
|
||||
/**
|
||||
* Common flag for testing. Do NOT modify.
|
||||
*/
|
||||
static bool commonTestFlag();
|
||||
|
||||
/**
|
||||
* Overrides the feature flags with the ones provided by the given provider
|
||||
* (generally one that extends `ReactNativeFeatureFlagsDefaults`).
|
||||
*
|
||||
* This method must be called before you initialize the React Native runtime.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```
|
||||
* class MyReactNativeFeatureFlags : public ReactNativeFeatureFlagsDefaults {
|
||||
* public:
|
||||
* bool someFlag() override;
|
||||
* };
|
||||
*
|
||||
* ReactNativeFeatureFlags.override(
|
||||
* std::make_unique<MyReactNativeFeatureFlags>());
|
||||
* ```
|
||||
*/
|
||||
static void override(
|
||||
std::unique_ptr<ReactNativeFeatureFlagsProvider> provider);
|
||||
|
||||
/**
|
||||
* Removes the overridden feature flags and makes the API return default
|
||||
* values again.
|
||||
*
|
||||
* This is **dangerous**. Use it only if you really understand the
|
||||
* implications of this method.
|
||||
*
|
||||
* This should only be called if you destroy the React Native runtime and
|
||||
* need to create a new one with different overrides. In that case,
|
||||
* call `dangerouslyReset` after destroying the runtime and `override` again
|
||||
* before initializing the new one.
|
||||
*/
|
||||
static void dangerouslyReset();
|
||||
|
||||
private:
|
||||
ReactNativeFeatureFlags() = delete;
|
||||
static ReactNativeFeatureFlagsAccessor& getAccessor(bool reset = false);
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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<<7f8b2ae5c0b18aceeaac0ee60e53bdbb>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
#include <react/featureflags/ReactNativeFeatureFlagsDefaults.h>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include "ReactNativeFeatureFlags.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
ReactNativeFeatureFlagsAccessor::ReactNativeFeatureFlagsAccessor()
|
||||
: currentProvider_(std::make_unique<ReactNativeFeatureFlagsDefaults>()) {}
|
||||
|
||||
bool ReactNativeFeatureFlagsAccessor::commonTestFlag() {
|
||||
if (!commonTestFlag_.has_value()) {
|
||||
// Mark the flag as accessed.
|
||||
static const char* flagName = "commonTestFlag";
|
||||
if (std::find(
|
||||
accessedFeatureFlags_.begin(),
|
||||
accessedFeatureFlags_.end(),
|
||||
flagName) == accessedFeatureFlags_.end()) {
|
||||
accessedFeatureFlags_.push_back(flagName);
|
||||
}
|
||||
|
||||
commonTestFlag_.emplace(currentProvider_->commonTestFlag());
|
||||
}
|
||||
|
||||
return commonTestFlag_.value();
|
||||
}
|
||||
|
||||
void ReactNativeFeatureFlagsAccessor::override(
|
||||
std::unique_ptr<ReactNativeFeatureFlagsProvider> provider) {
|
||||
if (!accessedFeatureFlags_.empty()) {
|
||||
std::ostringstream featureFlagListBuilder;
|
||||
for (const auto& featureFlagName : accessedFeatureFlags_) {
|
||||
featureFlagListBuilder << featureFlagName << ", ";
|
||||
}
|
||||
std::string accessedFeatureFlagNames = featureFlagListBuilder.str();
|
||||
if (!accessedFeatureFlagNames.empty()) {
|
||||
accessedFeatureFlagNames = accessedFeatureFlagNames.substr(
|
||||
0, accessedFeatureFlagNames.size() - 2);
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
"Feature flags were accessed before being overridden: " +
|
||||
accessedFeatureFlagNames);
|
||||
}
|
||||
|
||||
currentProvider_ = std::move(provider);
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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<<3fa0171b372cf6aae150b2ec159fc41e>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <react/featureflags/ReactNativeFeatureFlagsProvider.h>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class ReactNativeFeatureFlagsAccessor {
|
||||
public:
|
||||
ReactNativeFeatureFlagsAccessor();
|
||||
|
||||
bool commonTestFlag();
|
||||
|
||||
void override(std::unique_ptr<ReactNativeFeatureFlagsProvider> provider);
|
||||
|
||||
private:
|
||||
std::unique_ptr<ReactNativeFeatureFlagsProvider> currentProvider_;
|
||||
std::vector<const char*> accessedFeatureFlags_;
|
||||
|
||||
std::optional<bool> commonTestFlag_;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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<<76033e9fb87174da88e0ee922df28701>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <react/featureflags/ReactNativeFeatureFlagsProvider.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
|
||||
public:
|
||||
ReactNativeFeatureFlagsDefaults() = default;
|
||||
|
||||
bool commonTestFlag() override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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<<20dfc971dddc23a6d0cc55938b0d65b7>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class ReactNativeFeatureFlagsProvider {
|
||||
public:
|
||||
virtual ~ReactNativeFeatureFlagsProvider() = default;
|
||||
|
||||
virtual bool commonTestFlag() = 0;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlagsDefaults.h>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
uint overrideAccessCount = 0;
|
||||
|
||||
class ReactNativeFeatureFlagsTestOverrides
|
||||
: public ReactNativeFeatureFlagsDefaults {
|
||||
public:
|
||||
bool commonTestFlag() override {
|
||||
overrideAccessCount++;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class ReactNativeFeatureFlagsTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
overrideAccessCount = 0;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ReactNativeFeatureFlagsTest, providesDefaults) {
|
||||
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), false);
|
||||
}
|
||||
|
||||
TEST_F(ReactNativeFeatureFlagsTest, providesOverriddenValues) {
|
||||
ReactNativeFeatureFlags::override(
|
||||
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
|
||||
|
||||
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
|
||||
}
|
||||
|
||||
TEST_F(ReactNativeFeatureFlagsTest, preventsOverridingAfterAccess) {
|
||||
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), false);
|
||||
|
||||
try {
|
||||
ReactNativeFeatureFlags::override(
|
||||
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
|
||||
FAIL()
|
||||
<< "Expected ReactNativeFeatureFlags::override() to throw an exception";
|
||||
} catch (const std::runtime_error& e) {
|
||||
EXPECT_STREQ(
|
||||
"Feature flags were accessed before being overridden: commonTestFlag",
|
||||
e.what());
|
||||
}
|
||||
|
||||
// Overrides shouldn't be applied after they've been accessed
|
||||
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), false);
|
||||
}
|
||||
|
||||
TEST_F(ReactNativeFeatureFlagsTest, cachesValuesFromOverride) {
|
||||
ReactNativeFeatureFlags::override(
|
||||
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
|
||||
|
||||
EXPECT_EQ(overrideAccessCount, 0);
|
||||
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
|
||||
EXPECT_EQ(overrideAccessCount, 1);
|
||||
|
||||
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
|
||||
EXPECT_EQ(overrideAccessCount, 1);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
ReactNativeFeatureFlagsTest,
|
||||
providesDefaulValuesAgainWhenResettingAfterAnOverride) {
|
||||
ReactNativeFeatureFlags::override(
|
||||
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
|
||||
|
||||
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
|
||||
|
||||
ReactNativeFeatureFlags::dangerouslyReset();
|
||||
|
||||
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), false);
|
||||
}
|
||||
|
||||
TEST_F(ReactNativeFeatureFlagsTest, allowsOverridingAgainAfterReset) {
|
||||
ReactNativeFeatureFlags::override(
|
||||
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
|
||||
|
||||
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
|
||||
|
||||
ReactNativeFeatureFlags::dangerouslyReset();
|
||||
|
||||
ReactNativeFeatureFlags::override(
|
||||
std::make_unique<ReactNativeFeatureFlagsTestOverrides>());
|
||||
|
||||
EXPECT_EQ(ReactNativeFeatureFlags::commonTestFlag(), true);
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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<<f80c7362c3d377edfcbd5cc0642c2464>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
#include "NativeReactNativeFeatureFlags.h"
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
|
||||
#include "Plugins.h"
|
||||
|
||||
std::shared_ptr<facebook::react::TurboModule>
|
||||
NativeReactNativeFeatureFlagsModuleProvider(
|
||||
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
|
||||
return std::make_shared<facebook::react::NativeReactNativeFeatureFlags>(
|
||||
std::move(jsInvoker));
|
||||
}
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
NativeReactNativeFeatureFlags::NativeReactNativeFeatureFlags(
|
||||
std::shared_ptr<CallInvoker> jsInvoker)
|
||||
: NativeReactNativeFeatureFlagsCxxSpec(std::move(jsInvoker)) {}
|
||||
|
||||
bool NativeReactNativeFeatureFlags::commonTestFlag(
|
||||
jsi::Runtime& /*runtime*/) {
|
||||
return ReactNativeFeatureFlags::commonTestFlag();
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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<<18a3543b75c44e00bdf73ca2f12d230c>>
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class NativeReactNativeFeatureFlags
|
||||
: public NativeReactNativeFeatureFlagsCxxSpec<
|
||||
NativeReactNativeFeatureFlags>,
|
||||
std::enable_shared_from_this<NativeReactNativeFeatureFlags> {
|
||||
public:
|
||||
NativeReactNativeFeatureFlags(std::shared_ptr<CallInvoker> jsInvoker);
|
||||
|
||||
bool commonTestFlag(jsi::Runtime& runtime);
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
@@ -30,6 +30,7 @@ target_link_libraries(
|
||||
bridgeless
|
||||
jserrorhandler
|
||||
fabricjni
|
||||
featureflagsjni
|
||||
turbomodulejsijni
|
||||
fb
|
||||
jsi
|
||||
|
||||
@@ -89,7 +89,9 @@
|
||||
"types"
|
||||
],
|
||||
"scripts": {
|
||||
"prepack": "cp ../../README.md ."
|
||||
"prepack": "cp ../../README.md .",
|
||||
"featureflags-check": "node ./scripts/featureflags/update.js --verify-unchanged",
|
||||
"featureflags-update": "node ./scripts/featureflags/update.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "18.2.0"
|
||||
|
||||
@@ -556,6 +556,7 @@ class CodegenUtilsTests < Test::Unit::TestCase
|
||||
'React-Fabric': [],
|
||||
'React-FabricImage': [],
|
||||
'React-utils': [],
|
||||
'React-featureflags': [],
|
||||
'React-debug': [],
|
||||
'React-rendererdebug': [],
|
||||
})
|
||||
@@ -569,13 +570,14 @@ class CodegenUtilsTests < Test::Unit::TestCase
|
||||
specs = get_podspec_no_fabric_no_script()
|
||||
|
||||
specs["pod_target_xcconfig"]["FRAMEWORK_SEARCH_PATHS"].concat([])
|
||||
specs["pod_target_xcconfig"]["HEADER_SEARCH_PATHS"].concat(" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-FabricImage/React_FabricImage.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTFabric/RCTFabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-debug/React_debug.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-rendererdebug/React_rendererdebug.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-utils/React_utils.framework/Headers\"")
|
||||
specs["pod_target_xcconfig"]["HEADER_SEARCH_PATHS"].concat(" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-FabricImage/React_FabricImage.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTFabric/RCTFabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-debug/React_debug.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-rendererdebug/React_rendererdebug.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-utils/React_utils.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-featureflags/React_featureflags.framework/Headers\"")
|
||||
|
||||
specs[:dependencies].merge!({
|
||||
'React-graphics': [],
|
||||
'React-Fabric': [],
|
||||
'React-FabricImage': [],
|
||||
'React-utils': [],
|
||||
'React-featureflags': [],
|
||||
'React-debug': [],
|
||||
'React-rendererdebug': [],
|
||||
})
|
||||
|
||||
@@ -151,7 +151,7 @@ class NewArchitectureTests < Test::Unit::TestCase
|
||||
folly_compiler_flags = folly_config[:compiler_flags]
|
||||
|
||||
assert_equal(spec.compiler_flags, NewArchitectureHelper.folly_compiler_flags)
|
||||
assert_equal(spec.pod_target_xcconfig["HEADER_SEARCH_PATHS"], "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/Headers/Private/Yoga\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fmt/include\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-FabricImage/React_FabricImage.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTFabric/RCTFabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-utils/React_utils.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-debug/React_debug.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-ImageManager/React_ImageManager.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-rendererdebug/React_rendererdebug.framework/Headers\"")
|
||||
assert_equal(spec.pod_target_xcconfig["HEADER_SEARCH_PATHS"], "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/Headers/Private/Yoga\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fmt/include\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-FabricImage/React_FabricImage.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTFabric/RCTFabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-utils/React_utils.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-featureflags/React_featureflags.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-debug/React_debug.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-ImageManager/React_ImageManager.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-rendererdebug/React_rendererdebug.framework/Headers\"")
|
||||
assert_equal(spec.pod_target_xcconfig["CLANG_CXX_LANGUAGE_STANDARD"], "c++20")
|
||||
assert_equal(spec.pod_target_xcconfig["OTHER_CPLUSPLUSFLAGS"], "$(inherited) -DRCT_NEW_ARCH_ENABLED=1 "+ folly_compiler_flags)
|
||||
assert_equal(
|
||||
@@ -171,6 +171,7 @@ class NewArchitectureTests < Test::Unit::TestCase
|
||||
{ :dependency_name => "React-Fabric" },
|
||||
{ :dependency_name => "React-graphics" },
|
||||
{ :dependency_name => "React-utils" },
|
||||
{ :dependency_name => "React-featureflags" },
|
||||
{ :dependency_name => "React-debug" },
|
||||
{ :dependency_name => "React-ImageManager" },
|
||||
{ :dependency_name => "React-rendererdebug" },
|
||||
@@ -193,7 +194,7 @@ class NewArchitectureTests < Test::Unit::TestCase
|
||||
|
||||
# Assert
|
||||
assert_equal(Helpers::Constants.folly_config[:compiler_flags], "#{NewArchitectureHelper.folly_compiler_flags}")
|
||||
assert_equal(spec.pod_target_xcconfig["HEADER_SEARCH_PATHS"], "#{other_flags} \"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/Headers/Private/Yoga\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fmt/include\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-FabricImage/React_FabricImage.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTFabric/RCTFabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-utils/React_utils.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-debug/React_debug.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-ImageManager/React_ImageManager.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-rendererdebug/React_rendererdebug.framework/Headers\"")
|
||||
assert_equal(spec.pod_target_xcconfig["HEADER_SEARCH_PATHS"], "#{other_flags} \"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/Headers/Private/Yoga\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fmt/include\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-FabricImage/React_FabricImage.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTFabric/RCTFabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-utils/React_utils.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-featureflags/React_featureflags.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-debug/React_debug.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-ImageManager/React_ImageManager.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-rendererdebug/React_rendererdebug.framework/Headers\"")
|
||||
assert_equal(spec.pod_target_xcconfig["CLANG_CXX_LANGUAGE_STANDARD"], "c++20")
|
||||
assert_equal(
|
||||
spec.dependencies,
|
||||
@@ -212,6 +213,7 @@ class NewArchitectureTests < Test::Unit::TestCase
|
||||
{ :dependency_name => "React-Fabric" },
|
||||
{ :dependency_name => "React-graphics" },
|
||||
{ :dependency_name => "React-utils" },
|
||||
{ :dependency_name => "React-featureflags" },
|
||||
{ :dependency_name => "React-debug" },
|
||||
{ :dependency_name => "React-ImageManager" },
|
||||
{ :dependency_name => "React-rendererdebug" },
|
||||
|
||||
@@ -101,6 +101,7 @@ class CodegenUtils
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-debug", "React_debug", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-rendererdebug", "React_rendererdebug", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-utils", "React_utils", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-featureflags", "React_featureflags", []))
|
||||
.each { |search_path|
|
||||
header_search_paths << "\"#{search_path}\""
|
||||
}
|
||||
@@ -140,6 +141,7 @@ class CodegenUtils
|
||||
'React-FabricImage': [],
|
||||
'React-debug': [],
|
||||
'React-utils': [],
|
||||
'React-featureflags': [],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ class NewArchitectureHelper
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-NativeModulesApple", "React_NativeModulesApple", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-RCTFabric", "RCTFabric", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-utils", "React_utils", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-featureflags", "React_featureflags", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-debug", "React_debug", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-ImageManager", "React_ImageManager", []))
|
||||
.concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-rendererdebug", "React_rendererdebug", []))
|
||||
@@ -124,6 +125,7 @@ class NewArchitectureHelper
|
||||
spec.dependency "React-Fabric"
|
||||
spec.dependency "React-graphics"
|
||||
spec.dependency "React-utils"
|
||||
spec.dependency "React-featureflags"
|
||||
spec.dependency "React-debug"
|
||||
spec.dependency "React-ImageManager"
|
||||
spec.dependency "React-rendererdebug"
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
# Feature Flags
|
||||
|
||||
Feature flags are values that determine the behavior of specific parts of React
|
||||
Native. This directory contains the configuration for those values, and scripts
|
||||
to generate files for different languages to access and customize them.
|
||||
|
||||
There are 2 types of feature flags:
|
||||
* Common: can be accessed from any language and they provide consistent values
|
||||
everywhere.
|
||||
* JS-only: they can only be accessed and customized from JavaScript.
|
||||
|
||||
## Definition
|
||||
|
||||
The source of truth for the definition of the flags is the file `ReactNativeFeatureFlags.json`
|
||||
in this directory. That JSON file should have the following structure:
|
||||
|
||||
```flow
|
||||
type Config = {
|
||||
common: FeatureFlagsList,
|
||||
jsOnly: FeatureFlagsList,
|
||||
};
|
||||
|
||||
type FeatureFlagsList = {
|
||||
[flagName: string]: {
|
||||
description: string,
|
||||
defaultValue: boolean | number | string,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"common": {
|
||||
"enableMicrotasks": {
|
||||
"description": "Enable the use of microtasks in the JS runtime.",
|
||||
"defaultValue": false
|
||||
}
|
||||
},
|
||||
"jsOnly": {
|
||||
"enableAccessToHostTreeInFabric": {
|
||||
"description": "Enables access to the host tree in Fabric using DOM-compatible APIs.",
|
||||
"defaultValue": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After any changes to this definitions, the code that provides access to them
|
||||
must be regenerated executing the `update` script in this directory.
|
||||
|
||||
## Access
|
||||
|
||||
### C++ / Objective-C
|
||||
|
||||
```c++
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
|
||||
if (ReactNativeFeatureFlags::enableMicrotasks()) {
|
||||
// do something
|
||||
}
|
||||
```
|
||||
|
||||
### Kotlin
|
||||
|
||||
```kotlin
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
|
||||
|
||||
fun someMethod() {
|
||||
if (ReactNativeFeatureFlags.enableMicrotasks()) {
|
||||
// do something
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### JavaScript
|
||||
|
||||
```javascript
|
||||
import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags';
|
||||
|
||||
if (ReactNativeFeatureFlags.enableMicrotasks()) {
|
||||
// Native flag
|
||||
}
|
||||
|
||||
if (ReactNativeFeatureFlags.enableAccessToHostTreeInFabric()) {
|
||||
// JS-only flag
|
||||
}
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
### C++/Objective-C
|
||||
|
||||
```c++
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlagsDefaults.h>
|
||||
|
||||
class CustomReactNativeFeatureFlags : public ReactNativeFeatureFlagsDefaults {
|
||||
public:
|
||||
CustomReactNativeFeatureFlags();
|
||||
|
||||
bool enableMicrotasks() override {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNativeFeatureFlags::override(std::make_unique<CustomReactNativeFeatureFlags>());
|
||||
```
|
||||
|
||||
### Kotlin
|
||||
|
||||
```kotlin
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsDefaults
|
||||
|
||||
fun overrideFeatureFlags() {
|
||||
ReactNativeFeatureFlags.override(object : ReactNativeFeatureFlagsDefaults() {
|
||||
override fun useMicrotasks(): Boolean = true
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### JavaScript
|
||||
|
||||
```javascript
|
||||
import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags';
|
||||
|
||||
ReactNativeFeatureFlags.override({
|
||||
enableAccessToHostTreeInFabric: () => true,
|
||||
});
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
The architecture of this feature flags system can be described as follows:
|
||||
* A shared C++ core, where we provide access to the flags and allow
|
||||
customizations.
|
||||
* A Kotlin/Java interface that allows accessing and customizing the values in
|
||||
the C++ core (via JNI).
|
||||
* A JavaScript interface that allows accessing the common values (via a native
|
||||
module) and accessing and customizing the JS-only values.
|
||||
|
||||

|
||||
|
||||
_This image has an embedded [Excalidraw](https://www.excalidraw.com) diagram,
|
||||
so you can upload it there if you need to make further modifications._
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"common": {
|
||||
"commonTestFlag": {
|
||||
"description": "Common flag for testing. Do NOT modify.",
|
||||
"defaultValue": false
|
||||
}
|
||||
},
|
||||
"jsOnly": {
|
||||
"jsOnlyTestFlag": {
|
||||
"description": "JS-only flag for testing. Do NOT modify.",
|
||||
"defaultValue": false
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const JReactNativeFeatureFlagsCxxInteropCPP = require('./templates/android/JReactNativeFeatureFlagsCxxInterop.cpp-template');
|
||||
const JReactNativeFeatureFlagsCxxInteropH = require('./templates/android/JReactNativeFeatureFlagsCxxInterop.h-template');
|
||||
const ReactNativeFeatureFlagsKt = require('./templates/android/ReactNativeFeatureFlags.kt-template');
|
||||
const ReactNativeFeatureFlagsCxxAccessorKt = require('./templates/android/ReactNativeFeatureFlagsCxxAccessor.kt-template');
|
||||
const ReactNativeFeatureFlagsCxxInteropKt = require('./templates/android/ReactNativeFeatureFlagsCxxInterop.kt-template');
|
||||
const ReactNativeFeatureFlagsDefaultsKt = require('./templates/android/ReactNativeFeatureFlagsDefaults.kt-template');
|
||||
const ReactNativeFeatureFlagsLocalAccessorKt = require('./templates/android/ReactNativeFeatureFlagsLocalAccessor.kt-template');
|
||||
const ReactNativeFeatureFlagsProviderKt = require('./templates/android/ReactNativeFeatureFlagsProvider.kt-template');
|
||||
const ReactNativeFeatureFlagsProviderHolderCPP = require('./templates/android/ReactNativeFeatureFlagsProviderHolder.cpp-template');
|
||||
const ReactNativeFeatureFlagsProviderHolderH = require('./templates/android/ReactNativeFeatureFlagsProviderHolder.h-template');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = function generateandroidModules(
|
||||
generatorConfig,
|
||||
featureFlagsConfig,
|
||||
) {
|
||||
return {
|
||||
[path.join(generatorConfig.androidPath, 'ReactNativeFeatureFlags.kt')]:
|
||||
ReactNativeFeatureFlagsKt(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.androidPath,
|
||||
'ReactNativeFeatureFlagsCxxAccessor.kt',
|
||||
)]: ReactNativeFeatureFlagsCxxAccessorKt(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.androidPath,
|
||||
'ReactNativeFeatureFlagsLocalAccessor.kt',
|
||||
)]: ReactNativeFeatureFlagsLocalAccessorKt(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.androidPath,
|
||||
'ReactNativeFeatureFlagsCxxInterop.kt',
|
||||
)]: ReactNativeFeatureFlagsCxxInteropKt(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.androidPath,
|
||||
'ReactNativeFeatureFlagsDefaults.kt',
|
||||
)]: ReactNativeFeatureFlagsDefaultsKt(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.androidPath,
|
||||
'ReactNativeFeatureFlagsProvider.kt',
|
||||
)]: ReactNativeFeatureFlagsProviderKt(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.androidJniPath,
|
||||
'ReactNativeFeatureFlagsProviderHolder.h',
|
||||
)]: ReactNativeFeatureFlagsProviderHolderH(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.androidJniPath,
|
||||
'ReactNativeFeatureFlagsProviderHolder.cpp',
|
||||
)]: ReactNativeFeatureFlagsProviderHolderCPP(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.androidJniPath,
|
||||
'JReactNativeFeatureFlagsCxxInterop.h',
|
||||
)]: JReactNativeFeatureFlagsCxxInteropH(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.androidJniPath,
|
||||
'JReactNativeFeatureFlagsCxxInterop.cpp',
|
||||
)]: JReactNativeFeatureFlagsCxxInteropCPP(featureFlagsConfig),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ReactNativeFeatureFlagsCPP = require('./templates/common-cxx/ReactNativeFeatureFlags.cpp-template');
|
||||
const ReactNativeFeatureFlagsH = require('./templates/common-cxx/ReactNativeFeatureFlags.h-template');
|
||||
const ReactNativeFeatureFlagsAccessorCPP = require('./templates/common-cxx/ReactNativeFeatureFlagsAccessor.cpp-template');
|
||||
const ReactNativeFeatureFlagsAccessorH = require('./templates/common-cxx/ReactNativeFeatureFlagsAccessor.h-template');
|
||||
const ReactNativeFeatureFlagsDefaultsH = require('./templates/common-cxx/ReactNativeFeatureFlagsDefaults.h-template');
|
||||
const ReactNativeFeatureFlagsProviderH = require('./templates/common-cxx/ReactNativeFeatureFlagsProvider.h-template');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = function generateCommonCxxModules(
|
||||
generatorConfig,
|
||||
featureFlagsConfig,
|
||||
) {
|
||||
return {
|
||||
[path.join(generatorConfig.commonCxxPath, 'ReactNativeFeatureFlags.h')]:
|
||||
ReactNativeFeatureFlagsH(featureFlagsConfig),
|
||||
[path.join(generatorConfig.commonCxxPath, 'ReactNativeFeatureFlags.cpp')]:
|
||||
ReactNativeFeatureFlagsCPP(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.commonCxxPath,
|
||||
'ReactNativeFeatureFlagsAccessor.h',
|
||||
)]: ReactNativeFeatureFlagsAccessorH(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.commonCxxPath,
|
||||
'ReactNativeFeatureFlagsAccessor.cpp',
|
||||
)]: ReactNativeFeatureFlagsAccessorCPP(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.commonCxxPath,
|
||||
'ReactNativeFeatureFlagsDefaults.h',
|
||||
)]: ReactNativeFeatureFlagsDefaultsH(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.commonCxxPath,
|
||||
'ReactNativeFeatureFlagsProvider.h',
|
||||
)]: ReactNativeFeatureFlagsProviderH(featureFlagsConfig),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
const generateAndroidModules = require('./generateAndroidModules');
|
||||
const generateCommonCxxModules = require('./generateCommonCxxModules');
|
||||
const generateJavaScriptModules = require('./generateJavaScriptModules');
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = function generateFiles(generatorConfig, generatorOptions) {
|
||||
const userDefinedFeatureFlagsConfig = JSON.parse(
|
||||
fs.readFileSync(generatorConfig.configPath, 'utf8'),
|
||||
);
|
||||
|
||||
const featureFlagsConfig = Object.assign(
|
||||
{jsOnly: {}, common: {}},
|
||||
userDefinedFeatureFlagsConfig,
|
||||
);
|
||||
|
||||
fs.mkdirSync(generatorConfig.jsPath, {recursive: true});
|
||||
fs.mkdirSync(generatorConfig.commonCxxPath, {recursive: true});
|
||||
fs.mkdirSync(generatorConfig.commonNativeModuleCxxPath, {recursive: true});
|
||||
fs.mkdirSync(generatorConfig.androidPath, {recursive: true});
|
||||
fs.mkdirSync(generatorConfig.androidJniPath, {recursive: true});
|
||||
|
||||
const jsModules = generateJavaScriptModules(
|
||||
generatorConfig,
|
||||
featureFlagsConfig,
|
||||
);
|
||||
|
||||
const commonCxxModules = generateCommonCxxModules(
|
||||
generatorConfig,
|
||||
featureFlagsConfig,
|
||||
);
|
||||
|
||||
const androidModules = generateAndroidModules(
|
||||
generatorConfig,
|
||||
featureFlagsConfig,
|
||||
);
|
||||
|
||||
const generatedFiles = {...jsModules, ...commonCxxModules, ...androidModules};
|
||||
|
||||
if (generatorOptions.verifyUnchanged) {
|
||||
const existingModules = {};
|
||||
for (const moduleName of Object.keys(generatedFiles)) {
|
||||
const existingModule = fs.readFileSync(moduleName, 'utf8');
|
||||
existingModules[moduleName] = existingModule;
|
||||
}
|
||||
|
||||
const changedFiles = [];
|
||||
for (const moduleName of Object.keys(generatedFiles)) {
|
||||
const module = generatedFiles[moduleName];
|
||||
const existingModule = existingModules[moduleName];
|
||||
|
||||
if (module !== existingModule) {
|
||||
changedFiles.push(moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
if (changedFiles.length > 0) {
|
||||
const changedFilesStr = changedFiles
|
||||
.map(changedFile => ' ' + changedFile)
|
||||
.join('\n');
|
||||
|
||||
throw new Error(
|
||||
`Detected changes in generated files for feature flags:\n${changedFilesStr}\n\n` +
|
||||
'Please rerun `yarn featureflags-update` and commit the changes.',
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [modulePath, moduleContents] of Object.entries(generatedFiles)) {
|
||||
fs.writeFileSync(modulePath, moduleContents, 'utf8');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const NativeReactNativeFeatureFlagsCPP = require('./templates/js/NativeReactNativeFeatureFlags.cpp-template');
|
||||
const NativeReactNativeFeatureFlagsH = require('./templates/js/NativeReactNativeFeatureFlags.h-template');
|
||||
const NativeReactNativeFeatureFlagsJS = require('./templates/js/NativeReactNativeFeatureFlags.js-template');
|
||||
const ReactNativeFeatureFlagsJS = require('./templates/js/ReactNativeFeatureFlags.js-template');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = function generateCommonCxxModules(
|
||||
generatorConfig,
|
||||
featureFlagsConfig,
|
||||
) {
|
||||
return {
|
||||
[path.join(generatorConfig.jsPath, 'ReactNativeFeatureFlags.js')]:
|
||||
ReactNativeFeatureFlagsJS(featureFlagsConfig),
|
||||
[path.join(generatorConfig.jsPath, 'NativeReactNativeFeatureFlags.js')]:
|
||||
NativeReactNativeFeatureFlagsJS(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.commonNativeModuleCxxPath,
|
||||
'NativeReactNativeFeatureFlags.h',
|
||||
)]: NativeReactNativeFeatureFlagsH(featureFlagsConfig),
|
||||
[path.join(
|
||||
generatorConfig.commonNativeModuleCxxPath,
|
||||
'NativeReactNativeFeatureFlags.cpp',
|
||||
)]: NativeReactNativeFeatureFlagsCPP(featureFlagsConfig),
|
||||
};
|
||||
};
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getCxxTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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}
|
||||
|
||||
#include "JReactNativeFeatureFlagsCxxInterop.h"
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlagsProviderHolder.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
`${getCxxTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)} JReactNativeFeatureFlagsCxxInterop::${flagName}(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
|
||||
return ReactNativeFeatureFlags::${flagName}();
|
||||
}`,
|
||||
)
|
||||
.join('\n\n')}
|
||||
|
||||
void JReactNativeFeatureFlagsCxxInterop::override(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/,
|
||||
jni::alias_ref<jobject> provider) {
|
||||
ReactNativeFeatureFlags::override(
|
||||
std::make_unique<ReactNativeFeatureFlagsProviderHolder>(provider));
|
||||
}
|
||||
|
||||
void JReactNativeFeatureFlagsCxxInterop::dangerouslyReset(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
|
||||
ReactNativeFeatureFlags::dangerouslyReset();
|
||||
}
|
||||
|
||||
void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
|
||||
javaClassLocal()->registerNatives({
|
||||
makeNativeMethod(
|
||||
"override", JReactNativeFeatureFlagsCxxInterop::override),
|
||||
makeNativeMethod("dangerouslyReset", JReactNativeFeatureFlagsCxxInterop::dangerouslyReset),
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` makeNativeMethod(
|
||||
"${flagName}",
|
||||
JReactNativeFeatureFlagsCxxInterop::${flagName}),`,
|
||||
)
|
||||
.join('\n')}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
`);
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getCxxTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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 <fbjni/fbjni.h>
|
||||
#include <jni.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class JReactNativeFeatureFlagsCxxInterop
|
||||
: public jni::JavaClass<JReactNativeFeatureFlagsCxxInterop> {
|
||||
public:
|
||||
constexpr static auto kJavaDescriptor =
|
||||
"Lcom/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop;";
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` static ${getCxxTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)} ${flagName}(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);`,
|
||||
)
|
||||
.join('\n\n')}
|
||||
|
||||
static void override(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>,
|
||||
jni::alias_ref<jobject> provider);
|
||||
|
||||
static void dangerouslyReset(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
static void registerNatives();
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
`);
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {DO_NOT_MODIFY_COMMENT} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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}
|
||||
|
||||
package com.facebook.react.internal.featureflags
|
||||
|
||||
/**
|
||||
* This object provides access to internal React Native feature flags.
|
||||
*
|
||||
* All the methods are thread-safe if you handle \`override\` correctly.
|
||||
*/
|
||||
object ReactNativeFeatureFlags {
|
||||
private var accessorProvider: () -> ReactNativeFeatureFlagsAccessor = { ReactNativeFeatureFlagsCxxAccessor() }
|
||||
private var accessor: ReactNativeFeatureFlagsAccessor = accessorProvider()
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` /**
|
||||
* ${flagConfig.description}
|
||||
*/
|
||||
fun ${flagName}() = accessor.${flagName}()`,
|
||||
)
|
||||
.join('\n\n')}
|
||||
|
||||
/**
|
||||
* Overrides the feature flags with the ones provided by the given provider
|
||||
* (generally one that extends \`ReactNativeFeatureFlagsDefaults\`).
|
||||
*
|
||||
* This method must be called before you initialize the React Native runtime.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* \`\`\`
|
||||
* ReactNativeFeatureFlags.override(object : ReactNativeFeatureFlagsDefaults() {
|
||||
* override fun someFlag(): Boolean = true // or a dynamic value
|
||||
* })
|
||||
* \`\`\`
|
||||
*/
|
||||
fun override(provider: ReactNativeFeatureFlagsProvider) = accessor.override(provider)
|
||||
|
||||
/**
|
||||
* Removes the overridden feature flags and makes the API return default
|
||||
* values again.
|
||||
*
|
||||
* This should only be called if you destroy the React Native runtime and
|
||||
* need to create a new one with different overrides. In that case,
|
||||
* call \`dangerouslyReset\` after destroying the runtime and \`override\`
|
||||
* again before initializing the new one.
|
||||
*/
|
||||
fun dangerouslyReset() {
|
||||
// This is necessary when the accessor interops with C++ and we need to
|
||||
// remove the overrides set there.
|
||||
accessor.dangerouslyReset()
|
||||
|
||||
// This discards the cached values and the overrides set in the JVM.
|
||||
accessor = accessorProvider()
|
||||
}
|
||||
|
||||
/**
|
||||
* This is just used to replace the default ReactNativeFeatureFlagsCxxAccessor
|
||||
* that uses JNI with a version that doesn't, to simplify testing.
|
||||
*/
|
||||
internal fun setAccessorProvider(newAccessorProvider: () -> ReactNativeFeatureFlagsAccessor) {
|
||||
accessorProvider = newAccessorProvider
|
||||
accessor = accessorProvider()
|
||||
}
|
||||
}
|
||||
`);
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getKotlinTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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}
|
||||
|
||||
package com.facebook.react.internal.featureflags
|
||||
|
||||
class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccessor {
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` private var ${flagName}Cache: ${getKotlinTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)}? = null`,
|
||||
)
|
||||
.join('\n')}
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) => ` override fun ${flagName}(): Boolean {
|
||||
var cached = ${flagName}Cache
|
||||
if (cached == null) {
|
||||
cached = ReactNativeFeatureFlagsCxxInterop.${flagName}()
|
||||
${flagName}Cache = cached
|
||||
}
|
||||
return cached
|
||||
}`,
|
||||
)
|
||||
.join('\n\n')}
|
||||
|
||||
override fun override(provider: ReactNativeFeatureFlagsProvider) =
|
||||
ReactNativeFeatureFlagsCxxInterop.override(provider as Any)
|
||||
|
||||
override fun dangerouslyReset() = ReactNativeFeatureFlagsCxxInterop.dangerouslyReset()
|
||||
}
|
||||
`);
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getKotlinTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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}
|
||||
|
||||
package com.facebook.react.internal.featureflags
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip
|
||||
import com.facebook.soloader.SoLoader
|
||||
|
||||
@DoNotStrip
|
||||
object ReactNativeFeatureFlagsCxxInterop {
|
||||
init {
|
||||
SoLoader.loadLibrary("reactfeatureflagsjni")
|
||||
}
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` @DoNotStrip @JvmStatic external fun ${flagName}(): ${getKotlinTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)}`,
|
||||
)
|
||||
.join('\n')}
|
||||
|
||||
@DoNotStrip @JvmStatic external fun override(provider: Any)
|
||||
|
||||
@DoNotStrip @JvmStatic external fun dangerouslyReset()
|
||||
}
|
||||
`);
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getKotlinTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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}
|
||||
|
||||
package com.facebook.react.internal.featureflags
|
||||
|
||||
open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvider {
|
||||
// We could use JNI to get the defaults from C++,
|
||||
// but that is more expensive than just duplicating the defaults here.
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` override fun ${flagName}(): ${getKotlinTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)} = ${JSON.stringify(flagConfig.defaultValue)}`,
|
||||
)
|
||||
.join('\n')}
|
||||
}
|
||||
`);
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getKotlinTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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}
|
||||
|
||||
package com.facebook.react.internal.featureflags
|
||||
|
||||
class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAccessor {
|
||||
private var currentProvider: ReactNativeFeatureFlagsProvider = ReactNativeFeatureFlagsDefaults()
|
||||
|
||||
private val accessedFeatureFlags = mutableSetOf<String>()
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` private var ${flagName}Cache: ${getKotlinTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)}? = null`,
|
||||
)
|
||||
.join('\n')}
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) => ` override fun ${flagName}(): Boolean {
|
||||
var cached = ${flagName}Cache
|
||||
if (cached == null) {
|
||||
cached = currentProvider.${flagName}()
|
||||
accessedFeatureFlags.add("${flagName}")
|
||||
${flagName}Cache = cached
|
||||
}
|
||||
return cached
|
||||
}`,
|
||||
)
|
||||
.join('\n\n')}
|
||||
|
||||
override fun override(provider: ReactNativeFeatureFlagsProvider) {
|
||||
if (accessedFeatureFlags.isNotEmpty()) {
|
||||
val accessedFeatureFlagsStr = accessedFeatureFlags.joinToString(separator = ", ") { it }
|
||||
throw IllegalStateException(
|
||||
"Feature flags were accessed before being overridden: $accessedFeatureFlagsStr")
|
||||
}
|
||||
currentProvider = provider
|
||||
}
|
||||
|
||||
override fun dangerouslyReset() {
|
||||
// We don't need to do anything here because \`ReactNativeFeatureFlags\` will
|
||||
// just create a new instance of this class.
|
||||
}
|
||||
}
|
||||
`);
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getKotlinTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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}
|
||||
|
||||
package com.facebook.react.internal.featureflags
|
||||
|
||||
interface ReactNativeFeatureFlagsProvider {
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` fun ${flagName}(): ${getKotlinTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)}`,
|
||||
)
|
||||
.join('\n')}
|
||||
}
|
||||
`);
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getCxxTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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}
|
||||
|
||||
#include "ReactNativeFeatureFlagsProviderHolder.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
`${getCxxTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)} ReactNativeFeatureFlagsProviderHolder::${flagName}() {
|
||||
static const auto method =
|
||||
facebook::jni::findClassStatic(
|
||||
"com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider")
|
||||
->getMethod<jboolean()>("${flagName}");
|
||||
return method(javaProvider_);
|
||||
}`,
|
||||
)
|
||||
.join('\n\n')}
|
||||
|
||||
} // namespace facebook::react
|
||||
`);
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getCxxTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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 <fbjni/fbjni.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
/**
|
||||
* Implementation of ReactNativeFeatureFlagsProvider that wraps a
|
||||
* ReactNativeFeatureFlagsProvider Java object.
|
||||
*/
|
||||
class ReactNativeFeatureFlagsProviderHolder
|
||||
: public ReactNativeFeatureFlagsProvider {
|
||||
public:
|
||||
explicit ReactNativeFeatureFlagsProviderHolder(
|
||||
jni::alias_ref<jobject> javaProvider)
|
||||
: javaProvider_(make_global(javaProvider)){};
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` ${getCxxTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)} ${flagName}() override;`,
|
||||
)
|
||||
.join('\n')}
|
||||
|
||||
private:
|
||||
jni::global_ref<jobject> javaProvider_;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
`);
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getCxxTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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}
|
||||
|
||||
#include "ReactNativeFeatureFlags.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
`${getCxxTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)} ReactNativeFeatureFlags::${flagName}() {
|
||||
return getAccessor().${flagName}();
|
||||
}`,
|
||||
)
|
||||
.join('\n\n')}
|
||||
|
||||
void ReactNativeFeatureFlags::override(
|
||||
std::unique_ptr<ReactNativeFeatureFlagsProvider> provider) {
|
||||
getAccessor().override(std::move(provider));
|
||||
}
|
||||
|
||||
void ReactNativeFeatureFlags::dangerouslyReset() {
|
||||
getAccessor(true);
|
||||
}
|
||||
|
||||
ReactNativeFeatureFlagsAccessor& ReactNativeFeatureFlags::getAccessor(
|
||||
bool reset) {
|
||||
static std::unique_ptr<ReactNativeFeatureFlagsAccessor> accessor;
|
||||
if (accessor == nullptr || reset) {
|
||||
accessor = std::make_unique<ReactNativeFeatureFlagsAccessor>();
|
||||
}
|
||||
return *accessor;
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
`);
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getCxxTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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/ReactNativeFeatureFlagsAccessor.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlagsProvider.h>
|
||||
#include <memory>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
/**
|
||||
* This class provides access to internal React Native feature flags.
|
||||
*
|
||||
* All the methods are thread-safe if you handle \`override\` correctly.
|
||||
*/
|
||||
class ReactNativeFeatureFlags {
|
||||
public:
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` /**
|
||||
* ${flagConfig.description}
|
||||
*/
|
||||
static ${getCxxTypeFromDefaultValue(flagConfig.defaultValue)} ${flagName}();`,
|
||||
)
|
||||
.join('\n\n')}
|
||||
|
||||
/**
|
||||
* Overrides the feature flags with the ones provided by the given provider
|
||||
* (generally one that extends \`ReactNativeFeatureFlagsDefaults\`).
|
||||
*
|
||||
* This method must be called before you initialize the React Native runtime.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* \`\`\`
|
||||
* class MyReactNativeFeatureFlags : public ReactNativeFeatureFlagsDefaults {
|
||||
* public:
|
||||
* bool someFlag() override;
|
||||
* };
|
||||
*
|
||||
* ReactNativeFeatureFlags.override(
|
||||
* std::make_unique<MyReactNativeFeatureFlags>());
|
||||
* \`\`\`
|
||||
*/
|
||||
static void override(
|
||||
std::unique_ptr<ReactNativeFeatureFlagsProvider> provider);
|
||||
|
||||
/**
|
||||
* Removes the overridden feature flags and makes the API return default
|
||||
* values again.
|
||||
*
|
||||
* This is **dangerous**. Use it only if you really understand the
|
||||
* implications of this method.
|
||||
*
|
||||
* This should only be called if you destroy the React Native runtime and
|
||||
* need to create a new one with different overrides. In that case,
|
||||
* call \`dangerouslyReset\` after destroying the runtime and \`override\` again
|
||||
* before initializing the new one.
|
||||
*/
|
||||
static void dangerouslyReset();
|
||||
|
||||
private:
|
||||
ReactNativeFeatureFlags() = delete;
|
||||
static ReactNativeFeatureFlagsAccessor& getAccessor(bool reset = false);
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
`);
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getCxxTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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}
|
||||
|
||||
#include <react/featureflags/ReactNativeFeatureFlagsDefaults.h>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include "ReactNativeFeatureFlags.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
ReactNativeFeatureFlagsAccessor::ReactNativeFeatureFlagsAccessor()
|
||||
: currentProvider_(std::make_unique<ReactNativeFeatureFlagsDefaults>()) {}
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
`${getCxxTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)} ReactNativeFeatureFlagsAccessor::${flagName}() {
|
||||
if (!${flagName}_.has_value()) {
|
||||
// Mark the flag as accessed.
|
||||
static const char* flagName = "${flagName}";
|
||||
if (std::find(
|
||||
accessedFeatureFlags_.begin(),
|
||||
accessedFeatureFlags_.end(),
|
||||
flagName) == accessedFeatureFlags_.end()) {
|
||||
accessedFeatureFlags_.push_back(flagName);
|
||||
}
|
||||
|
||||
${flagName}_.emplace(currentProvider_->${flagName}());
|
||||
}
|
||||
|
||||
return ${flagName}_.value();
|
||||
}`,
|
||||
)
|
||||
.join('\n\n')}
|
||||
|
||||
void ReactNativeFeatureFlagsAccessor::override(
|
||||
std::unique_ptr<ReactNativeFeatureFlagsProvider> provider) {
|
||||
if (!accessedFeatureFlags_.empty()) {
|
||||
std::ostringstream featureFlagListBuilder;
|
||||
for (const auto& featureFlagName : accessedFeatureFlags_) {
|
||||
featureFlagListBuilder << featureFlagName << ", ";
|
||||
}
|
||||
std::string accessedFeatureFlagNames = featureFlagListBuilder.str();
|
||||
if (!accessedFeatureFlagNames.empty()) {
|
||||
accessedFeatureFlagNames = accessedFeatureFlagNames.substr(
|
||||
0, accessedFeatureFlagNames.size() - 2);
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
"Feature flags were accessed before being overridden: " +
|
||||
accessedFeatureFlagNames);
|
||||
}
|
||||
|
||||
currentProvider_ = std::move(provider);
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
`);
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getCxxTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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/ReactNativeFeatureFlagsProvider.h>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class ReactNativeFeatureFlagsAccessor {
|
||||
public:
|
||||
ReactNativeFeatureFlagsAccessor();
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` ${getCxxTypeFromDefaultValue(flagConfig.defaultValue)} ${flagName}();`,
|
||||
)
|
||||
.join('\n')}
|
||||
|
||||
void override(std::unique_ptr<ReactNativeFeatureFlagsProvider> provider);
|
||||
|
||||
private:
|
||||
std::unique_ptr<ReactNativeFeatureFlagsProvider> currentProvider_;
|
||||
std::vector<const char*> accessedFeatureFlags_;
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` std::optional<${getCxxTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)}> ${flagName}_;`,
|
||||
)
|
||||
.join('\n')}
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
`);
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getCxxTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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/ReactNativeFeatureFlagsProvider.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
|
||||
public:
|
||||
ReactNativeFeatureFlagsDefaults() = default;
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` ${getCxxTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)} ${flagName}() override {
|
||||
return ${JSON.stringify(flagConfig.defaultValue)};
|
||||
}`,
|
||||
)
|
||||
.join('\n\n')}
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
`);
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getCxxTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class ReactNativeFeatureFlagsProvider {
|
||||
public:
|
||||
virtual ~ReactNativeFeatureFlagsProvider() = default;
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` virtual ${getCxxTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)} ${flagName}() = 0;`,
|
||||
)
|
||||
.join('\n')}
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
`);
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getCxxTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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}
|
||||
|
||||
#include "NativeReactNativeFeatureFlags.h"
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
|
||||
#include "Plugins.h"
|
||||
|
||||
std::shared_ptr<facebook::react::TurboModule>
|
||||
NativeReactNativeFeatureFlagsModuleProvider(
|
||||
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
|
||||
return std::make_shared<facebook::react::NativeReactNativeFeatureFlags>(
|
||||
std::move(jsInvoker));
|
||||
}
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
NativeReactNativeFeatureFlags::NativeReactNativeFeatureFlags(
|
||||
std::shared_ptr<CallInvoker> jsInvoker)
|
||||
: NativeReactNativeFeatureFlagsCxxSpec(std::move(jsInvoker)) {}
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
`${getCxxTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)} NativeReactNativeFeatureFlags::${flagName}(
|
||||
jsi::Runtime& /*runtime*/) {
|
||||
return ReactNativeFeatureFlags::${flagName}();
|
||||
}`,
|
||||
)
|
||||
.join('\n\n')}
|
||||
|
||||
} // namespace facebook::react
|
||||
`);
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DO_NOT_MODIFY_COMMENT,
|
||||
getCxxTypeFromDefaultValue,
|
||||
} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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 <FBReactNativeSpec/FBReactNativeSpecJSI.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class NativeReactNativeFeatureFlags
|
||||
: public NativeReactNativeFeatureFlagsCxxSpec<
|
||||
NativeReactNativeFeatureFlags>,
|
||||
std::enable_shared_from_this<NativeReactNativeFeatureFlags> {
|
||||
public:
|
||||
NativeReactNativeFeatureFlags(std::shared_ptr<CallInvoker> jsInvoker);
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` ${getCxxTypeFromDefaultValue(
|
||||
flagConfig.defaultValue,
|
||||
)} ${flagName}(jsi::Runtime& runtime);`,
|
||||
)
|
||||
.join('\n\n')}
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
`);
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {DO_NOT_MODIFY_COMMENT} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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()}
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
${DO_NOT_MODIFY_COMMENT}
|
||||
|
||||
import type {TurboModule} from '../../../Libraries/TurboModule/RCTExport';
|
||||
|
||||
import * as TurboModuleRegistry from '../../../Libraries/TurboModule/TurboModuleRegistry';
|
||||
|
||||
export interface Spec extends TurboModule {
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` +${flagName}?: () => ${typeof flagConfig.defaultValue};`,
|
||||
)
|
||||
.join('\n')}
|
||||
}
|
||||
|
||||
const NativeReactNativeFeatureFlags: ?Spec = TurboModuleRegistry.get<Spec>(
|
||||
'NativeReactNativeFeatureFlagsCxx',
|
||||
);
|
||||
|
||||
export default NativeReactNativeFeatureFlags;
|
||||
`);
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {DO_NOT_MODIFY_COMMENT} = require('../../utils');
|
||||
const signedsource = require('signedsource');
|
||||
|
||||
module.exports = config =>
|
||||
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()}
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
${DO_NOT_MODIFY_COMMENT}
|
||||
|
||||
import {
|
||||
type Getter,
|
||||
createJavaScriptFlagGetter,
|
||||
createNativeFlagGetter,
|
||||
setOverrides,
|
||||
} from './ReactNativeFeatureFlagsBase';
|
||||
|
||||
export type ReactNativeFeatureFlagsJsOnly = {
|
||||
${Object.entries(config.jsOnly)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` ${flagName}: Getter<${typeof flagConfig.defaultValue}>,`,
|
||||
)
|
||||
.join('\n')}
|
||||
};
|
||||
|
||||
export type ReactNativeFeatureFlagsJsOnlyOverrides = Partial<ReactNativeFeatureFlagsJsOnly>;
|
||||
|
||||
export type ReactNativeFeatureFlags = {
|
||||
...ReactNativeFeatureFlagsJsOnly,
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
` ${flagName}: Getter<${typeof flagConfig.defaultValue}>,`,
|
||||
)
|
||||
.join('\n')}
|
||||
}
|
||||
|
||||
${Object.entries(config.jsOnly)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
`/**
|
||||
* ${flagConfig.description}
|
||||
*/
|
||||
export const ${flagName}: Getter<${typeof flagConfig.defaultValue}> = createJavaScriptFlagGetter('${flagName}', ${JSON.stringify(
|
||||
flagConfig.defaultValue,
|
||||
)});`,
|
||||
)
|
||||
.join('\n\n')}
|
||||
|
||||
${Object.entries(config.common)
|
||||
.map(
|
||||
([flagName, flagConfig]) =>
|
||||
`/**
|
||||
* ${flagConfig.description}
|
||||
*/
|
||||
export const ${flagName}: Getter<${typeof flagConfig.defaultValue}> = createNativeFlagGetter('${flagName}', ${JSON.stringify(
|
||||
flagConfig.defaultValue,
|
||||
)});`,
|
||||
)
|
||||
.join('\n')}
|
||||
|
||||
/**
|
||||
* Overrides the feature flags with the provided methods.
|
||||
* NOTE: Only JS-only flags can be overridden from JavaScript using this API.
|
||||
*/
|
||||
export const override = setOverrides;
|
||||
`);
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
const generateFiles = require('./generateFiles');
|
||||
const path = require('path');
|
||||
|
||||
const REACT_NATIVE_PACKAGE_ROOT = path.join(__dirname, '..', '..');
|
||||
|
||||
function update() {
|
||||
generateFiles(
|
||||
{
|
||||
configPath: path.join(__dirname, 'ReactNativeFeatureFlags.json'),
|
||||
jsPath: path.join(
|
||||
REACT_NATIVE_PACKAGE_ROOT,
|
||||
'src',
|
||||
'private',
|
||||
'featureflags',
|
||||
),
|
||||
commonCxxPath: path.join(
|
||||
REACT_NATIVE_PACKAGE_ROOT,
|
||||
'ReactCommon',
|
||||
'react',
|
||||
'featureflags',
|
||||
),
|
||||
commonNativeModuleCxxPath: path.join(
|
||||
REACT_NATIVE_PACKAGE_ROOT,
|
||||
'ReactCommon',
|
||||
'react',
|
||||
'nativemodule',
|
||||
'featureflags',
|
||||
),
|
||||
androidPath: path.join(
|
||||
REACT_NATIVE_PACKAGE_ROOT,
|
||||
'ReactAndroid',
|
||||
'src',
|
||||
'main',
|
||||
'java',
|
||||
'com',
|
||||
'facebook',
|
||||
'react',
|
||||
'internal',
|
||||
'featureflags',
|
||||
),
|
||||
androidJniPath: path.join(
|
||||
REACT_NATIVE_PACKAGE_ROOT,
|
||||
'ReactAndroid',
|
||||
'src',
|
||||
'main',
|
||||
'jni',
|
||||
'react',
|
||||
'featureflags',
|
||||
),
|
||||
},
|
||||
{
|
||||
verifyUnchanged: process.argv.includes('--verify-unchanged'),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
update();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
getCxxTypeFromDefaultValue: defaultValue => {
|
||||
switch (typeof defaultValue) {
|
||||
case 'boolean':
|
||||
return 'bool';
|
||||
case 'number':
|
||||
return 'int';
|
||||
case 'string':
|
||||
return 'std::string';
|
||||
default:
|
||||
throw new Error(
|
||||
`Unsupported default value type: ${typeof defaultValue}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
getKotlinTypeFromDefaultValue: defaultValue => {
|
||||
switch (typeof defaultValue) {
|
||||
case 'boolean':
|
||||
return 'Boolean';
|
||||
case 'number':
|
||||
return 'Int';
|
||||
case 'string':
|
||||
return 'String';
|
||||
default:
|
||||
throw new Error(
|
||||
`Unsupported default value type: ${typeof defaultValue}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
DO_NOT_MODIFY_COMMENT: `/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/`,
|
||||
};
|
||||
@@ -124,6 +124,7 @@ def use_react_native! (
|
||||
pod 'React-cxxreact', :path => "#{prefix}/ReactCommon/cxxreact"
|
||||
pod 'React-debug', :path => "#{prefix}/ReactCommon/react/debug"
|
||||
pod 'React-utils', :path => "#{prefix}/ReactCommon/react/utils"
|
||||
pod 'React-featureflags', :path => "#{prefix}/ReactCommon/react/featureflags"
|
||||
pod 'React-Mapbuffer', :path => "#{prefix}/ReactCommon"
|
||||
pod 'React-jserrorhandler', :path => "#{prefix}/ReactCommon/jserrorhandler"
|
||||
pod 'React-nativeconfig', :path => "#{prefix}/ReactCommon"
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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<<ec82a5158f766de0467ab9a38e4ffb15>>
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
import type {TurboModule} from '../../../Libraries/TurboModule/RCTExport';
|
||||
|
||||
import * as TurboModuleRegistry from '../../../Libraries/TurboModule/TurboModuleRegistry';
|
||||
|
||||
export interface Spec extends TurboModule {
|
||||
+commonTestFlag?: () => boolean;
|
||||
}
|
||||
|
||||
const NativeReactNativeFeatureFlags: ?Spec = TurboModuleRegistry.get<Spec>(
|
||||
'NativeReactNativeFeatureFlagsCxx',
|
||||
);
|
||||
|
||||
export default NativeReactNativeFeatureFlags;
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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<<b554016876afbf53c25670806a5ff581>>
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
/**
|
||||
* IMPORTANT: Do NOT modify this file directly.
|
||||
*
|
||||
* To change the definition of the flags, edit
|
||||
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
|
||||
*
|
||||
* To regenerate this code, run the following script from the repo root:
|
||||
* yarn featureflags-update
|
||||
*/
|
||||
|
||||
import {
|
||||
type Getter,
|
||||
createJavaScriptFlagGetter,
|
||||
createNativeFlagGetter,
|
||||
setOverrides,
|
||||
} from './ReactNativeFeatureFlagsBase';
|
||||
|
||||
export type ReactNativeFeatureFlagsJsOnly = {
|
||||
jsOnlyTestFlag: Getter<boolean>,
|
||||
};
|
||||
|
||||
export type ReactNativeFeatureFlagsJsOnlyOverrides = Partial<ReactNativeFeatureFlagsJsOnly>;
|
||||
|
||||
export type ReactNativeFeatureFlags = {
|
||||
...ReactNativeFeatureFlagsJsOnly,
|
||||
commonTestFlag: Getter<boolean>,
|
||||
}
|
||||
|
||||
/**
|
||||
* JS-only flag for testing. Do NOT modify.
|
||||
*/
|
||||
export const jsOnlyTestFlag: Getter<boolean> = createJavaScriptFlagGetter('jsOnlyTestFlag', false);
|
||||
|
||||
/**
|
||||
* Common flag for testing. Do NOT modify.
|
||||
*/
|
||||
export const commonTestFlag: Getter<boolean> = createNativeFlagGetter('commonTestFlag', false);
|
||||
|
||||
/**
|
||||
* Overrides the feature flags with the provided methods.
|
||||
* NOTE: Only JS-only flags can be overridden from JavaScript using this API.
|
||||
*/
|
||||
export const override = setOverrides;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {
|
||||
ReactNativeFeatureFlagsJsOnly,
|
||||
ReactNativeFeatureFlagsJsOnlyOverrides,
|
||||
} from './ReactNativeFeatureFlags';
|
||||
|
||||
import NativeReactNativeFeatureFlags from './NativeReactNativeFeatureFlags';
|
||||
|
||||
const accessedFeatureFlags: Set<string> = new Set();
|
||||
const overrides: ReactNativeFeatureFlagsJsOnlyOverrides = {};
|
||||
|
||||
export type Getter<T> = () => T;
|
||||
|
||||
function createGetter<T: boolean | number | string>(
|
||||
configName: string,
|
||||
customValueGetter: Getter<?T>,
|
||||
defaultValue: T,
|
||||
): Getter<T> {
|
||||
let cachedValue: ?T;
|
||||
|
||||
return () => {
|
||||
if (cachedValue == null) {
|
||||
accessedFeatureFlags.add(configName);
|
||||
cachedValue = customValueGetter() ?? defaultValue;
|
||||
}
|
||||
return cachedValue;
|
||||
};
|
||||
}
|
||||
|
||||
export function createJavaScriptFlagGetter<
|
||||
K: $Keys<ReactNativeFeatureFlagsJsOnly>,
|
||||
>(
|
||||
configName: K,
|
||||
defaultValue: ReturnType<ReactNativeFeatureFlagsJsOnly[K]>,
|
||||
): Getter<ReturnType<ReactNativeFeatureFlagsJsOnly[K]>> {
|
||||
return createGetter(
|
||||
configName,
|
||||
() => overrides[configName]?.(),
|
||||
defaultValue,
|
||||
);
|
||||
}
|
||||
|
||||
type NativeFeatureFlags = $NonMaybeType<typeof NativeReactNativeFeatureFlags>;
|
||||
|
||||
export function createNativeFlagGetter<K: $Keys<NativeFeatureFlags>>(
|
||||
configName: K,
|
||||
defaultValue: ReturnType<$NonMaybeType<NativeFeatureFlags[K]>>,
|
||||
): Getter<ReturnType<$NonMaybeType<NativeFeatureFlags[K]>>> {
|
||||
return createGetter(
|
||||
configName,
|
||||
() => NativeReactNativeFeatureFlags?.[configName]?.(),
|
||||
defaultValue,
|
||||
);
|
||||
}
|
||||
|
||||
export function getOverrides(): ?ReactNativeFeatureFlagsJsOnlyOverrides {
|
||||
return overrides;
|
||||
}
|
||||
|
||||
export function setOverrides(
|
||||
newOverrides: ReactNativeFeatureFlagsJsOnlyOverrides,
|
||||
): void {
|
||||
if (accessedFeatureFlags.size > 0) {
|
||||
const accessedFeatureFlagsStr = Array.from(accessedFeatureFlags).join(', ');
|
||||
throw new Error(
|
||||
`Feature flags were accessed before being overridden: ${accessedFeatureFlagsStr}`,
|
||||
);
|
||||
}
|
||||
|
||||
Object.assign(overrides, newOverrides);
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
describe('ReactNativeFeatureFlags', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it('should provide default values for common flags if the native module is NOT available', () => {
|
||||
const ReactNativeFeatureFlags = require('../ReactNativeFeatureFlags');
|
||||
expect(ReactNativeFeatureFlags.commonTestFlag()).toBe(false);
|
||||
});
|
||||
|
||||
it('should access and cache common flags from the native module if it is available', () => {
|
||||
const commonTestFlagFn = jest.fn(() => true);
|
||||
|
||||
jest.doMock('../NativeReactNativeFeatureFlags', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
commonTestFlag: commonTestFlagFn,
|
||||
},
|
||||
}));
|
||||
|
||||
const ReactNativeFeatureFlags = require('../ReactNativeFeatureFlags');
|
||||
|
||||
expect(commonTestFlagFn).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(ReactNativeFeatureFlags.commonTestFlag()).toBe(true);
|
||||
expect(commonTestFlagFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(ReactNativeFeatureFlags.commonTestFlag()).toBe(true);
|
||||
expect(commonTestFlagFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should provide default values for JS-only flags', () => {
|
||||
const ReactNativeFeatureFlags = require('../ReactNativeFeatureFlags');
|
||||
expect(ReactNativeFeatureFlags.jsOnlyTestFlag()).toBe(false);
|
||||
});
|
||||
|
||||
it('should access and cache overridden JS-only flags', () => {
|
||||
const ReactNativeFeatureFlags = require('../ReactNativeFeatureFlags');
|
||||
|
||||
const jsOnlyTestFlagFn = jest.fn(() => true);
|
||||
ReactNativeFeatureFlags.override({
|
||||
jsOnlyTestFlag: jsOnlyTestFlagFn,
|
||||
});
|
||||
|
||||
expect(jsOnlyTestFlagFn).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(ReactNativeFeatureFlags.jsOnlyTestFlag()).toBe(true);
|
||||
expect(jsOnlyTestFlagFn).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(ReactNativeFeatureFlags.jsOnlyTestFlag()).toBe(true);
|
||||
expect(jsOnlyTestFlagFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should throw an error if any of the flags has been accessed before overridding', () => {
|
||||
const ReactNativeFeatureFlags = require('../ReactNativeFeatureFlags');
|
||||
|
||||
ReactNativeFeatureFlags.commonTestFlag();
|
||||
|
||||
expect(() =>
|
||||
ReactNativeFeatureFlags.override({
|
||||
jsOnlyTestFlag: () => true,
|
||||
}),
|
||||
).toThrow(
|
||||
'Feature flags were accessed before being overridden: commonTestFlag',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -33,6 +33,13 @@ function describe(message) {
|
||||
try {
|
||||
echo('Executing JavaScript tests');
|
||||
|
||||
describe('Test: feature flags codegen');
|
||||
if (exec(`${YARN_BINARY} run featureflags-check`).code) {
|
||||
echo('Failed to run featureflags check.');
|
||||
exitCode = 1;
|
||||
throw Error(exitCode);
|
||||
}
|
||||
|
||||
describe('Test: eslint');
|
||||
if (exec(`${YARN_BINARY} run lint`).code) {
|
||||
echo('Failed to run eslint.');
|
||||
|
||||
Reference in New Issue
Block a user