diff --git a/.github/actions/diff-js-api-breaking-changes/action.yml b/.github/actions/diff-js-api-breaking-changes/action.yml index bc45b1b207aa..c3b1ae212480 100644 --- a/.github/actions/diff-js-api-breaking-changes/action.yml +++ b/.github/actions/diff-js-api-breaking-changes/action.yml @@ -17,6 +17,11 @@ runs: env: SCRATCH_DIR: ${{ runner.temp }}/diff-js-api-breaking-changes run: | + # Check if the file was changed in this PR, if not, exit early without saving to the output.json + git diff --name-status + git diff --name-status | awk '{print $2}' + git diff --name-status | awk '{print $2}' | grep 'packages/react-native/ReactNativeApi.d.ts' || echo "ReactNativeApi.d.ts not found, exiting" && exit 0 + node ./scripts/js-api/diff-api-snapshot \ ${{ github.workspace }}/packages/react-native/ReactNativeApi.d.ts \ $SCRATCH_DIR/ReactNativeApi-after.d.ts \ diff --git a/.github/workflows/danger-pr.yml b/.github/workflows/danger-pr.yml index cf28b788105a..06daf7ee8378 100644 --- a/.github/workflows/danger-pr.yml +++ b/.github/workflows/danger-pr.yml @@ -15,7 +15,7 @@ permissions: jobs: danger: runs-on: ubuntu-latest - if: github.repository == 'facebook/react-native' + if: github.repository == 'coado/react-native' steps: - uses: actions/checkout@v4 - name: Setup Node.js diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/AnimatedCxxReactPackage.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/AnimatedCxxReactPackage.kt new file mode 100644 index 000000000000..658a5a8ba1e3 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/AnimatedCxxReactPackage.kt @@ -0,0 +1,26 @@ +/* + * 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.animated + import com.facebook.jni.HybridData + import com.facebook.react.bridge.ReactApplicationContext + import com.facebook.react.runtime.cxxreactpackage.CxxReactPackage + import com.facebook.soloader.SoLoader + + public class AnimatedCxxReactPackage(public val context: ReactApplicationContext) : CxxReactPackage(null) { + init { + mHybridData = initHybrid() + } + + private external fun initHybrid(): HybridData + + public companion object { + init { + SoLoader.loadLibrary("react_animatedjni") + } + } + } \ No newline at end of file diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHost.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHost.kt index 5aae109d5819..bd2a50c1d447 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHost.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHost.kt @@ -13,6 +13,7 @@ import android.content.Context import com.facebook.react.ReactHost import com.facebook.react.ReactNativeHost import com.facebook.react.ReactPackage +import com.facebook.react.animated.AnimatedCxxReactPackage import com.facebook.react.bridge.JSBundleLoader import com.facebook.react.bridge.ReactContext import com.facebook.react.common.annotations.UnstableReactNativeAPI @@ -85,6 +86,7 @@ public object DefaultReactHost { } val defaultTmmDelegateBuilder = DefaultTurboModuleManagerDelegate.Builder() cxxReactPackageProviders.forEach { defaultTmmDelegateBuilder.addCxxReactPackage(it) } + defaultTmmDelegateBuilder.addCxxReactPackage { context -> AnimatedCxxReactPackage(context) } val defaultReactHostDelegate = DefaultReactHostDelegate( jsMainModulePath = jsMainModulePath, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 3eb6dec1a899..ddf478d38f4e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -27,7 +27,7 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun cdpInteractionMetricsEnabled(): Boolean = false - override fun cxxNativeAnimatedEnabled(): Boolean = false + override fun cxxNativeAnimatedEnabled(): Boolean = true override fun cxxNativeAnimatedRemoveJsSync(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/soloader/OpenSourceMergedSoMapping.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/soloader/OpenSourceMergedSoMapping.kt index c8ba2b2190c4..c17f6f180874 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/soloader/OpenSourceMergedSoMapping.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/soloader/OpenSourceMergedSoMapping.kt @@ -26,6 +26,7 @@ public object OpenSourceMergedSoMapping : ExternalSoMapping { "jsinspector", "mapbufferjni", "react_devsupportjni", + "react_animatedjni", "react_featureflagsjni", "react_newarchdefaults", "reactnativeblob", @@ -55,6 +56,7 @@ public object OpenSourceMergedSoMapping : ExternalSoMapping { "jsinspector" -> libjsinspector_so() "mapbufferjni" -> libmapbufferjni_so() "react_devsupportjni" -> libreact_devsupportjni_so() + "react_animatedjni" -> libreact_animatedjni_so() "react_featureflagsjni" -> libreact_featureflagsjni_so() "react_newarchdefaults" -> libreact_newarchdefaults_so() "reactnative" -> libreactnative_so() @@ -84,6 +86,8 @@ public object OpenSourceMergedSoMapping : ExternalSoMapping { public external fun libreact_devsupportjni_so(): Int + public external fun libreact_animatedjni_so(): Int + public external fun libreact_featureflagsjni_so(): Int public external fun libreact_newarchdefaults_so(): Int diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index 04676e762177..dea8a2a51f58 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt @@ -83,6 +83,8 @@ add_react_common_subdir(react/featureflags) add_react_common_subdir(react/performance/cdpmetrics) add_react_common_subdir(react/performance/timeline) add_react_common_subdir(react/renderer/animations) +add_react_common_subdir(react/renderer/animated) +add_react_common_subdir(react/renderer/animationbackend) add_react_common_subdir(react/renderer/attributedstring) add_react_common_subdir(react/renderer/componentregistry) add_react_common_subdir(react/renderer/mounting) @@ -153,6 +155,7 @@ add_react_android_subdir(src/main/jni/react/runtime/cxxreactpackage) add_react_android_subdir(src/main/jni/react/runtime/jni) add_react_android_subdir(src/main/jni/react/runtime/hermes/jni) add_react_android_subdir(src/main/jni/react/devsupport) +add_react_android_subdir(src/main/jni/react/animated) # SoMerging Utils include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) @@ -198,6 +201,9 @@ add_library(reactnative $ $ $ + $ + $ + $ $ $ $ @@ -289,6 +295,9 @@ target_include_directories(reactnative $ $ $ + $ + $ + $ $ $ $ diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/animated/AnimatedCxxReactPackage.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/animated/AnimatedCxxReactPackage.cpp new file mode 100644 index 000000000000..fa4c0d3b848a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/animated/AnimatedCxxReactPackage.cpp @@ -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. + */ +#include "AnimatedCxxReactPackage.h" +#include +#include + +namespace facebook::react { + +AnimatedCxxReactPackage::AnimatedCxxReactPackage( + jni::alias_ref jobj) {} + +jni::local_ref +AnimatedCxxReactPackage::initHybrid( + jni::alias_ref jobj) { + return makeCxxInstance(jobj); +} + +void AnimatedCxxReactPackage::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", AnimatedCxxReactPackage::initHybrid), + }); +} + +std::shared_ptr AnimatedCxxReactPackage::getModule( + const std::string& moduleName, + const std::shared_ptr& jsInvoker) { + if (moduleName == "NativeAnimatedModule") { + return std::make_shared( + jsInvoker, getNativeAnimatedNodesManagerProvider()); + } + + return nullptr; +} + +std::shared_ptr +AnimatedCxxReactPackage::getNativeAnimatedNodesManagerProvider() { + if (auto provider = nativeAnimatedNodesManagerProvider_.lock()) { + return provider; + } + + auto nativeAnimatedNodesManagerProvider = + std::make_shared(nullptr, nullptr); + + nativeAnimatedNodesManagerProvider_ = nativeAnimatedNodesManagerProvider; + return nativeAnimatedNodesManagerProvider; +} + +} // namespace facebook::react \ No newline at end of file diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/animated/AnimatedCxxReactPackage.h b/packages/react-native/ReactAndroid/src/main/jni/react/animated/AnimatedCxxReactPackage.h new file mode 100644 index 000000000000..61b5eff3af7d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/animated/AnimatedCxxReactPackage.h @@ -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. + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { +class AnimatedCxxReactPackage + : public jni::HybridClass { + public: + static constexpr auto kJavaDescriptor = + "Lcom/facebook/react/animated/AnimatedCxxReactPackage;"; + + static jni::local_ref initHybrid( + jni::alias_ref + jobj); + + static void registerNatives(); + + AnimatedCxxReactPackage( + jni::alias_ref jobj); + + std::shared_ptr getModule( + const std::string& moduleName, + const std::shared_ptr& jsInvoker) override; + + private: + std::weak_ptr + nativeAnimatedNodesManagerProvider_; + + std::shared_ptr + getNativeAnimatedNodesManagerProvider(); + + friend HybridBase; + using HybridBase::HybridBase; +}; +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/animated/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/animated/CMakeLists.txt new file mode 100644 index 000000000000..e64d3c3b9493 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/animated/CMakeLists.txt @@ -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. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +# include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) + +file(GLOB react_animatedjni_SRC CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) + +add_library(react_animatedjni OBJECT ${react_animatedjni_SRC}) + +target_include_directories(react_animatedjni PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +# target_merge_so(react_animatedjni) + +target_link_libraries(react_animatedjni + fbjni + react_codegen_rncore + react_debug + react_renderer_core + react_renderer_graphics + react_renderer_mounting + react_renderer_uimanager + react_renderer_scheduler + react_renderer_animationbackend + react_cxxreactpackage + glog + folly_runtime) + +target_compile_reactnative_options(react_animatedjni PRIVATE) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/animated/OnLoad.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/animated/OnLoad.cpp new file mode 100644 index 000000000000..a9358dc75241 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/animated/OnLoad.cpp @@ -0,0 +1,15 @@ +/* + * 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 + +#include "AnimatedCxxReactPackage.h" + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*reserved*/) { + return facebook::jni::initialize( + vm, [] { facebook::react::AnimatedCxxReactPackage::registerNatives(); }); +} \ No newline at end of file diff --git a/packages/react-native/ReactApple/RCTAnimatedModuleProvider/React-RCTAnimatedModuleProvider.podspec b/packages/react-native/ReactApple/RCTAnimatedModuleProvider/React-RCTAnimatedModuleProvider.podspec new file mode 100644 index 000000000000..3e6e68539a3a --- /dev/null +++ b/packages/react-native/ReactApple/RCTAnimatedModuleProvider/React-RCTAnimatedModuleProvider.podspec @@ -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. + +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 = [ + "\"$(PODS_TARGET_SRCROOT)/../../ReactCommon\"", + "\"$(PODS_ROOT)/Headers/Public/React-Core\"", + "\"$(PODS_ROOT)/Headers/Private/Yoga\"" +] + +Pod::Spec.new do |s| + s.name = "React-RCTAnimatedModuleProvider" + s.version = version + s.summary = "Animated TurboModule provider for iOS (bridgeless)" + 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 = podspec_sources("*.{m,mm}", "**/*.h") + s.public_header_files = "*.h" + s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs" + s.header_dir = "RCTAnimatedModuleProvider" + s.pod_target_xcconfig = { + "USE_HEADERMAP" => "YES", + "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), + # "HEADER_SEARCH_PATHS" => '$(inherited) "$(PODS_ROOT)/Headers/Public/Yoga" "$(PODS_ROOT)/Headers/Private/Yoga"' + "HEADER_SEARCH_PATHS" => header_search_paths.join(' ') + } + +# resolve_use_frameworks(s, header_mappings_dir: "./", module_name: "React_RCTAnimatedModuleProvider") + resolve_use_frameworks(s, header_mappings_dir: "./") + + + # Dependencies needed for TurboModule types and feature flags access + s.dependency "React-Core" + s.dependency "React-jsi" + add_dependency(s, "ReactCommon", :subspec => "turbomodule/core") + add_dependency(s, "React-callinvoker") + add_dependency(s, "React-featureflags") + add_dependency(s, "React-Fabric", :subspec => "animated", :additional_framework_paths => ["react/renderer/animated"]) + add_dependency(s, "Yoga") + + add_rn_third_party_dependencies(s) + add_rncore_dependency(s) +end + + diff --git a/packages/react-native/ReactCommon/React-Fabric.podspec b/packages/react-native/ReactCommon/React-Fabric.podspec index e19c74b1138c..cdd5bd440e05 100644 --- a/packages/react-native/ReactCommon/React-Fabric.podspec +++ b/packages/react-native/ReactCommon/React-Fabric.podspec @@ -59,6 +59,9 @@ Pod::Spec.new do |s| ss.source_files = podspec_sources("react/renderer/animated/**/*.{m,mm,cpp,h}", "react/renderer/animated/**/*.{h}") ss.exclude_files = "react/renderer/animated/tests" ss.header_dir = "react/renderer/animated" + ss.pod_target_xcconfig = { + "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) RN_USE_ANIMATION_BACKEND=1", + } end s.subspec "animations" do |ss| diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index d3e884a76728..d6fd4cdf592d 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -36,7 +36,7 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { } bool cxxNativeAnimatedEnabled() override { - return false; + return true; } bool cxxNativeAnimatedRemoveJsSync() override { diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm index ac4c5c5c2a0f..ccd64bc30209 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm @@ -15,6 +15,7 @@ #import +#import #import #import #import @@ -219,6 +220,7 @@ @implementation RCTTurboModuleManager { RCTDevMenuConfigurationDecorator *_devMenuConfigurationDecorator; dispatch_queue_t _sharedModuleQueue; + RCTAnimatedModuleProvider *animatedModuleProvider; } - (instancetype)initWithBridge:(RCTBridge *)bridge @@ -260,6 +262,10 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge _legacyEagerlyRegisteredModuleClasses = legacyEagerlyRegisteredModuleClasses; } + if (ReactNativeFeatureFlags::cxxNativeAnimatedEnabled()) { + animatedModuleProvider = [RCTAnimatedModuleProvider new]; + } + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bridgeWillInvalidateModules:) name:RCTBridgeWillInvalidateModulesNotification @@ -356,6 +362,12 @@ - (instancetype)initWithBridgeProxy:(RCTBridgeProxy *)bridgeProxy return turboModule; } + if (ReactNativeFeatureFlags::cxxNativeAnimatedEnabled()) { + if (auto module = [animatedModuleProvider getTurboModule:moduleName jsInvoker:_jsInvoker]) { + return module; + } + } + /** * Step 2: Look for platform-specific modules. */ diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index 2ac82083cbfe..073a1f2a9a1e 100644 --- a/packages/react-native/ReactNativeApi.d.ts +++ b/packages/react-native/ReactNativeApi.d.ts @@ -6109,7 +6109,7 @@ export { ScrollViewProps, // 27986ff5 ScrollViewPropsAndroid, // 84e2134b ScrollViewPropsIOS, // d83c9733 - ScrollViewScrollToOptions, // 3313411e + ScrollViewScrollToOptions, // 33134111 SectionBase, // b376bddc SectionList, // ff1193b2 SectionListData, // 119baf83 diff --git a/packages/react-native/scripts/react_native_pods.rb b/packages/react-native/scripts/react_native_pods.rb index 72c267389c39..ce347e52a8e2 100644 --- a/packages/react-native/scripts/react_native_pods.rb +++ b/packages/react-native/scripts/react_native_pods.rb @@ -153,6 +153,8 @@ def use_react_native! ( pod 'React-jsi', :path => "#{prefix}/ReactCommon/jsi" pod 'RCTSwiftUI', :path => "#{prefix}/ReactApple/RCTSwiftUI" pod 'RCTSwiftUIWrapper', :path => "#{prefix}/ReactApple/RCTSwiftUIWrapper" + # Provide Animated TurboModule for iOS bridgeless via Objective-C++ provider + pod 'React-RCTAnimatedModuleProvider', :path => "#{prefix}/ReactApple/RCTAnimatedModuleProvider" if hermes_enabled setup_hermes!(:react_native_path => prefix) diff --git a/private/react-native-bots/dangerfile.js b/private/react-native-bots/dangerfile.js index 1bff20819a97..7ed51328e3d3 100644 --- a/private/react-native-bots/dangerfile.js +++ b/private/react-native-bots/dangerfile.js @@ -30,23 +30,26 @@ const isFromPhabricator = body_contains('differential revision:'); // Provides advice if a summary section is missing, or body is too short const includesSummary = body_contains('## summary', 'summary:'); -const snapshot_output = JSON.parse( - fs.readFileSync( - path.join( - process.env.RUNNER_TEMP, - 'diff-js-api-breaking-changes/output.json', - ), - 'utf8', - ), +const snapshot_path = path.join( + process.env.RUNNER_TEMP, + 'diff-js-api-breaking-changes/output.json', ); -if (snapshot_output && snapshot_output.result !== 'NON_BREAKING') { - const title = ':exclamation: JavaScript API change detected'; - const idea = - 'This PR commits an update to ReactNativeApi.d.ts, indicating a change to React Native's public JavaScript API. ' + - 'Please include a clear changelog message. ' + - 'This change will be subject to extra review.\n\n' + - `This change was flagged as: ${snapshot_output.result}`; - warn(`${title} - ${idea}`); + +if (fs.existsSync(snapshot_path)) { + console.log('JS API snapshot found, checking for changes...'); + const snapshot_output = JSON.parse(fs.readFileSync(snapshot_path, 'utf8')); + console.log('Snapshot output:', snapshot_output); + if (snapshot_output && snapshot_output.result !== 'NON_BREAKING') { + const title = ':exclamation: JavaScript API change detected'; + const idea = + 'This PR commits an update to ReactNativeApi.d.ts, indicating a change to React Native's public JavaScript API. ' + + 'Please include a clear changelog message. ' + + 'This change will be subject to extra review.\n\n' + + `This change was flagged as: ${snapshot_output.result}`; + warn(`${title} - ${idea}`); + } +} else { + console.log('No JS API snapshot found, skipping JS API change check.'); } const hasNoUsefulBody =