Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(react-native-host): add support for bridgeless mode #2931

Merged
merged 6 commits into from
Feb 1, 2024
Merged
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
12 changes: 12 additions & 0 deletions .changeset/old-bottles-clean.md
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions packages/react-native-host/ReactNativeHost.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down
40 changes: 40 additions & 0 deletions packages/react-native-host/cocoa/RNXBridgelessHeaders.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#if USE_BRIDGELESS

#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <ReactCommon/RCTHost+Internal.h>
#import <ReactCommon/RCTHost.h>

#if USE_HERMES
#import <ReactCommon/RCTHermesInstance.h>
#else
#import <ReactCommon/RCTJscInstance.h>
#endif // USE_HERMES

#import <react/config/ReactNativeConfig.h>

#if __has_include(<react/runtime/JSEngineInstance.h>)
using SharedJSRuntimeFactory = std::shared_ptr<facebook::react::JSEngineInstance>;
#else
using SharedJSRuntimeFactory = std::shared_ptr<facebook::react::JSRuntimeFactory>;
#endif // __has_include(<react/runtime/JSEngineInstance.h>)

#elif USE_FABRIC

#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <react/config/ReactNativeConfig.h>

@class RCTHost;

#else

@class RCTHost;
@class RCTSurfacePresenterBridgeAdapter;

namespace facebook::react
{
class EmptyReactNativeConfig
{
};
} // namespace facebook::react

#endif // USE_BRIDGELESS
4 changes: 3 additions & 1 deletion packages/react-native-host/cocoa/RNXFabricAdapter.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#import <Foundation/Foundation.h>

@class RCTBridge;
@class RCTSurfacePresenterBridgeAdapter;

NS_ASSUME_NONNULL_BEGIN

NSObject *RNXInstallSurfacePresenterBridgeAdapter(RCTBridge *bridge);
RCTSurfacePresenterBridgeAdapter *_Nullable RNXInstallSurfacePresenterBridgeAdapter(
RCTBridge *bridge);

NS_ASSUME_NONNULL_END
10 changes: 5 additions & 5 deletions packages/react-native-host/cocoa/RNXFabricAdapter.mm
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -9,11 +9,11 @@
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <react/config/ReactNativeConfig.h>
#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<facebook::react::ContextContainer const>();
auto reactNativeConfig = std::make_shared<facebook::react::EmptyReactNativeConfig const>();
contextContainer->insert("ReactNativeConfig", reactNativeConfig);
Expand All @@ -24,5 +24,5 @@
return bridgeAdapter;
#else
return nil;
#endif // USE_FABRIC
#endif // USE_FABRIC && !USE_BRIDGELESS
}
3 changes: 3 additions & 0 deletions packages/react-native-host/cocoa/RNXHostConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
16 changes: 16 additions & 0 deletions packages/react-native-host/cocoa/ReactNativeHost+Private.h
Original file line number Diff line number Diff line change
@@ -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
9 changes: 5 additions & 4 deletions packages/react-native-host/cocoa/ReactNativeHost+View.mm
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#import "ReactNativeHost.h"
#import "ReactNativeHost+Private.h"

#ifdef USE_FABRIC
#if __has_include(<React/RCTFabricSurfaceHostingProxyRootView.h>)
Expand Down Expand Up @@ -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(<React/RCTFabricSurfaceHostingProxyRootView.h>)
#else
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-host/cocoa/ReactNativeHost.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN
/// Hosts a React Native instance.
@interface ReactNativeHost : NSObject <RCTBridgeDelegate>

/// 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;
Expand Down
103 changes: 97 additions & 6 deletions packages/react-native-host/cocoa/ReactNativeHost.mm
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,32 @@
#import <React/RCTDevLoadingViewSetEnabled.h>
#import <React/RCTUtils.h>

#import "RNXBridgelessHeaders.h"
#import "RNXFabricAdapter.h"
#import "RNXHostConfig.h"
#import "RNXHostReleaser.h"
#import "RNXTurboModuleAdapter.h"

@class RCTSurfacePresenter;

using ReactNativeConfig = facebook::react::EmptyReactNativeConfig const;

#if USE_BRIDGELESS
@interface ReactNativeHost () <RCTContextContainerHandling>
#else
@interface ReactNativeHost () <RCTCxxBridgeDelegate>
#endif // USE_BRIDGELESS
@end

@implementation ReactNativeHost {
__weak id<RNXHostConfig> _config;
RNXTurboModuleAdapter *_turboModuleAdapter;
NSObject *_surfacePresenterBridgeAdapter;
RCTSurfacePresenterBridgeAdapter *_surfacePresenterBridgeAdapter;
RCTBridge *_bridge;
RCTHost *_reactHost;
NSLock *_isShuttingDown;
RNXHostReleaser *_hostReleaser;
std::shared_ptr<ReactNativeConfig> _reactNativeConfig;
}

- (instancetype)initWithConfig:(id<RNXHostConfig>)config
Expand All @@ -53,23 +64,24 @@ - (instancetype)initWithConfig:(id<RNXHostConfig>)config
}

_config = config;
#if USE_FABRIC
_turboModuleAdapter = [[RNXTurboModuleAdapter alloc] init];
#endif
[self enableTurboModule];
_isShuttingDown = [[NSLock alloc] init];

if ([config respondsToSelector:@selector(shouldReleaseBridgeWhenBackgrounded)] &&
[config shouldReleaseBridgeWhenBackgrounded]) {
_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;
Expand All @@ -86,6 +98,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
Expand All @@ -106,7 +130,7 @@ - (void)usingModule:(Class)moduleClass block:(void (^)(id<RCTBridgeModule> _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];
});
Expand Down Expand Up @@ -139,6 +163,18 @@ - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
: @[];
}

#if USE_BRIDGELESS

// MARK: - RCTContextContainerHandling details

- (void)didCreateContextContainer:
(std::shared_ptr<facebook::react::ContextContainer>)contextContainer
{
contextContainer->insert("ReactNativeConfig", _reactNativeConfig);
}

#else // USE_BRIDGELESS

// MARK: - RCTCxxBridgeDelegate details

- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:
Expand All @@ -153,6 +189,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<ReactNativeConfig>();
std::weak_ptr<ReactNativeConfig> reactNativeConfig{_reactNativeConfig};

SharedJSRuntimeFactory (^jsEngineProvider)() = ^SharedJSRuntimeFactory {
#if USE_HERMES
auto config = reactNativeConfig.lock();
NSAssert(config, @"Expected nonnull ReactNativeConfig instance");
return std::make_shared<facebook::react::RCTHermesInstance>(config, nullptr);
#else
return std::make_shared<facebook::react::RCTJscInstance>();
#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
Loading