From d8fc462c2d909c7365ab0c51b39cae0b410b44bb Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Wed, 24 Jan 2024 12:01:40 +0100 Subject: [PATCH 1/6] fix(react-native-host): add support for bridgeless mode --- .changeset/old-bottles-clean.md | 12 +++ .../react-native-host/ReactNativeHost.podspec | 2 + .../cocoa/RNXBridgelessHeaders.h | 32 ++++++ .../cocoa/RNXFabricAdapter.h | 4 +- .../cocoa/RNXFabricAdapter.mm | 10 +- .../react-native-host/cocoa/RNXHostConfig.h | 3 + .../cocoa/ReactNativeHost+View.mm | 7 +- .../react-native-host/cocoa/ReactNativeHost.h | 7 ++ .../cocoa/ReactNativeHost.mm | 102 ++++++++++++++++-- 9 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 .changeset/old-bottles-clean.md create mode 100644 packages/react-native-host/cocoa/RNXBridgelessHeaders.h diff --git a/.changeset/old-bottles-clean.md b/.changeset/old-bottles-clean.md new file mode 100644 index 0000000000..9dcdd1a07f --- /dev/null +++ b/.changeset/old-bottles-clean.md @@ -0,0 +1,12 @@ +--- +"@rnx-kit/react-native-host": patch +--- + +Added support for Bridgeless Mode + +Bridgeless mode can now be enabled by setting the environment variable +`USE_BRIDGELESS=1`. This build flag will enable bridgeless bits, but you can +still disable it at runtime by implementing `RNXHostConfig.isBridgelessEnabled`. + +See the full announcement here: +https://reactnative.dev/blog/2023/12/06/0.73-debugging-improvements-stable-symlinks#new-architecture-updates diff --git a/packages/react-native-host/ReactNativeHost.podspec b/packages/react-native-host/ReactNativeHost.podspec index 18328a47a6..0c425a9d75 100644 --- a/packages/react-native-host/ReactNativeHost.podspec +++ b/packages/react-native-host/ReactNativeHost.podspec @@ -16,10 +16,12 @@ preprocessor_definitions = [ 'FOLLY_MOBILE=1', 'FOLLY_NO_CONFIG=1', 'FOLLY_USE_LIBCPP=1', + "USE_HERMES=#{ENV['USE_HERMES'] || '0'}", ] if new_arch_enabled preprocessor_definitions << 'RCT_NEW_ARCH_ENABLED=1' preprocessor_definitions << 'USE_FABRIC=1' + preprocessor_definitions << 'USE_BRIDGELESS=1' if ENV['USE_BRIDGELESS'] == '1' end Pod::Spec.new do |s| diff --git a/packages/react-native-host/cocoa/RNXBridgelessHeaders.h b/packages/react-native-host/cocoa/RNXBridgelessHeaders.h new file mode 100644 index 0000000000..aef213fc26 --- /dev/null +++ b/packages/react-native-host/cocoa/RNXBridgelessHeaders.h @@ -0,0 +1,32 @@ +#if USE_BRIDGELESS + +#import +#import +#import + +#if USE_HERMES +#import +#else +#import +#endif // USE_HERMES + +#import + +#if __has_include() +using SharedJSRuntimeFactory = std::shared_ptr; +#else +using SharedJSRuntimeFactory = std::shared_ptr; +#endif // __has_include() + +#elif USE_FABRIC + +#import + +@class RCTHost; + +#else + +@class RCTHost; +@class RCTSurfacePresenterBridgeAdapter; + +#endif // USE_BRIDGELESS diff --git a/packages/react-native-host/cocoa/RNXFabricAdapter.h b/packages/react-native-host/cocoa/RNXFabricAdapter.h index 92fec1a057..8960cc35f3 100644 --- a/packages/react-native-host/cocoa/RNXFabricAdapter.h +++ b/packages/react-native-host/cocoa/RNXFabricAdapter.h @@ -1,9 +1,11 @@ #import @class RCTBridge; +@class RCTSurfacePresenterBridgeAdapter; NS_ASSUME_NONNULL_BEGIN -NSObject *RNXInstallSurfacePresenterBridgeAdapter(RCTBridge *bridge); +RCTSurfacePresenterBridgeAdapter *_Nullable RNXInstallSurfacePresenterBridgeAdapter( + RCTBridge *bridge); NS_ASSUME_NONNULL_END diff --git a/packages/react-native-host/cocoa/RNXFabricAdapter.mm b/packages/react-native-host/cocoa/RNXFabricAdapter.mm index 3cbb2a293d..68d038937a 100644 --- a/packages/react-native-host/cocoa/RNXFabricAdapter.mm +++ b/packages/react-native-host/cocoa/RNXFabricAdapter.mm @@ -1,6 +1,6 @@ #import "RNXFabricAdapter.h" -#ifdef USE_FABRIC +#if USE_FABRIC && !USE_BRIDGELESS #include "FollyConfig.h" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wcomma" @@ -9,11 +9,11 @@ #import #import #pragma clang diagnostic pop -#endif // USE_FABRIC +#endif // USE_FABRIC && !USE_BRIDGELESS -NSObject *RNXInstallSurfacePresenterBridgeAdapter(RCTBridge *bridge) +RCTSurfacePresenterBridgeAdapter *RNXInstallSurfacePresenterBridgeAdapter(RCTBridge *bridge) { -#ifdef USE_FABRIC +#if USE_FABRIC && !USE_BRIDGELESS auto contextContainer = std::make_shared(); auto reactNativeConfig = std::make_shared(); contextContainer->insert("ReactNativeConfig", reactNativeConfig); @@ -24,5 +24,5 @@ return bridgeAdapter; #else return nil; -#endif // USE_FABRIC +#endif // USE_FABRIC && !USE_BRIDGELESS } diff --git a/packages/react-native-host/cocoa/RNXHostConfig.h b/packages/react-native-host/cocoa/RNXHostConfig.h index 0a57bf6b1a..3d93093ef7 100644 --- a/packages/react-native-host/cocoa/RNXHostConfig.h +++ b/packages/react-native-host/cocoa/RNXHostConfig.h @@ -10,6 +10,9 @@ NS_ASSUME_NONNULL_BEGIN @optional +/// Returns whether the new initialization layer is enabled. +@property (nonatomic, readonly) BOOL isBridgelessEnabled; + /// Returns whether the loading view should be visible while loading JavaScript. @property (nonatomic, readonly) BOOL isDevLoadingViewEnabled; diff --git a/packages/react-native-host/cocoa/ReactNativeHost+View.mm b/packages/react-native-host/cocoa/ReactNativeHost+View.mm index bc8d8ef5b5..59e964642d 100644 --- a/packages/react-native-host/cocoa/ReactNativeHost+View.mm +++ b/packages/react-native-host/cocoa/ReactNativeHost+View.mm @@ -41,9 +41,10 @@ - (RNXView *)viewWithModuleName:(NSString *)moduleName moduleName:moduleName initialProperties:initialProperties]; #else - RCTFabricSurface *surface = [[RCTFabricSurface alloc] initWithBridge:self.bridge - moduleName:moduleName - initialProperties:initialProperties]; + RCTFabricSurface *surface = + [[RCTFabricSurface alloc] initWithSurfacePresenter:self.surfacePresenter + moduleName:moduleName + initialProperties:initialProperties]; return [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface]; #endif // __has_include() #else diff --git a/packages/react-native-host/cocoa/ReactNativeHost.h b/packages/react-native-host/cocoa/ReactNativeHost.h index 16b303e6f9..3e74734f10 100644 --- a/packages/react-native-host/cocoa/ReactNativeHost.h +++ b/packages/react-native-host/cocoa/ReactNativeHost.h @@ -4,6 +4,8 @@ #import "RNXHostConfig.h" +@class RCTSurfacePresenter; + #if TARGET_OS_OSX @class NSView; typedef NSView RNXView; @@ -22,6 +24,11 @@ NS_ASSUME_NONNULL_BEGIN /// - Note: This is not forwards compatible and will be removed in the future. @property (nonatomic, readonly, nullable) RCTBridge *bridge; +/// Returns the current `RCTSurfacePresenter` instance. +/// +/// - Note: Returns `nil` if New Architecture is not enabled. +@property (nonatomic, readonly, nullable) RCTSurfacePresenter *surfacePresenter; + - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithConfig:(id)config NS_DESIGNATED_INITIALIZER diff --git a/packages/react-native-host/cocoa/ReactNativeHost.mm b/packages/react-native-host/cocoa/ReactNativeHost.mm index 5660d842d7..a5f099500d 100644 --- a/packages/react-native-host/cocoa/ReactNativeHost.mm +++ b/packages/react-native-host/cocoa/ReactNativeHost.mm @@ -11,22 +11,32 @@ #import #import #import +#import +#import "RNXBridgelessHeaders.h" #import "RNXFabricAdapter.h" #import "RNXHostConfig.h" #import "RNXHostReleaser.h" #import "RNXTurboModuleAdapter.h" +using ReactNativeConfig = facebook::react::EmptyReactNativeConfig const; + +#if USE_BRIDGELESS +@interface ReactNativeHost () +#else @interface ReactNativeHost () +#endif // USE_BRIDGELESS @end @implementation ReactNativeHost { __weak id _config; RNXTurboModuleAdapter *_turboModuleAdapter; - NSObject *_surfacePresenterBridgeAdapter; + RCTSurfacePresenterBridgeAdapter *_surfacePresenterBridgeAdapter; RCTBridge *_bridge; + RCTHost *_reactHost; NSLock *_isShuttingDown; RNXHostReleaser *_hostReleaser; + std::shared_ptr _reactNativeConfig; } - (instancetype)initWithConfig:(id)config @@ -53,9 +63,7 @@ - (instancetype)initWithConfig:(id)config } _config = config; -#if USE_FABRIC - _turboModuleAdapter = [[RNXTurboModuleAdapter alloc] init]; -#endif + [self enableTurboModule]; _isShuttingDown = [[NSLock alloc] init]; if ([config respondsToSelector:@selector(shouldReleaseBridgeWhenBackgrounded)] && @@ -63,13 +71,16 @@ - (instancetype)initWithConfig:(id)config _hostReleaser = [[RNXHostReleaser alloc] initWithHost:self]; } - (void)self.bridge; // Initialize the bridge now + [self initializeReactHost]; } return self; } - (RCTBridge *)bridge { +#if USE_BRIDGELESS + return nil; +#else if (![_isShuttingDown tryLock]) { NSAssert(NO, @"Tried to access the bridge while shutting down"); return nil; @@ -86,6 +97,18 @@ - (RCTBridge *)bridge } @finally { [_isShuttingDown unlock]; } +#endif // USE_BRIDGELESS +} + +- (RCTSurfacePresenter *)surfacePresenter +{ +#if USE_BRIDGELESS + return [_reactHost getSurfacePresenter]; +#elif USE_FABRIC + return [_surfacePresenterBridgeAdapter surfacePresenter]; +#else + return nil; +#endif } - (void)shutdown @@ -106,7 +129,7 @@ - (void)usingModule:(Class)moduleClass block:(void (^)(id _Null [moduleClass respondsToSelector:@selector(requiresMainQueueSetup)] && [moduleClass requiresMainQueueSetup]; if (requiresMainQueueSetup && !RCTIsMainQueue()) { - __weak id weakSelf = self; + __weak __typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf usingModule:moduleClass block:block]; }); @@ -139,6 +162,18 @@ - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge : @[]; } +#if USE_BRIDGELESS + +// MARK: - RCTContextContainerHandling details + +- (void)didCreateContextContainer: + (std::shared_ptr)contextContainer +{ + contextContainer->insert("ReactNativeConfig", _reactNativeConfig); +} + +#else // USE_BRIDGELESS + // MARK: - RCTCxxBridgeDelegate details - (std::unique_ptr)jsExecutorFactoryForBridge: @@ -153,6 +188,61 @@ - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge #endif // USE_FABRIC } +#endif // USE_BRIDGELESS + // MARK: - Private +- (void)enableTurboModule +{ +#if USE_FABRIC + _turboModuleAdapter = [[RNXTurboModuleAdapter alloc] init]; + RCTEnableTurboModule(true); +#endif +} + +- (void)initializeReactHost +{ +#if USE_BRIDGELESS + // Bridgeless mode is enabled if it was turned on with a build flag, unless + // `isBridgelessEnabled` is explicitly implemented and returns false. + if ([_config respondsToSelector:@selector(isBridgelessEnabled)] && + ![_config isBridgelessEnabled]) { + (void)self.bridge; // Initialize the bridge now + return; + } + + RCTSetUseNativeViewConfigsInBridgelessMode(YES); + RCTEnableTurboModuleInterop(YES); + RCTEnableTurboModuleInteropBridgeProxy(YES); + + _reactNativeConfig = std::make_shared(); + std::weak_ptr reactNativeConfig{_reactNativeConfig}; + + SharedJSRuntimeFactory (^jsEngineProvider)() = ^SharedJSRuntimeFactory { +#if USE_HERMES + auto config = reactNativeConfig.lock(); + NSAssert(config, @"Expected nonnull ReactNativeConfig instance"); + return std::make_shared(config, nullptr); +#else + return std::make_shared(); +#endif // USE_HERMES + }; + + _reactHost = [[RCTHost alloc] initWithBundleURL:[self sourceURLForBridge:nil] + hostDelegate:nil + turboModuleManagerDelegate:_turboModuleAdapter + jsEngineProvider:jsEngineProvider]; + + __weak __typeof(self) weakSelf = self; + [_reactHost setBundleURLProvider:^NSURL *() { + return [weakSelf sourceURLForBridge:nil]; + }]; + + [_reactHost setContextContainerHandler:self]; + [_reactHost start]; +#else + (void)self.bridge; +#endif // USE_BRIDGELESS +} + @end From dda742a9852eacd762b215033d0c1b6b652e061e Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:04:21 +0100 Subject: [PATCH 2/6] link to class docs --- packages/react-native-host/cocoa/ReactNativeHost.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native-host/cocoa/ReactNativeHost.h b/packages/react-native-host/cocoa/ReactNativeHost.h index 3e74734f10..bf9fc3f4b9 100644 --- a/packages/react-native-host/cocoa/ReactNativeHost.h +++ b/packages/react-native-host/cocoa/ReactNativeHost.h @@ -19,12 +19,12 @@ NS_ASSUME_NONNULL_BEGIN /// Hosts a React Native instance. @interface ReactNativeHost : NSObject -/// Returns the current `RCTBridge` instance. +/// Returns the current ``RCTBridge`` instance. /// /// - Note: This is not forwards compatible and will be removed in the future. @property (nonatomic, readonly, nullable) RCTBridge *bridge; -/// Returns the current `RCTSurfacePresenter` instance. +/// Returns the current ``RCTSurfacePresenter`` instance. /// /// - Note: Returns `nil` if New Architecture is not enabled. @property (nonatomic, readonly, nullable) RCTSurfacePresenter *surfacePresenter; From 9ea45c6abfc8f760e919e2494b7c88282f3b8ab0 Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:38:05 +0100 Subject: [PATCH 3/6] stub `EmptyReactNativeConfig` --- packages/react-native-host/cocoa/ReactNativeHost.mm | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/react-native-host/cocoa/ReactNativeHost.mm b/packages/react-native-host/cocoa/ReactNativeHost.mm index a5f099500d..ffcf28f018 100644 --- a/packages/react-native-host/cocoa/ReactNativeHost.mm +++ b/packages/react-native-host/cocoa/ReactNativeHost.mm @@ -11,7 +11,17 @@ #import #import #import + +#if __has_include() #import +#else +namespace facebook::react +{ + class EmptyReactNativeConfig + { + }; +} // namespace facebook::react +#endif // __has_include() #import "RNXBridgelessHeaders.h" #import "RNXFabricAdapter.h" From c47ead4113c7020beee657aa46a032eb8e254dcb Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:47:15 +0100 Subject: [PATCH 4/6] move `EmptyReactNativeConfig` stub --- .../react-native-host/cocoa/RNXBridgelessHeaders.h | 8 ++++++++ packages/react-native-host/cocoa/ReactNativeHost.mm | 11 ----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/react-native-host/cocoa/RNXBridgelessHeaders.h b/packages/react-native-host/cocoa/RNXBridgelessHeaders.h index aef213fc26..85d933e8bb 100644 --- a/packages/react-native-host/cocoa/RNXBridgelessHeaders.h +++ b/packages/react-native-host/cocoa/RNXBridgelessHeaders.h @@ -21,6 +21,7 @@ using SharedJSRuntimeFactory = std::shared_ptr +#import @class RCTHost; @@ -29,4 +30,11 @@ using SharedJSRuntimeFactory = std::shared_ptr #import -#if __has_include() -#import -#else -namespace facebook::react -{ - class EmptyReactNativeConfig - { - }; -} // namespace facebook::react -#endif // __has_include() - #import "RNXBridgelessHeaders.h" #import "RNXFabricAdapter.h" #import "RNXHostConfig.h" From 62fa51ced5386d7da740246f709ee58341871e95 Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Mon, 29 Jan 2024 11:27:06 +0100 Subject: [PATCH 5/6] make `-surfacePresenter` private --- .../cocoa/ReactNativeHost+Private.h | 16 ++++++++++++++++ .../cocoa/ReactNativeHost+View.mm | 2 +- .../react-native-host/cocoa/ReactNativeHost.h | 7 ------- 3 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 packages/react-native-host/cocoa/ReactNativeHost+Private.h diff --git a/packages/react-native-host/cocoa/ReactNativeHost+Private.h b/packages/react-native-host/cocoa/ReactNativeHost+Private.h new file mode 100644 index 0000000000..d6e6bc3da5 --- /dev/null +++ b/packages/react-native-host/cocoa/ReactNativeHost+Private.h @@ -0,0 +1,16 @@ +#import "ReactNativeHost.h" + +@class RCTSurfacePresenter; + +NS_ASSUME_NONNULL_BEGIN + +@interface ReactNativeHost (Private) + +/// Returns the current ``RCTSurfacePresenter`` instance. +/// +/// - Note: Returns `nil` if New Architecture is not enabled. +@property (nonatomic, readonly, nullable) RCTSurfacePresenter *surfacePresenter; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/react-native-host/cocoa/ReactNativeHost+View.mm b/packages/react-native-host/cocoa/ReactNativeHost+View.mm index 59e964642d..b75f435051 100644 --- a/packages/react-native-host/cocoa/ReactNativeHost+View.mm +++ b/packages/react-native-host/cocoa/ReactNativeHost+View.mm @@ -1,4 +1,4 @@ -#import "ReactNativeHost.h" +#import "ReactNativeHost+Private.h" #ifdef USE_FABRIC #if __has_include() diff --git a/packages/react-native-host/cocoa/ReactNativeHost.h b/packages/react-native-host/cocoa/ReactNativeHost.h index bf9fc3f4b9..9134b4b648 100644 --- a/packages/react-native-host/cocoa/ReactNativeHost.h +++ b/packages/react-native-host/cocoa/ReactNativeHost.h @@ -4,8 +4,6 @@ #import "RNXHostConfig.h" -@class RCTSurfacePresenter; - #if TARGET_OS_OSX @class NSView; typedef NSView RNXView; @@ -24,11 +22,6 @@ NS_ASSUME_NONNULL_BEGIN /// - Note: This is not forwards compatible and will be removed in the future. @property (nonatomic, readonly, nullable) RCTBridge *bridge; -/// Returns the current ``RCTSurfacePresenter`` instance. -/// -/// - Note: Returns `nil` if New Architecture is not enabled. -@property (nonatomic, readonly, nullable) RCTSurfacePresenter *surfacePresenter; - - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithConfig:(id)config NS_DESIGNATED_INITIALIZER From b92f53d03ea1a73a85321aba45b2ae5bd9da551e Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Mon, 29 Jan 2024 11:40:12 +0100 Subject: [PATCH 6/6] fixup! make `-surfacePresenter` private --- packages/react-native-host/cocoa/ReactNativeHost.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-native-host/cocoa/ReactNativeHost.mm b/packages/react-native-host/cocoa/ReactNativeHost.mm index 52f7f8daf1..d5135295ab 100644 --- a/packages/react-native-host/cocoa/ReactNativeHost.mm +++ b/packages/react-native-host/cocoa/ReactNativeHost.mm @@ -18,6 +18,8 @@ #import "RNXHostReleaser.h" #import "RNXTurboModuleAdapter.h" +@class RCTSurfacePresenter; + using ReactNativeConfig = facebook::react::EmptyReactNativeConfig const; #if USE_BRIDGELESS