diff --git a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h index 455d23e42cb525..1671d7080907ab 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h +++ b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h @@ -24,6 +24,7 @@ @class RCTBridge; @protocol RCTComponentViewProtocol; @class RCTSurfacePresenterBridgeAdapter; +@class RCTBundleConfiguration; @class RCTDevMenuConfiguration; NS_ASSUME_NONNULL_BEGIN @@ -117,6 +118,8 @@ typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable }; @property (nonatomic, weak) id delegate; +@property (nonatomic, strong, nonnull) RCTBundleConfiguration *bundleConfiguration; + @property (nonatomic, nullable) RCTDevMenuConfiguration *devMenuConfiguration; @end diff --git a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm index a54e17796f05cc..e1eecb0ae73bb3 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm @@ -6,6 +6,7 @@ */ #import "RCTReactNativeFactory.h" +#import #import #import #import @@ -42,6 +43,8 @@ @interface RCTReactNativeFactory () < @implementation RCTReactNativeFactory +@synthesize bundleConfiguration = _bundleConfiguration; + - (instancetype)initWithDelegate:(id)delegate { return [self initWithDelegate:delegate releaseLevel:Stable]; @@ -84,6 +87,7 @@ - (void)startReactNativeWithModuleName:(NSString *)moduleName UIView *rootView = [self.rootViewFactory viewWithModuleName:moduleName initialProperties:initialProperties launchOptions:launchOptions + bundleConfiguration:self.bundleConfiguration devMenuConfiguration:self.devMenuConfiguration]; UIViewController *rootViewController = [_delegate createRootViewController]; [_delegate setRootView:rootView toRootViewController:rootViewController]; @@ -112,6 +116,14 @@ - (NSURL *_Nullable)bundleURL return _delegate.bundleURL; } +- (RCTBundleConfiguration *)bundleConfiguration +{ + if (_bundleConfiguration == nullptr) { + _bundleConfiguration = [RCTBundleConfiguration defaultConfiguration]; + } + return _bundleConfiguration; +} + #pragma mark - RCTJSRuntimeConfiguratorProtocol - (JSRuntimeFactoryRef)createJSRuntimeFactory diff --git a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h index 119d91a9ee29fc..7473c01a8b25c7 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h +++ b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h @@ -18,6 +18,7 @@ @class RCTHost; @class RCTRootView; @class RCTSurfacePresenterBridgeAdapter; +@class RCTBundleConfiguration; @class RCTDevMenuConfiguration; NS_ASSUME_NONNULL_BEGIN @@ -202,12 +203,14 @@ typedef void (^RCTLoadSourceForBridgeBlock)(RCTBridge *bridge, RCTSourceLoadBloc * @parameter: moduleName - the name of the app, used by Metro to resolve the module. * @parameter: initialProperties - a set of initial properties. * @parameter: launchOptions - a dictionary with a set of options. + * @parameter: bundleConfiguration - a configuration for custom bundle source URL. * @parameter: devMenuConfiguration - a configuration for enabling/disabling dev menu. */ - (UIView *_Nonnull)viewWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary *__nullable)initialProperties launchOptions:(NSDictionary *__nullable)launchOptions - devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration; + bundleConfiguration:(RCTBundleConfiguration *)bundleConfiguration + devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration; - (UIView *_Nonnull)viewWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary *__nullable)initialProperties @@ -226,15 +229,18 @@ typedef void (^RCTLoadSourceForBridgeBlock)(RCTBridge *bridge, RCTSourceLoadBloc * Use it to speed up later viewWithModuleName: calls. * * @parameter: launchOptions - a dictionary with a set of options. + * @parameter: bundleConfiguration - a configuration for custom bundle source URL. * @parameter: devMenuConfiguration - a configuration for enabling/disabling dev menu. */ - (void)initializeReactHostWithLaunchOptions:(NSDictionary *__nullable)launchOptions + bundleConfiguration:(RCTBundleConfiguration *)bundleConfiguration devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration; -- (RCTHost *)createReactHost:(NSDictionary *__nullable)launchOptions; - - (RCTHost *)createReactHost:(NSDictionary *__nullable)launchOptions - devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration; + bundleConfiguration:(RCTBundleConfiguration *)bundleConfiguration + devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration; + +- (RCTHost *)createReactHost:(NSDictionary *__nullable)launchOptions; @end diff --git a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm index 77201b675ec53b..266d6bab211cdb 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm @@ -21,7 +21,6 @@ #else #import #endif -#import #import #import #import @@ -137,6 +136,7 @@ - (UIView *)viewWithModuleName:(NSString *)moduleName initialProperties:(NSDicti return [self viewWithModuleName:moduleName initialProperties:initialProperties launchOptions:nil + bundleConfiguration:[RCTBundleConfiguration defaultConfiguration] devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]]; } @@ -145,17 +145,21 @@ - (UIView *)viewWithModuleName:(NSString *)moduleName return [self viewWithModuleName:moduleName initialProperties:nil launchOptions:nil + bundleConfiguration:[RCTBundleConfiguration defaultConfiguration] devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]]; } - (void)initializeReactHostWithLaunchOptions:(NSDictionary *)launchOptions + bundleConfiguration:(RCTBundleConfiguration *)bundleConfiguration devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration { // Enable TurboModule interop by default in Bridgeless mode RCTEnableTurboModuleInterop(YES); RCTEnableTurboModuleInteropBridgeProxy(YES); - [self createReactHostIfNeeded:launchOptions devMenuConfiguration:devMenuConfiguration]; + [self createReactHostIfNeeded:launchOptions + bundleConfiguration:bundleConfiguration + devMenuConfiguration:devMenuConfiguration]; return; } @@ -166,15 +170,19 @@ - (UIView *)viewWithModuleName:(NSString *)moduleName return [self viewWithModuleName:moduleName initialProperties:initialProperties launchOptions:launchOptions + bundleConfiguration:[RCTBundleConfiguration defaultConfiguration] devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]]; } - (UIView *)viewWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary *)initProps launchOptions:(NSDictionary *)launchOptions + bundleConfiguration:(RCTBundleConfiguration *)bundleConfiguration devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration { - [self initializeReactHostWithLaunchOptions:launchOptions devMenuConfiguration:devMenuConfiguration]; + [self initializeReactHostWithLaunchOptions:launchOptions + bundleConfiguration:bundleConfiguration + devMenuConfiguration:devMenuConfiguration]; RCTFabricSurface *surface = [self.reactHost createSurfaceWithModuleName:moduleName initialProperties:initProps ? initProps : @{}]; @@ -245,20 +253,27 @@ - (void)createBridgeAdapterIfNeeded #pragma mark - New Arch Utilities - (void)createReactHostIfNeeded:(NSDictionary *)launchOptions + bundleConfiguration:(RCTBundleConfiguration *)bundleConfiguration devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration { if (self.reactHost) { return; } - self.reactHost = [self createReactHost:launchOptions devMenuConfiguration:devMenuConfiguration]; + + self.reactHost = [self createReactHost:launchOptions + bundleConfiguration:bundleConfiguration + devMenuConfiguration:devMenuConfiguration]; } - (RCTHost *)createReactHost:(NSDictionary *)launchOptions { - return [self createReactHost:launchOptions devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]]; + return [self createReactHost:launchOptions + bundleConfiguration:[RCTBundleConfiguration defaultConfiguration] + devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]]; } - (RCTHost *)createReactHost:(NSDictionary *)launchOptions + bundleConfiguration:(RCTBundleConfiguration *)bundleConfiguration devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration { __weak __typeof(self) weakSelf = self; @@ -270,6 +285,7 @@ - (RCTHost *)createReactHost:(NSDictionary *)launchOptions return [weakSelf createJSRuntimeFactory]; } launchOptions:launchOptions + bundleConfiguration:bundleConfiguration devMenuConfiguration:devMenuConfiguration]; [reactHost setBundleURLProvider:^NSURL *() { return [weakSelf bundleURL]; diff --git a/packages/react-native/React/Base/RCTBundleManager.h b/packages/react-native/React/Base/RCTBundleManager.h index fa0559ba50cd22..268c86898631ed 100644 --- a/packages/react-native/React/Base/RCTBundleManager.h +++ b/packages/react-native/React/Base/RCTBundleManager.h @@ -9,19 +9,71 @@ @class RCTBridge; -typedef NSURL * (^RCTBridgelessBundleURLGetter)(void); -typedef void (^RCTBridgelessBundleURLSetter)(NSURL *bundleURL); +typedef NSURL *_Nullable (^RCTBridgelessBundleURLGetter)(void); +typedef void (^RCTBridgelessBundleURLSetter)(NSURL *_Nullable bundleURL); +typedef NSMutableArray *_Nullable (^RCTPackagerOptionsUpdater)( + NSMutableArray *_Nullable options); + +/** + * Configuration class for setting up custom bundle locations + */ +@interface RCTBundleConfiguration : NSObject + ++ (nonnull instancetype)defaultConfiguration; + +/** + * The URL of the bundle to load from the file system + */ +@property (nonatomic, readonly, nullable) NSURL *bundleFilePath; + +/** + * The server scheme (e.g. http or https) to use when loading from the packager + */ +@property (nonatomic, readonly, nullable) NSString *packagerServerScheme; + +/** + * The server host (e.g. localhost) to use when loading from the packager + */ +@property (nonatomic, readonly, nullable) NSString *packagerServerHost; + +/** + * A block that modifies the packager options when loading from the packager + */ +@property (nonatomic, copy, nullable) RCTPackagerOptionsUpdater packagerOptionsUpdater; + +/** + * The relative path to the bundle. + */ +@property (nonatomic, readonly, nullable) NSString *bundlePath; + +- (nonnull instancetype)initWithBundleFilePath:(nullable NSURL *)bundleFilePath; + +- (nonnull instancetype)initWithPackagerServerScheme:(nullable NSString *)packagerServerScheme + packagerServerHost:(nullable NSString *)packagerServerHost + bundlePath:(nullable NSString *)bundlePath; + +- (nullable NSURL *)getBundleURL; + +- (nonnull NSString *)getPackagerServerScheme; + +- (nonnull NSString *)getPackagerServerHost; + +@end /** * A class that allows NativeModules/TurboModules to read/write the bundleURL, with or without the bridge. */ @interface RCTBundleManager : NSObject + +- (nullable instancetype)initWithBundleConfig:(nullable RCTBundleConfiguration *)bundleConfig; + #ifndef RCT_REMOVE_LEGACY_ARCH -- (void)setBridge:(RCTBridge *)bridge; +- (void)setBridge:(nullable RCTBridge *)bridge; #endif // RCT_REMOVE_LEGACY_ARCH -- (void)setBridgelessBundleURLGetter:(RCTBridgelessBundleURLGetter)getter - andSetter:(RCTBridgelessBundleURLSetter)setter - andDefaultGetter:(RCTBridgelessBundleURLGetter)defaultGetter; +- (void)setBridgelessBundleURLGetter:(nullable RCTBridgelessBundleURLGetter)getter + andSetter:(nullable RCTBridgelessBundleURLSetter)setter + andDefaultGetter:(nullable RCTBridgelessBundleURLGetter)defaultGetter; - (void)resetBundleURL; -@property NSURL *bundleURL; +@property (nonatomic, nullable) NSURL *bundleURL; +@property (nonatomic, nonnull) RCTBundleConfiguration *bundleConfig; @end diff --git a/packages/react-native/React/Base/RCTBundleManager.m b/packages/react-native/React/Base/RCTBundleManager.m index baa23d9123653a..88d2217efcf16e 100644 --- a/packages/react-native/React/Base/RCTBundleManager.m +++ b/packages/react-native/React/Base/RCTBundleManager.m @@ -6,9 +6,100 @@ */ #import "RCTBundleManager.h" +#import +#import #import "RCTAssert.h" #import "RCTBridge+Private.h" #import "RCTBridge.h" +#import "RCTLog.h" + +@implementation RCTBundleConfiguration + ++ (instancetype)defaultConfiguration +{ + return [[self alloc] initWithBundleFilePath:nil packagerServerScheme:nil packagerServerHost:nil bundlePath:nil]; +} + +- (instancetype)initWithBundleFilePath:(NSURL *)bundleFilePath +{ + return [self initWithBundleFilePath:bundleFilePath packagerServerScheme:nil packagerServerHost:nil bundlePath:nil]; +} + +- (instancetype)initWithPackagerServerScheme:(NSString *)packagerServerScheme + packagerServerHost:(NSString *)packagerServerHost + bundlePath:(NSString *)bundlePath +{ + return [self initWithBundleFilePath:nil + packagerServerScheme:packagerServerScheme + packagerServerHost:packagerServerHost + bundlePath:bundlePath]; +} + +- (instancetype)initWithBundleFilePath:(NSURL *)bundleFilePath + packagerServerScheme:(NSString *)packagerServerScheme + packagerServerHost:(NSString *)packagerServerHost + bundlePath:(NSString *)bundlePath +{ + if (self = [super init]) { + _bundleFilePath = bundleFilePath; + _packagerServerScheme = packagerServerScheme; + _packagerServerHost = packagerServerHost; + _bundlePath = bundlePath; + _packagerOptionsUpdater = ^NSMutableArray *(NSMutableArray *options) + { + return options; + }; + + // When the bundleFilePath is set in the RCTBundleConfiguration the Metro connection + // shouldn't be suggested/required. + if (_bundleFilePath != nil) { + RCTDevLoadingViewSetEnabled(false); + } + } + + return self; +} + +- (NSString *)getPackagerServerScheme +{ + if (!_packagerServerScheme) { + return [[RCTBundleURLProvider sharedSettings] packagerScheme]; + } + + return _packagerServerScheme; +} + +- (NSString *)getPackagerServerHost +{ + if (!_packagerServerHost) { + return [[RCTBundleURLProvider sharedSettings] packagerServerHostPort]; + } + + return _packagerServerHost; +} + +- (NSURL *)getBundleURL +{ + if (_packagerServerScheme && _packagerServerHost) { + return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:_bundlePath + packagerServerScheme:_packagerServerScheme + packagerServerHost:_packagerServerHost + packagerOptionsUpdater:_packagerOptionsUpdater]; + } + + if (_bundleFilePath) { + if (!_bundleFilePath.fileURL) { + RCTLogError(@"Bundle file path must be a file URL"); + return nil; + } + + return _bundleFilePath; + } + + return nil; +} + +@end @implementation RCTBundleManager { #ifndef RCT_REMOVE_LEGACY_ARCH @@ -19,6 +110,20 @@ @implementation RCTBundleManager { RCTBridgelessBundleURLGetter _bridgelessBundleURLDefaultGetter; } +- (instancetype)initWithBundleConfig:(RCTBundleConfiguration *)bundleConfig +{ + if (self = [super init]) { + self.bundleConfig = bundleConfig ? bundleConfig : [RCTBundleConfiguration defaultConfiguration]; + } + + return self; +} + +- (instancetype)init +{ + return [self initWithBundleConfig:[RCTBundleConfiguration defaultConfiguration]]; +} + #ifndef RCT_REMOVE_LEGACY_ARCH - (void)setBridge:(RCTBridge *)bridge { @@ -62,7 +167,13 @@ - (NSURL *)bundleURL _bridgelessBundleURLGetter != nil, @"RCTBundleManager: In bridgeless mode, RCTBridgelessBundleURLGetter must not be nil."); - return _bridgelessBundleURLGetter(); + NSURL *bundleURL = [_bundleConfig getBundleURL]; + + if (bundleURL == nil) { + return _bridgelessBundleURLGetter(); + } + + return bundleURL; } - (void)resetBundleURL diff --git a/packages/react-native/React/Base/RCTBundleURLProvider.h b/packages/react-native/React/Base/RCTBundleURLProvider.h index 3ade62f9c2f2ba..cc060e4d42d23a 100644 --- a/packages/react-native/React/Base/RCTBundleURLProvider.h +++ b/packages/react-native/React/Base/RCTBundleURLProvider.h @@ -7,6 +7,7 @@ #import +#import "RCTBundleManager.h" #import "RCTDefines.h" RCT_EXTERN NSString *_Nonnull const RCTBundleURLProviderUpdatedNotification; @@ -88,6 +89,16 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSURL *__nullable)jsBundleURLForFallbackExtension:(NSString *__nullable)extension; +/** + * Returns the jsBundleURL for a given bundle entrypoint, + * the packager scheme, server host and options updater + * for modifying default packager options. + */ +- (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot + packagerServerScheme:(NSString *)packagerServerScheme + packagerServerHost:(NSString *)packagerServerHost + packagerOptionsUpdater:(RCTPackagerOptionsUpdater)packagerOptionsUpdater; + /** * Returns the resourceURL for a given bundle entrypoint and * the fallback offline resource file if the packager is not running. @@ -97,6 +108,19 @@ NS_ASSUME_NONNULL_BEGIN resourceExtension:(NSString *)extension offlineBundle:(NSBundle *)offlineBundle; +/** + * Returns the query items for given options used to create the jsBundleURL. + */ ++ (NSArray *)createJSBundleURLQuery:(NSString *)packagerHost + packagerScheme:(NSString *__nullable)scheme + enableDev:(BOOL)enableDev + enableMinification:(BOOL)enableMinification + inlineSourceMap:(BOOL)inlineSourceMap + modulesOnly:(BOOL)modulesOnly + runModule:(BOOL)runModule + additionalOptions: + (NSDictionary *__nullable)additionalOptions; + /** * The IP address or hostname of the packager. */ diff --git a/packages/react-native/React/Base/RCTBundleURLProvider.mm b/packages/react-native/React/Base/RCTBundleURLProvider.mm index 89ccd0c7ad7a58..e99b2dcc1515cf 100644 --- a/packages/react-native/React/Base/RCTBundleURLProvider.mm +++ b/packages/react-native/React/Base/RCTBundleURLProvider.mm @@ -313,6 +313,54 @@ + (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot additionalOptions:(NSDictionary *__nullable)additionalOptions { NSString *path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot]; + NSArray *queryItems = [self createJSBundleURLQuery:packagerHost + packagerScheme:scheme + enableDev:enableDev + enableMinification:enableMinification + inlineSourceMap:inlineSourceMap + modulesOnly:modulesOnly + runModule:runModule + additionalOptions:additionalOptions]; + + return [RCTBundleURLProvider resourceURLForResourcePath:path + packagerHost:packagerHost + scheme:scheme + queryItems:[queryItems copy]]; +} + +- (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot + packagerServerScheme:(NSString *)packagerServerScheme + packagerServerHost:(NSString *)packagerServerHost + packagerOptionsUpdater:(RCTPackagerOptionsUpdater)packagerOptionsUpdater +{ + NSArray *queryItems = [RCTBundleURLProvider createJSBundleURLQuery:packagerServerHost + packagerScheme:packagerServerScheme + enableDev:[self enableDev] + enableMinification:[self enableMinification] + inlineSourceMap:[self inlineSourceMap] + modulesOnly:NO + runModule:YES + additionalOptions:nil]; + + NSArray *updatedQueryItems = packagerOptionsUpdater((NSMutableArray *)queryItems); + NSString *path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot]; + + return [RCTBundleURLProvider resourceURLForResourcePath:path + packagerHost:packagerServerHost + scheme:packagerServerScheme + queryItems:updatedQueryItems]; +} + ++ (NSArray *)createJSBundleURLQuery:(NSString *)packagerHost + packagerScheme:(NSString *__nullable)scheme + enableDev:(BOOL)enableDev + enableMinification:(BOOL)enableMinification + inlineSourceMap:(BOOL)inlineSourceMap + modulesOnly:(BOOL)modulesOnly + runModule:(BOOL)runModule + additionalOptions: + (NSDictionary *__nullable)additionalOptions +{ BOOL lazy = enableDev; NSMutableArray *queryItems = [[NSMutableArray alloc] initWithArray:@[ [[NSURLQueryItem alloc] initWithName:@"platform" value:RCTPlatformName], @@ -345,10 +393,7 @@ + (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot } } - return [[self class] resourceURLForResourcePath:path - packagerHost:packagerHost - scheme:scheme - queryItems:[queryItems copy]]; + return queryItems; } + (NSURL *)resourceURLForResourcePath:(NSString *)path diff --git a/packages/react-native/React/CoreModules/RCTDevSettings.h b/packages/react-native/React/CoreModules/RCTDevSettings.h index a4d9c8f9e30328..242c3f4fbdeb61 100644 --- a/packages/react-native/React/CoreModules/RCTDevSettings.h +++ b/packages/react-native/React/CoreModules/RCTDevSettings.h @@ -11,6 +11,8 @@ #import #import +@class RCTPackagerConnection; + @protocol RCTPackagerClientMethod; /** @@ -85,6 +87,10 @@ */ @property (nonatomic, assign) BOOL isPerfMonitorShown; +#if RCT_DEV +@property (nonatomic, readonly) RCTPackagerConnection *packagerConnection; +#endif + /** * Toggle the element inspector. */ diff --git a/packages/react-native/React/CoreModules/RCTDevSettings.mm b/packages/react-native/React/CoreModules/RCTDevSettings.mm index fbbeec0e703d53..20cbc8f8b57223 100644 --- a/packages/react-native/React/CoreModules/RCTDevSettings.mm +++ b/packages/react-native/React/CoreModules/RCTDevSettings.mm @@ -32,9 +32,12 @@ static NSString *const kRCTDevSettingsUserDefaultsKey = @"RCTDevMenu"; +#if RCT_DEV +#import +#endif + #if RCT_DEV_SETTINGS_ENABLE_PACKAGER_CONNECTION #import -#import #endif #if RCT_ENABLE_INSPECTOR @@ -145,6 +148,9 @@ - (instancetype)init }; RCTDevSettingsUserDefaultsDataSource *dataSource = [[RCTDevSettingsUserDefaultsDataSource alloc] initWithDefaultValues:defaultValues]; +#if RCT_DEV + _packagerConnection = [RCTPackagerConnection new]; +#endif _isShakeGestureEnabled = true; return [self initWithDataSource:dataSource]; } @@ -178,18 +184,27 @@ - (instancetype)initWithDataSource:(id)dataSource - (void)initialize { +#if RCT_DEV + [_packagerConnection startWithBundleManager:_bundleManager]; +#endif + #if RCT_DEV_SETTINGS_ENABLE_PACKAGER_CONNECTION if (numInitializedModules++ == 0) { - reloadToken = [[RCTPackagerConnection sharedPackagerConnection] + reloadToken = [_packagerConnection addNotificationHandler:^(id params) { RCTTriggerReloadCommandListeners(@"Global hotkey"); } queue:dispatch_get_main_queue() forMethod:@"reload"]; #if RCT_DEV_MENU - devMenuToken = [[RCTPackagerConnection sharedPackagerConnection] + __weak __typeof(self) weakSelf = self; + devMenuToken = [_packagerConnection addNotificationHandler:^(id params) { - [[self.moduleRegistry moduleForName:"DevMenu"] show]; + __typeof(self) strongSelf = weakSelf; + if (strongSelf == nullptr) { + return; + } + [[strongSelf.moduleRegistry moduleForName:"DevMenu"] show]; } queue:dispatch_get_main_queue() forMethod:@"devMenu"]; @@ -240,9 +255,9 @@ - (void)invalidate [super invalidate]; #if RCT_DEV_SETTINGS_ENABLE_PACKAGER_CONNECTION if (--numInitializedModules == 0) { - [[RCTPackagerConnection sharedPackagerConnection] removeHandler:reloadToken]; + [_packagerConnection removeHandler:reloadToken]; #if RCT_DEV_MENU - [[RCTPackagerConnection sharedPackagerConnection] removeHandler:devMenuToken]; + [_packagerConnection removeHandler:devMenuToken]; #endif } #endif @@ -425,7 +440,7 @@ - (void)setExecutorClass:(Class)executorClass - (void)addHandler:(id)handler forPackagerMethod:(NSString *)name { #if RCT_DEV_SETTINGS_ENABLE_PACKAGER_CONNECTION - [[RCTPackagerConnection sharedPackagerConnection] addHandler:handler forMethod:name]; + [_packagerConnection addHandler:handler forMethod:name]; #endif } diff --git a/packages/react-native/React/DevSupport/RCTPackagerConnection.h b/packages/react-native/React/DevSupport/RCTPackagerConnection.h index cdd7c745a895ff..68310757c9c752 100644 --- a/packages/react-native/React/DevSupport/RCTPackagerConnection.h +++ b/packages/react-native/React/DevSupport/RCTPackagerConnection.h @@ -7,6 +7,7 @@ #import +#import #import #if RCT_DEV @@ -24,8 +25,6 @@ typedef void (^RCTConnectedHandler)(void); /** Encapsulates singleton connection to React Native packager. */ @interface RCTPackagerConnection : NSObject -+ (instancetype)sharedPackagerConnection; - /** * Registers a handler for a notification broadcast from the packager. An * example is "reload" - an instruction to reload from the packager. @@ -62,6 +61,9 @@ typedef void (^RCTConnectedHandler)(void); /** Reconnect with given packager server, if packagerServerHostPort has changed. */ - (void)reconnect:(NSString *)packagerServerHostPort; +/** starts packager connection with configuration from the bundle manager */ +- (void)startWithBundleManager:(RCTBundleManager *)bundleManager; + /** * Historically no distinction was made between notification and request * handlers. If you use this method, it will be registered as *both* a diff --git a/packages/react-native/React/DevSupport/RCTPackagerConnection.mm b/packages/react-native/React/DevSupport/RCTPackagerConnection.mm index 95dbf82437c934..1c17945855dc32 100644 --- a/packages/react-native/React/DevSupport/RCTPackagerConnection.mm +++ b/packages/react-native/React/DevSupport/RCTPackagerConnection.mm @@ -13,6 +13,7 @@ #import #import +#import #import #import #import @@ -50,43 +51,13 @@ @implementation RCTPackagerConnection { std::vector> _notificationRegistrations; std::vector> _requestRegistrations; std::vector> _connectedRegistrations; -} - -+ (instancetype)sharedPackagerConnection -{ - static RCTPackagerConnection *connection; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - connection = [RCTPackagerConnection new]; - }); - return connection; + RCTBundleManager *_bundleManager; } - (instancetype)init { if (self = [super init]) { _nextToken = 1; // Prevent randomly erasing a handler if you pass a bogus 0 token - _serverHostPortForSocket = [[RCTBundleURLProvider sharedSettings] packagerServerHostPort]; - _serverSchemeForSocket = [[RCTBundleURLProvider sharedSettings] packagerScheme]; - _socket = socketForLocation(_serverHostPortForSocket, _serverSchemeForSocket); - _socket.delegate = self; - [_socket start]; - - RCTPackagerConnection *const __weak weakSelf = self; - _bundleURLChangeObserver = - [[NSNotificationCenter defaultCenter] addObserverForName:RCTBundleURLProviderUpdatedNotification - object:nil - queue:[NSOperationQueue mainQueue] - usingBlock:^(NSNotification *_Nonnull __unused note) { - [weakSelf bundleURLSettingsChanged]; - }]; - _reloadWithPotentiallyNewURLObserver = - [[NSNotificationCenter defaultCenter] addObserverForName:RCTTriggerReloadCommandNotification - object:nil - queue:[NSOperationQueue mainQueue] - usingBlock:^(NSNotification *_Nonnull __unused note) { - [weakSelf bundleURLSettingsChanged]; - }]; } return self; } @@ -119,6 +90,32 @@ - (instancetype)init return [[RCTReconnectingWebSocket alloc] initWithURL:components.URL queue:queue]; } +- (void)startWithBundleManager:(RCTBundleManager *)bundleManager +{ + _serverHostPortForSocket = [bundleManager.bundleConfig getPackagerServerHost]; + _serverSchemeForSocket = [bundleManager.bundleConfig getPackagerServerScheme]; + _socket = socketForLocation(_serverHostPortForSocket, _serverSchemeForSocket); + _socket.delegate = self; + [_socket start]; + _bundleManager = bundleManager; + + RCTPackagerConnection *const __weak weakSelf = self; + _bundleURLChangeObserver = + [[NSNotificationCenter defaultCenter] addObserverForName:RCTBundleURLProviderUpdatedNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *_Nonnull __unused note) { + [weakSelf bundleURLSettingsChanged]; + }]; + _reloadWithPotentiallyNewURLObserver = + [[NSNotificationCenter defaultCenter] addObserverForName:RCTTriggerReloadCommandNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *_Nonnull __unused note) { + [weakSelf bundleURLSettingsChanged]; + }]; +} + - (void)stop { std::lock_guard l(_mutex); @@ -144,7 +141,7 @@ - (void)reconnect:(NSString *)packagerServerHostPort return; // already stopped } - NSString *const serverScheme = [[RCTBundleURLProvider sharedSettings] packagerScheme]; + NSString *const serverScheme = [_bundleManager.bundleConfig getPackagerServerScheme]; if ([packagerServerHostPort isEqual:_serverHostPortForSocket] && [serverScheme isEqual:_serverSchemeForSocket]) { return; // unchanged } @@ -161,7 +158,7 @@ - (void)reconnect:(NSString *)packagerServerHostPort - (void)bundleURLSettingsChanged { // Will only reconnect if `packagerServerHostPort` has actually changed - [self reconnect:[[RCTBundleURLProvider sharedSettings] packagerServerHostPort]]; + [self reconnect:[_bundleManager.bundleConfig getPackagerServerHost]]; } - (RCTHandlerToken)addNotificationHandler:(RCTNotificationHandler)handler diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h index 0aa5845a80f3c1..9f4721be7f6dc7 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.h @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @class RCTFabricSurface; @class RCTHost; @class RCTModuleRegistry; +@class RCTBundleConfiguration; @class RCTDevMenuConfiguration; @protocol RCTTurboModuleManagerDelegate; @@ -65,8 +66,8 @@ typedef std::shared_ptr (^RCTHostJSEngineProv turboModuleManagerDelegate:(id)turboModuleManagerDelegate jsEngineProvider:(RCTHostJSEngineProvider)jsEngineProvider launchOptions:(nullable NSDictionary *)launchOptions - devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration - NS_DESIGNATED_INITIALIZER; + bundleConfiguration:(RCTBundleConfiguration *)bundleConfiguration + devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration NS_DESIGNATED_INITIALIZER; - (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider hostDelegate:(id)hostDelegate diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm index 5c7bf6b986a7bd..07f4c0686816fb 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm @@ -152,10 +152,6 @@ - (instancetype)initWithBundleURL:(NSURL *)bundleURL launchOptions:launchOptions]; } -/** - Host initialization should not be resource intensive. A host may be created before any intention of using React Native - has been expressed. - */ - (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider hostDelegate:(id)hostDelegate turboModuleManagerDelegate:(id)turboModuleManagerDelegate @@ -167,24 +163,32 @@ - (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider turboModuleManagerDelegate:turboModuleManagerDelegate jsEngineProvider:jsEngineProvider launchOptions:launchOptions + bundleConfiguration:[RCTBundleConfiguration defaultConfiguration] devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]]; } +/** + Host initialization should not be resource intensive. A host may be created before any intention of using React Native + has been expressed. + */ - (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider hostDelegate:(id)hostDelegate turboModuleManagerDelegate:(id)turboModuleManagerDelegate jsEngineProvider:(RCTHostJSEngineProvider)jsEngineProvider launchOptions:(nullable NSDictionary *)launchOptions + bundleConfiguration:(RCTBundleConfiguration *)bundleConfiguration devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration { if (self = [super init]) { _hostDelegate = hostDelegate; _turboModuleManagerDelegate = turboModuleManagerDelegate; - _bundleManager = [RCTBundleManager new]; + _bundleManager = [[RCTBundleManager alloc] initWithBundleConfig:bundleConfiguration]; _moduleRegistry = [RCTModuleRegistry new]; _jsEngineProvider = [jsEngineProvider copy]; _launchOptions = [launchOptions copy]; + [self setBundleURLProvider:provider]; + __weak RCTHost *weakSelf = self; auto bundleURLGetter = ^NSURL *() { RCTHost *strongSelf = weakSelf; @@ -450,7 +454,7 @@ - (void)_setBundleURL:(NSURL *)bundleURL // Sanitize the bundle URL _bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString]; - // Update the global bundle URLq + // Update the global bundle URL RCTReloadCommandSetBundleURL(_bundleURL); }