Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ munge_underscores=true

module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/packages/react-native/index.js'
module.name_mapper='^react-native/asset-registry$' -> '<PROJECT_ROOT>/packages/react-native/src/asset-registry.js'
module.name_mapper='^react-native/node-api$' -> '<PROJECT_ROOT>/packages/react-native/src/node-api.js'
module.name_mapper='^react-native/package.json$' -> '<PROJECT_ROOT>/packages/react-native/package.json'
module.name_mapper='^react-native/react-private-interface$' -> '<PROJECT_ROOT>/packages/react-native/src/react-private-interface.js'
module.name_mapper='^react-native/setup-env$' -> '<PROJECT_ROOT>/packages/react-native/src/setup-env.js'
Expand Down
1 change: 1 addition & 0 deletions __docs__/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ TODO: Explain the different components of React Native at a high level.
- [Animation Backend](../packages/react-native/ReactCommon/react/renderer/animationbackend/__docs__/AnimationBackend.md)
- [Native Animated](../packages/react-native/ReactCommon/react/renderer/animated/__docs__/NativeAnimated.md)
- Native Modules / TurboModules
- [Node-API](../packages/react-native/ReactCommon/react/nativemodule/nodeapi/__docs__/README.md)
- JS Runtime
- [Event Loop](../packages/react-native/ReactCommon/react/renderer/runtimescheduler/__docs__/README.md)
- Globals and environment setup
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ react.internal.useHermesStable=false

# Controls whether to use Hermes from nightly builds. This will speed up builds
# but should NOT be turned on for CI or release builds.
react.internal.useHermesNightly=true
react.internal.useHermesNightly=false
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.facebook.react.utils.JsonUtils
import com.facebook.react.utils.NdkConfiguratorUtils.configureReactNativeNdk
import com.facebook.react.utils.NdkConfiguratorUtils.configureStrictCxxApiForLibrary
import com.facebook.react.utils.NdkConfiguratorUtils.configureStubPchGeneration
import com.facebook.react.utils.ProjectUtils.isNodeApiEnabled
import com.facebook.react.utils.ProjectUtils.isStrictCxxApiEnabled
import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson
import com.facebook.react.utils.PropertyUtils
Expand Down Expand Up @@ -99,6 +100,11 @@ class ReactPlugin : Plugin<Project> {
readVersionAndGroupStrings(project, propertiesFile, hermesVersionPropertiesFile)
configureDependencies(project, versionAndGroupStrings)
configureRepositories(project, versionAndGroupStrings)
if (project.isNodeApiEnabled && !isReactNativeBuiltFromSource(project, reactNativeDir)) {
project.logger.warn(
"WARNING: 'nodeApiEnabled' has no effect on the prebuilt React Native AAR, which doesn't include Node-API. Build React Native from source to load Node-API addons.",
)
}
}

configureReactNativeNdk(project, extension)
Expand Down Expand Up @@ -154,6 +160,12 @@ class ReactPlugin : Plugin<Project> {
}
}

private fun isReactNativeBuiltFromSource(project: Project, reactNativeDir: File): Boolean =
project.rootProject.findProject(":packages:react-native:ReactAndroid") != null ||
project.gradle.includedBuilds.any {
it.projectDir.canonicalFile == reactNativeDir.canonicalFile
}

private fun checkJvmVersion(project: Project) {
val jvmVersion = Jvm.current().javaVersion?.majorVersion
if ((jvmVersion?.toIntOrNull() ?: 0) <= 16) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.android.build.api.variant.Variant
import com.facebook.react.ReactExtension
import com.facebook.react.internal.StubPchBuildService
import com.facebook.react.utils.ProjectUtils.getReactNativeArchitectures
import com.facebook.react.utils.ProjectUtils.isNodeApiEnabled
import com.facebook.react.utils.ProjectUtils.isStrictCxxApiEnabled
import java.io.File
import org.gradle.api.Project
Expand Down Expand Up @@ -60,6 +61,11 @@ internal object NdkConfiguratorUtils {
if (project.isStrictCxxApiEnabled && cmakeArgs.none { it.startsWith("-DRN_STRICT_API") }) {
cmakeArgs.add("-DRN_STRICT_API=ON")
}
if (
project.isNodeApiEnabled && cmakeArgs.none { it.startsWith("-DRCT_NODE_API_ENABLED") }
) {
cmakeArgs.add("-DRCT_NODE_API_ENABLED=ON")
}

val architectures = project.getReactNativeArchitectures()
// abiFilters are split ABI are not compatible each other, so we set the abiFilters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import com.facebook.react.ReactExtension
import com.facebook.react.model.ModelPackageJson
import com.facebook.react.utils.PropertyUtils.EDGE_TO_EDGE_ENABLED
import com.facebook.react.utils.PropertyUtils.HERMES_ENABLED
import com.facebook.react.utils.PropertyUtils.NODE_API_ENABLED
import com.facebook.react.utils.PropertyUtils.REACT_NATIVE_ARCHITECTURES
import com.facebook.react.utils.PropertyUtils.RESET_METRO_CACHE_BEFORE_BUNDLING
import com.facebook.react.utils.PropertyUtils.SCOPED_EDGE_TO_EDGE_ENABLED
import com.facebook.react.utils.PropertyUtils.SCOPED_HERMES_ENABLED
import com.facebook.react.utils.PropertyUtils.SCOPED_NODE_API_ENABLED
import com.facebook.react.utils.PropertyUtils.SCOPED_REACT_NATIVE_ARCHITECTURES
import com.facebook.react.utils.PropertyUtils.SCOPED_RESET_METRO_CACHE_BEFORE_BUNDLING
import com.facebook.react.utils.PropertyUtils.SCOPED_STRICT_CXX_API_ENABLED
Expand Down Expand Up @@ -67,6 +69,13 @@ internal object ProjectUtils {
(project.hasProperty(SCOPED_STRICT_CXX_API_ENABLED) &&
project.property(SCOPED_STRICT_CXX_API_ENABLED).toString().toBoolean())

internal val Project.isNodeApiEnabled: Boolean
get() =
(project.hasProperty(NODE_API_ENABLED) &&
project.property(NODE_API_ENABLED).toString().toBoolean()) ||
(project.hasProperty(SCOPED_NODE_API_ENABLED) &&
project.property(SCOPED_NODE_API_ENABLED).toString().toBoolean())

internal val Project.useThirdPartyJSC: Boolean
get() =
(project.hasProperty(USE_THIRD_PARTY_JSC) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ object PropertyUtils {
const val STRICT_CXX_API_ENABLED = "strictCxxApiEnabled"
const val SCOPED_STRICT_CXX_API_ENABLED = "react.strictCxxApiEnabled"

/**
* Public property that builds React Native and Hermes with Node-API when they're built from
* source, and defines RCT_NODE_API_ENABLED for the app's native build
*/
const val NODE_API_ENABLED = "nodeApiEnabled"
const val SCOPED_NODE_API_ENABLED = "react.nodeApiEnabled"

/** Public property that excludes jsctooling from core */
const val USE_THIRD_PARTY_JSC = "useThirdPartyJSC"
const val SCOPED_USE_THIRD_PARTY_JSC = "react.useThirdPartyJSC"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.facebook.react.utils.ProjectUtils.getReactNativeArchitectures
import com.facebook.react.utils.ProjectUtils.isEdgeToEdgeEnabled
import com.facebook.react.utils.ProjectUtils.isHermesEnabled
import com.facebook.react.utils.ProjectUtils.isNewArchEnabled
import com.facebook.react.utils.ProjectUtils.isNodeApiEnabled
import com.facebook.react.utils.ProjectUtils.isStrictCxxApiEnabled
import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson
import com.facebook.react.utils.ProjectUtils.shouldResetMetroCacheBeforeBundling
Expand Down Expand Up @@ -205,6 +206,32 @@ class ProjectUtilsTest {
assertThat(project.isStrictCxxApiEnabled).isFalse()
}

@Test
fun isNodeApiEnabled_returnsFalseByDefault() {
assertThat(createProject().isNodeApiEnabled).isFalse()
}

@Test
fun isNodeApiEnabled_withEnabledViaProperty_returnsTrue() {
val project = createProject()
project.extensions.extraProperties.set("nodeApiEnabled", "true")
assertThat(project.isNodeApiEnabled).isTrue()
}

@Test
fun isNodeApiEnabled_withEnabledViaScopedProperty_returnsTrue() {
val project = createProject()
project.extensions.extraProperties.set("react.nodeApiEnabled", "true")
assertThat(project.isNodeApiEnabled).isTrue()
}

@Test
fun isNodeApiEnabled_withFalseViaProperty_returnsFalse() {
val project = createProject()
project.extensions.extraProperties.set("nodeApiEnabled", "false")
assertThat(project.isNodeApiEnabled).isFalse()
}

@Test
fun needsCodegenFromPackageJson_withCodegenConfigInPackageJson_returnsTrue() {
val project = createProject()
Expand Down
18 changes: 16 additions & 2 deletions packages/react-native/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ let REMOVE_LEGACY_MODULE_INTEROP =
let REMOVE_LEGACY_COMPONENT_INTEROP =
ProcessInfo.processInfo.environment["RCT_REMOVE_LEGACY_COMPONENT_INTEROP"] == "1"

let NODE_API_ENABLED = ProcessInfo.processInfo.environment["RCT_NODE_API_ENABLED"] == "1"

/**
This is the `Package.swift` file that allows to build React Native core using Swift PM.
To build React Native, you need to follow these steps:
Expand Down Expand Up @@ -343,6 +345,14 @@ let reactTurboModuleCoreMicrotasks = RNTarget(
dependencies: [.reactNativeDependencies, .reactDebug, .reactFeatureFlags, .reactUtils, .reactPerfLogger, .reactCxxReact, .reactTurboModuleCore]
)

/// React-nodeapinativemodule.podspec
let reactNodeApiNativeModule = RNTarget(
name: .reactNodeApiNativeModule,
path: "ReactCommon/react/nativemodule/nodeapi",
searchPaths: [".build/artifacts/hermes/destroot/include/hermes/napi"],
dependencies: [.reactNativeDependencies, .jsi, .logger, .reactCxxReact, .reactTurboModuleCore, .hermesPrebuilt]
)

/// React-idlecallback.podspec
let reactIdleCallbacksNativeModule = RNTarget(
name: .reactIdleCallbacksNativeModule,
Expand Down Expand Up @@ -429,7 +439,8 @@ let reactRuntime = RNTarget(
name: .reactRuntime,
path: "ReactCommon/react/runtime",
excludedPaths: ["tests", "iostests", "platform"],
dependencies: [.reactNativeDependencies, .jsi, .reactJsiExecutor, .reactCxxReact, .reactJsErrorHandler, .reactPerformanceTimeline, .reactUtils, .reactFeatureFlags, .reactJsInspector, .reactJsiTooling, .reactHermes, .reactRuntimeScheduler, .hermesPrebuilt],
dependencies: [.reactNativeDependencies, .jsi, .reactJsiExecutor, .reactCxxReact, .reactJsErrorHandler, .reactPerformanceTimeline, .reactUtils, .reactFeatureFlags, .reactJsInspector, .reactJsiTooling, .reactHermes, .reactRuntimeScheduler, .hermesPrebuilt]
+ (NODE_API_ENABLED ? [.reactNodeApiNativeModule] : []),
defines: [
CXXSetting.define("HERMES_ENABLE_DEBUGGER", to: "1", .when(configuration: BuildConfiguration.debug))
]
Expand Down Expand Up @@ -745,6 +756,7 @@ let targets = [
reactTurboModuleCore,
reactTurboModuleCoreDefaults,
reactTurboModuleCoreMicrotasks,
reactNodeApiNativeModule,
reactIdleCallbacksNativeModule,
reactWebPerformanceNativeModule,
reactIntersectionObserverNativeModule,
Expand All @@ -757,7 +769,7 @@ let targets = [
reactAppDelegate,
reactSettings,
reactRuntimeExecutor,
]
].filter { NODE_API_ENABLED || $0.name != .reactNodeApiNativeModule }

// MARK: Package object

Expand Down Expand Up @@ -938,6 +950,7 @@ extension String {
static let reactTurboModuleCore = "ReactCommon/turbomodule/core"
static let reactTurboModuleCoreDefaults = "ReactCommon/turbomodule/core/defaults"
static let reactTurboModuleCoreMicrotasks = "ReactCommon/turbomodule/core/microtasks"
static let reactNodeApiNativeModule = "React-nodeapinativemodule"
static let reactIdleCallbacksNativeModule = "React-idlecallbacksnativemodule"
static let reactWebPerformanceNativeModule = "React-webperformancenativemodule"
static let reactIntersectionObserverNativeModule = "React-intersectionobservernativemodule"
Expand Down Expand Up @@ -982,6 +995,7 @@ extension Target {
let legacyInteropDefines: [CXXSetting] =
(REMOVE_LEGACY_MODULE_INTEROP ? [.define("RCT_REMOVE_LEGACY_MODULE_INTEROP", to: "1")] : [])
+ (REMOVE_LEGACY_COMPONENT_INTEROP ? [.define("RCT_REMOVE_LEGACY_COMPONENT_INTEROP", to: "1")] : [])
+ (NODE_API_ENABLED ? [.define("RCT_NODE_API_ENABLED", to: "1")] : [])

// Every target built through this factory is React Native's own, so RN_BUILDING
// keeps the react/cxxstableapi guards inert for internal sources. cxxSettings are
Expand Down
8 changes: 8 additions & 0 deletions packages/react-native/ReactAndroid/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,11 @@ fun reactNativeArchitectures(): List<String> {
return value?.toString()?.split(",") ?: listOf("armeabi-v7a", "x86", "x86_64", "arm64-v8a")
}

fun nodeApiEnabled(): Boolean {
val value = project.properties["nodeApiEnabled"] ?: project.properties["react.nodeApiEnabled"]
return value?.toString()?.toBoolean() ?: false
}

fun enableWarningsAsErrors(): Boolean {
val value = project.properties["enableWarningsAsErrors"]
return value?.toString()?.toBoolean() ?: false
Expand Down Expand Up @@ -620,6 +625,9 @@ android {
"-DANDROID_TOOLCHAIN=clang",
"-DCMAKE_POLICY_DEFAULT_CMP0069=NEW",
)
if (nodeApiEnabled()) {
arguments("-DRCT_NODE_API_ENABLED=ON")
}

targets(
"reactnative",
Expand Down
10 changes: 10 additions & 0 deletions packages/react-native/ReactAndroid/hermes-engine/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ val prepareHeadersForPrefab by
dependsOn(buildHermesC)
from("$hermesDir/API")
from("$hermesDir/public")
from("$hermesDir/include") { include("hermes/napi/**") }
include("**/*.h")
exclude("jsi/**")
into(prefabHeadersDir)
Expand Down Expand Up @@ -260,6 +261,7 @@ val prepareHeadersForPrefabWithDebugger by
dependsOn(buildHermesCWithDebugger)
from("$hermesDir/API")
from("$hermesDir/public")
from("$hermesDir/include") { include("hermes/napi/**") }
include("**/*.h")
exclude("jsi/**")
into(prefabHeadersDir)
Expand Down Expand Up @@ -294,6 +296,11 @@ fun windowsAwareCommandLine(vararg commands: String): List<String> {
return result
}

fun nodeApiEnabled(): Boolean {
val value = project.properties["nodeApiEnabled"] ?: project.properties["react.nodeApiEnabled"]
return value?.toString()?.toBoolean() ?: false
}

fun reactNativeArchitectures(): List<String> {
val value = project.properties["reactNativeArchitectures"]
return value?.toString()?.split(",") ?: listOf("armeabi-v7a", "x86", "x86_64", "arm64-v8a")
Expand Down Expand Up @@ -346,6 +353,9 @@ android {
)

arguments("-DHERMESVM_HEAP_HV_MODE=HEAP_HV_PREFER32")
if (!nodeApiEnabled()) {
arguments("-DHERMES_ENABLE_NAPI=False")
}

targets("hermesvm")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ add_react_third_party_ndk_subdir(folly)
# the prefab artifacts through ReactNative-application.cmake).
add_compile_definitions(RN_BUILDING)

if(RCT_NODE_API_ENABLED)
add_compile_definitions(RCT_NODE_API_ENABLED)
endif()

# Common targets
add_react_common_subdir(yoga)
add_react_common_subdir(runtimeexecutor)
Expand Down Expand Up @@ -139,6 +143,9 @@ add_react_common_subdir(react/nativemodule/microtasks)
add_react_common_subdir(react/nativemodule/idlecallbacks)
add_react_common_subdir(react/nativemodule/intersectionobserver)
add_react_common_subdir(react/nativemodule/mutationobserver)
if(RCT_NODE_API_ENABLED)
add_react_common_subdir(react/nativemodule/nodeapi)
endif()
add_react_common_subdir(react/nativemodule/resizeobserver)
add_react_common_subdir(react/nativemodule/viewtransition)
add_react_common_subdir(react/nativemodule/webperformance)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ add_library(hermestooling
$<TARGET_OBJECTS:jni_lib_merge_glue>
$<TARGET_OBJECTS:jsijniprofiler>
)
if(RCT_NODE_API_ENABLED)
target_sources(hermestooling PRIVATE $<TARGET_OBJECTS:react_nativemodule_nodeapi>)
target_include_directories(hermestooling
PUBLIC
$<TARGET_PROPERTY:react_nativemodule_nodeapi,INTERFACE_INCLUDE_DIRECTORIES>
)
endif()
target_merge_so(hermestooling)
target_link_libraries(hermestooling
PUBLIC
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ target_link_libraries(
jsi
jsitooling
fbjni
glog_init
bridgeless
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@
* LICENSE file in the root directory of this source tree.
*/

#include <fb/glog_init.h>
#include <fbjni/fbjni.h>
#include <react/jni/JReactMarker.h>

#include "JJSTimerExecutor.h"
#include "JReactHostInspectorTarget.h"
#include "JReactInstance.h"

#ifndef WITH_GLOGINIT
#define WITH_GLOGINIT 1
#endif

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*unused*/) {
return facebook::jni::initialize(vm, [] {
#if WITH_GLOGINIT
facebook::gloginit::initialize();
#endif
facebook::react::JReactMarker::setLogPerfMarkerIfNeeded();
facebook::react::JReactInstance::registerNatives();
facebook::react::JJSTimerExecutor::registerNatives();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 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)

include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)

file(GLOB react_nativemodule_nodeapi_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_nativemodule_nodeapi OBJECT ${react_nativemodule_nodeapi_SRC})

target_include_directories(react_nativemodule_nodeapi PUBLIC ${REACT_COMMON_DIR})

get_target_property(hermes_include_dirs hermes-engine::hermesvm INTERFACE_INCLUDE_DIRECTORIES)
list(TRANSFORM hermes_include_dirs APPEND /hermes/napi)
target_include_directories(react_nativemodule_nodeapi PUBLIC ${hermes_include_dirs})

target_link_libraries(react_nativemodule_nodeapi
hermes-engine::hermesvm
jsi
logger
react_codegen_rncore
react_cxxreact
react_cxxstableapi
)
target_compile_reactnative_options(react_nativemodule_nodeapi PRIVATE)
Loading
Loading