Skip to content

Commit d871bb8

Browse files
coadofacebook-github-bot
authored andcommitted
iOS: Add new RCTCustomBundleConfiguration for modifying bundle URL (#54006)
Summary: Following the [RFC](react-native-community/discussions-and-proposals#933), this PR introduces a new `RCTCustomBundleConfiguration` interface for modifying the bundle URL and exposes a new API for setting its instance in the `RCTReactNativeFactory`. The configuration object includes: - bundleFilePath - the URL of the bundle to load from the file system, - packagerServerScheme - the server scheme (e.g. http or https) to use when loading from the packager, - packagerServerHost - the server host (e.g. localhost) to use when loading from the packager. It associates `RCTPackagerConnection` (previously singleton) with the `RCTDevSettings` instance, which has access to the `RCTBundleManager`, which contains the specified configuration object. The connection is now established in the `RCTDevSettings initialize` method, called after the bundle manager is set by invoking the new `startWithBundleManager` method on the `RCTPackagerConnection`. The `RCTCustomBundleConfiguration` allows only for either `bundleFilePath` or `(packagerServerScheme, packagerServerHost)` to be set by defining appropriate initializers. The logic for creating bundle URL query items is extracted to a separate `createJSBundleURLQuery` method and is used by `RCTBundleManager` to set the configured `packagerServerHost` and `packagerServerScheme`. If the configuration is not defined, the `getBundleURL` method returns the result of the passed `fallbackURLProvider`. The `bundleFilePath` should be created with `[NSURL fileURLWithPath:<path>]`, as otherwise the HMR client is created and fails ungracefully. The check is added in the `getBundle` method to log the error beforehand: <img width="306" height="822" alt="Simulator Screenshot - iPhone 16 Pro - 2025-10-15 at 17 09 58" src="https://github.com/user-attachments/assets/869eed16-c5d8-4204-81d7-bd9cd42b2223" /> When the `bundleFilePath` is set in the `RCTCustomBundleConfiguration` the `Connect to Metro...` message shouldn't be suggested. ## Changelog: [IOS][ADDED] - Add new `RCTCustomBundleConfiguration` for modifying bundle URL on `RCTReactNativeFactory`. Test Plan: Tested changing `packagerServerHost` from the `AppDelegate` by re-creating the React Native instance with updated `RCTCustomBundleConfiguration`. I've run two Metro instances, each serving a different JS bundle (changed background) on `8081` and `8082` ports. The native `Restart RN:<current port>` button on top of the screen toggles between ports (used in bundle configuration) and re-creates connections. Tested with `RCT_DEV` set to true and false. https://github.com/user-attachments/assets/fd57068b-869c-4f45-93be-09d33f691cea For setting bundle source from a file, I've generated bundle with a blue background and created a custom bundle configuration using `initWithBundleFilePath`. I've run the app without starting Metro: <img width="306" height="822" alt="Simulator Screenshot - iPhone 16 Pro - 2025-10-15 at 17 06 21" src="https://github.com/user-attachments/assets/8283f202-0150-4e93-a4a9-d2b6ea6f1c37" /> <details> <summary>code:</summary> `AppDelegate.mm` ```objc #import "AppDelegate.h" #import <UserNotifications/UserNotifications.h> #import <React/RCTBundleManager.h> #import <React/RCTBundleURLProvider.h> #import <React/RCTDefines.h> #import <React/RCTLinkingManager.h> #import <ReactCommon/RCTSampleTurboModule.h> #import <ReactCommon/RCTTurboModuleManager.h> #import <React/RCTPushNotificationManager.h> #import <NativeCxxModuleExample/NativeCxxModuleExample.h> #ifndef RN_DISABLE_OSS_PLUGIN_HEADER #import <RNTMyNativeViewComponentView.h> #endif #if __has_include(<ReactAppDependencyProvider/RCTAppDependencyProvider.h>) #define USE_OSS_CODEGEN 1 #import <ReactAppDependencyProvider/RCTAppDependencyProvider.h> #else #define USE_OSS_CODEGEN 0 #endif static NSString *kBundlePath = @"js/RNTesterApp.ios"; interface AppDelegate () <UNUserNotificationCenterDelegate> end implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.launchOptions = launchOptions; self.port = @"8081"; #if USE_OSS_CODEGEN self.dependencyProvider = [RCTAppDependencyProvider new]; #endif self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; [self startReactNative]; [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; return YES; } - (void)startReactNative { self.reactNativeFactory = [[RCTReactNativeFactory alloc] initWithDelegate:self]; NSString *packagerServerHost = [NSString stringWithFormat:@"localhost:%@", self.port]; RCTCustomBundleConfiguration *customBundleConfiguration = [[RCTCustomBundleConfiguration alloc] initWithPackagerServerScheme:@"http" packagerServerHost:packagerServerHost bundlePath:kBundlePath]; self.reactNativeFactory.customBundleConfiguration = customBundleConfiguration; [self.reactNativeFactory startReactNativeWithModuleName:@"RNTesterApp" inWindow:self.window initialProperties:[self prepareInitialProps] launchOptions:self.launchOptions]; [self createTopButton]; } - (void)createTopButton { NSString *title = [NSString stringWithFormat:@"Restart RN:%@", self.port]; self.topButton = [UIButton buttonWithType:UIButtonTypeSystem]; [self.topButton setTitle:title forState:UIControlStateNormal]; [self.topButton setBackgroundColor:[UIColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:1]]; [self.topButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; CGFloat buttonWidth = 120; CGFloat buttonHeight = 44; CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; self.topButton.frame = CGRectMake((screenWidth - buttonWidth) / 2, 50, buttonWidth, buttonHeight); self.topButton.layer.cornerRadius = 8; [self.topButton addTarget:self action:selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; [self.window addSubview:self.topButton]; [self.window bringSubviewToFront:self.topButton]; } - (void)togglePort { self.port = [self.port isEqual: @"8081"] ? @"8082" : @"8081"; } - (void)buttonTapped:(UIButton *)sender { self.reactNativeFactory = nil; [self togglePort]; [self startReactNative]; } - (NSDictionary *)prepareInitialProps { NSMutableDictionary *initProps = [NSMutableDictionary new]; NSString *_routeUri = [[NSUserDefaults standardUserDefaults] stringForKey:@"route"]; if (_routeUri) { initProps[@"exampleFromAppetizeParams"] = [NSString stringWithFormat:@"rntester://example/%Example", _routeUri]; } return initProps; } - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:kBundlePath]; } - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options { return [RCTLinkingManager application:app openURL:url options:options]; } - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker { if (name == facebook::react::NativeCxxModuleExample::kModuleName) { return std::make_shared<facebook::react::NativeCxxModuleExample>(jsInvoker); } return [super getTurboModule:name jsInvoker:jsInvoker]; } // Required for the remoteNotificationsRegistered event. - (void)application:(__unused UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } // Required for the remoteNotificationRegistrationError event. - (void)application:(__unused UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [RCTPushNotificationManager didFailToRegisterForRemoteNotificationsWithError:error]; } #pragma mark - UNUserNotificationCenterDelegate // Required for the remoteNotificationReceived and localNotificationReceived events - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { [RCTPushNotificationManager didReceiveNotification:notification]; completionHandler(UNNotificationPresentationOptionNone); } // Required for the remoteNotificationReceived and localNotificationReceived events // Called when a notification is tapped from background. (Foreground notification will not be shown per // the presentation option selected above). - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { UNNotification *notification = response.notification; // This condition will be true if tapping the notification launched the app. if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) { // This can be retrieved with getInitialNotification. [RCTPushNotificationManager setInitialNotification:notification]; } [RCTPushNotificationManager didReceiveNotification:notification]; completionHandler(); } #pragma mark - New Arch Enabled settings - (BOOL)bridgelessEnabled { return YES; } #pragma mark - RCTComponentViewFactoryComponentProvider #ifndef RN_DISABLE_OSS_PLUGIN_HEADER - (nonnull NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents { NSMutableDictionary *dict = [super thirdPartyFabricComponents].mutableCopy; if (!dict[@"RNTMyNativeView"]) { dict[@"RNTMyNativeView"] = NSClassFromString(@"RNTMyNativeViewComponentView"); } if (!dict[@"SampleNativeComponent"]) { dict[@"SampleNativeComponent"] = NSClassFromString(@"RCTSampleNativeComponentComponentView"); } return dict; } #endif - (NSURL *)bundleURL { return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:kBundlePath]; } end ``` `AppDelegate.h` ```objc #import <RCTDefaultReactNativeFactoryDelegate.h> #import <RCTReactNativeFactory.h> #import <UIKit/UIKit.h> interface AppDelegate : RCTDefaultReactNativeFactoryDelegate <UIApplicationDelegate> property (nonatomic, strong, nonnull) UIWindow *window; property (nonatomic, strong, nonnull) RCTReactNativeFactory *reactNativeFactory; property (nonatomic, strong, nullable) UIButton *topButton; property (nonatomic, strong) NSDictionary *launchOptions; property (nonatomic, assign) NSString *port; end ``` </details> Differential Revision: D84058022 Pulled By: coado
1 parent 50b1bec commit d871bb8

File tree

15 files changed

+312
-70
lines changed

15 files changed

+312
-70
lines changed

packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
@class RCTBridge;
2525
@protocol RCTComponentViewProtocol;
2626
@class RCTSurfacePresenterBridgeAdapter;
27+
@class RCTCustomBundleConfiguration;
2728
@class RCTDevMenuConfiguration;
2829

2930
NS_ASSUME_NONNULL_BEGIN
@@ -117,6 +118,8 @@ typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable };
117118

118119
@property (nonatomic, weak) id<RCTReactNativeFactoryDelegate> delegate;
119120

121+
@property (nonatomic, nullable) RCTCustomBundleConfiguration *customBundleConfiguration;
122+
120123
@property (nonatomic, nullable) RCTDevMenuConfiguration *devMenuConfiguration;
121124

122125
@end

packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
#import "RCTReactNativeFactory.h"
9+
#import <React/RCTBundleManager.h>
910
#import <React/RCTColorSpaceUtils.h>
1011
#import <React/RCTDevMenu.h>
1112
#import <React/RCTLog.h>
@@ -59,6 +60,8 @@ - (instancetype)initWithDelegate:(id<RCTReactNativeFactoryDelegate>)delegate rel
5960
self.rootViewFactory = [self createRCTRootViewFactory];
6061

6162
[RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self;
63+
64+
self.customBundleConfiguration = [RCTCustomBundleConfiguration new];
6265
}
6366

6467
return self;
@@ -84,6 +87,7 @@ - (void)startReactNativeWithModuleName:(NSString *)moduleName
8487
UIView *rootView = [self.rootViewFactory viewWithModuleName:moduleName
8588
initialProperties:initialProperties
8689
launchOptions:launchOptions
90+
customBundleConfiguration:self.customBundleConfiguration
8791
devMenuConfiguration:self.devMenuConfiguration];
8892
UIViewController *rootViewController = [_delegate createRootViewController];
8993
[_delegate setRootView:rootView toRootViewController:rootViewController];

packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
@class RCTHost;
1919
@class RCTRootView;
2020
@class RCTSurfacePresenterBridgeAdapter;
21+
@class RCTCustomBundleConfiguration;
2122
@class RCTDevMenuConfiguration;
2223

2324
NS_ASSUME_NONNULL_BEGIN
@@ -202,11 +203,13 @@ typedef void (^RCTLoadSourceForBridgeBlock)(RCTBridge *bridge, RCTSourceLoadBloc
202203
* @parameter: moduleName - the name of the app, used by Metro to resolve the module.
203204
* @parameter: initialProperties - a set of initial properties.
204205
* @parameter: launchOptions - a dictionary with a set of options.
206+
* @parameter: customBundleConfiguration - a configuration for custom bundle source URL.
205207
* @parameter: devMenuConfiguration - a configuration for enabling/disabling dev menu.
206208
*/
207209
- (UIView *_Nonnull)viewWithModuleName:(NSString *)moduleName
208210
initialProperties:(NSDictionary *__nullable)initialProperties
209211
launchOptions:(NSDictionary *__nullable)launchOptions
212+
customBundleConfiguration:(RCTCustomBundleConfiguration *__nullable)customBundleConfiguration
210213
devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration;
211214

212215
- (UIView *_Nonnull)viewWithModuleName:(NSString *)moduleName
@@ -226,15 +229,18 @@ typedef void (^RCTLoadSourceForBridgeBlock)(RCTBridge *bridge, RCTSourceLoadBloc
226229
* Use it to speed up later viewWithModuleName: calls.
227230
*
228231
* @parameter: launchOptions - a dictionary with a set of options.
232+
* @parameter: customBundleConfiguration - a configuration for custom bundle source URL.
229233
* @parameter: devMenuConfiguration - a configuration for enabling/disabling dev menu.
230234
*/
231235
- (void)initializeReactHostWithLaunchOptions:(NSDictionary *__nullable)launchOptions
236+
customBundleConfiguration:(RCTCustomBundleConfiguration *__nullable)customBundleConfiguration
232237
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration;
233238

234-
- (RCTHost *)createReactHost:(NSDictionary *__nullable)launchOptions;
235-
236239
- (RCTHost *)createReactHost:(NSDictionary *__nullable)launchOptions
237-
devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration;
240+
customBundleConfiguration:(RCTCustomBundleConfiguration *__nullable)customBundleConfiguration
241+
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration;
242+
243+
- (RCTHost *)createReactHost:(NSDictionary *__nullable)launchOptions;
238244

239245
@end
240246

packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
#else
2222
#import <React/CoreModulesPlugins.h>
2323
#endif
24-
#import <React/RCTBundleURLProvider.h>
2524
#import <React/RCTComponentViewFactory.h>
2625
#import <React/RCTComponentViewProtocol.h>
2726
#import <React/RCTFabricSurface.h>
@@ -137,6 +136,7 @@ - (UIView *)viewWithModuleName:(NSString *)moduleName initialProperties:(NSDicti
137136
return [self viewWithModuleName:moduleName
138137
initialProperties:initialProperties
139138
launchOptions:nil
139+
customBundleConfiguration:[RCTCustomBundleConfiguration new]
140140
devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
141141
}
142142

@@ -145,17 +145,21 @@ - (UIView *)viewWithModuleName:(NSString *)moduleName
145145
return [self viewWithModuleName:moduleName
146146
initialProperties:nil
147147
launchOptions:nil
148+
customBundleConfiguration:[RCTCustomBundleConfiguration new]
148149
devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
149150
}
150151

151152
- (void)initializeReactHostWithLaunchOptions:(NSDictionary *)launchOptions
153+
customBundleConfiguration:(RCTCustomBundleConfiguration *)customBundleConfiguration
152154
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
153155
{
154156
// Enable TurboModule interop by default in Bridgeless mode
155157
RCTEnableTurboModuleInterop(YES);
156158
RCTEnableTurboModuleInteropBridgeProxy(YES);
157159

158-
[self createReactHostIfNeeded:launchOptions devMenuConfiguration:devMenuConfiguration];
160+
[self createReactHostIfNeeded:launchOptions
161+
customBundleConfiguration:customBundleConfiguration
162+
devMenuConfiguration:devMenuConfiguration];
159163
return;
160164
}
161165

@@ -166,15 +170,19 @@ - (UIView *)viewWithModuleName:(NSString *)moduleName
166170
return [self viewWithModuleName:moduleName
167171
initialProperties:initialProperties
168172
launchOptions:launchOptions
173+
customBundleConfiguration:[RCTCustomBundleConfiguration new]
169174
devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
170175
}
171176

172177
- (UIView *)viewWithModuleName:(NSString *)moduleName
173178
initialProperties:(NSDictionary *)initProps
174179
launchOptions:(NSDictionary *)launchOptions
180+
customBundleConfiguration:(RCTCustomBundleConfiguration *)customBundleConfiguration
175181
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
176182
{
177-
[self initializeReactHostWithLaunchOptions:launchOptions devMenuConfiguration:devMenuConfiguration];
183+
[self initializeReactHostWithLaunchOptions:launchOptions
184+
customBundleConfiguration:customBundleConfiguration
185+
devMenuConfiguration:devMenuConfiguration];
178186

179187
RCTFabricSurface *surface = [self.reactHost createSurfaceWithModuleName:moduleName
180188
initialProperties:initProps ? initProps : @{}];
@@ -245,21 +253,28 @@ - (void)createBridgeAdapterIfNeeded
245253
#pragma mark - New Arch Utilities
246254

247255
- (void)createReactHostIfNeeded:(NSDictionary *)launchOptions
256+
customBundleConfiguration:(RCTCustomBundleConfiguration *)customBundleConfiguration
248257
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
249258
{
250259
if (self.reactHost) {
251260
return;
252261
}
253-
self.reactHost = [self createReactHost:launchOptions devMenuConfiguration:devMenuConfiguration];
262+
263+
self.reactHost = [self createReactHost:launchOptions
264+
customBundleConfiguration:customBundleConfiguration
265+
devMenuConfiguration:devMenuConfiguration];
254266
}
255267

256268
- (RCTHost *)createReactHost:(NSDictionary *)launchOptions
257269
{
258-
return [self createReactHost:launchOptions devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
270+
return [self createReactHost:launchOptions
271+
customBundleConfiguration:[RCTCustomBundleConfiguration new]
272+
devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
259273
}
260274

261275
- (RCTHost *)createReactHost:(NSDictionary *)launchOptions
262-
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
276+
customBundleConfiguration:(RCTCustomBundleConfiguration *)customBundleConfiguration
277+
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
263278
{
264279
__weak __typeof(self) weakSelf = self;
265280
RCTHost *reactHost =
@@ -270,6 +285,7 @@ - (RCTHost *)createReactHost:(NSDictionary *)launchOptions
270285
return [weakSelf createJSRuntimeFactory];
271286
}
272287
launchOptions:launchOptions
288+
customBundleConfiguration:customBundleConfiguration
273289
devMenuConfiguration:devMenuConfiguration];
274290
[reactHost setBundleURLProvider:^NSURL *() {
275291
return [weakSelf bundleURL];

packages/react-native/React/Base/RCTBundleManager.h

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,66 @@
99

1010
@class RCTBridge;
1111

12-
typedef NSURL * (^RCTBridgelessBundleURLGetter)(void);
13-
typedef void (^RCTBridgelessBundleURLSetter)(NSURL *bundleURL);
12+
typedef NSURL *_Nullable (^RCTBridgelessBundleURLGetter)(void);
13+
typedef void (^RCTBridgelessBundleURLSetter)(NSURL *_Nullable bundleURL);
14+
typedef NSMutableArray<NSURLQueryItem *> *_Nullable (^RCTPackagerOptionsUpdater)(
15+
NSMutableArray<NSURLQueryItem *> *_Nullable options);
16+
17+
/**
18+
* Configuration class for setting up custom bundle locations
19+
*/
20+
@interface RCTCustomBundleConfiguration : NSObject
21+
22+
/**
23+
* The URL of the bundle to load from the file system
24+
*/
25+
@property (nonatomic, readonly, nullable) NSURL *bundleFilePath;
26+
27+
/**
28+
* The server scheme (e.g. http or https) to use when loading from the packager
29+
*/
30+
@property (nonatomic, readonly, nullable) NSString *packagerServerScheme;
31+
32+
/**
33+
* The server host (e.g. localhost) to use when loading from the packager
34+
*/
35+
@property (nonatomic, readonly, nullable) NSString *packagerServerHost;
36+
37+
/**
38+
* A block that modifies the packager options when loading from the packager
39+
*/
40+
@property (nonatomic, copy, nullable) RCTPackagerOptionsUpdater packagerOptionsUpdater;
41+
42+
/**
43+
* The relative path to the bundle.
44+
*/
45+
@property (nonatomic, readonly, nullable) NSString *bundlePath;
46+
47+
- (nonnull instancetype)initWithBundleFilePath:(nullable NSURL *)bundleFilePath;
48+
49+
- (nonnull instancetype)initWithPackagerServerScheme:(nullable NSString *)packagerServerScheme
50+
packagerServerHost:(nullable NSString *)packagerServerHost
51+
bundlePath:(nullable NSString *)bundlePath;
52+
53+
- (nullable NSURL *)getBundleURL:(NSURL *_Nullable (^_Nullable)(void))fallbackURLProvider;
54+
55+
- (nullable NSString *)getPackagerServerScheme;
56+
57+
- (nullable NSString *)getPackagerServerHost;
58+
59+
@end
1460

1561
/**
1662
* A class that allows NativeModules/TurboModules to read/write the bundleURL, with or without the bridge.
1763
*/
1864
@interface RCTBundleManager : NSObject
1965
#ifndef RCT_REMOVE_LEGACY_ARCH
20-
- (void)setBridge:(RCTBridge *)bridge;
66+
- (void)setBridge:(nullable RCTBridge *)bridge;
2167
#endif // RCT_REMOVE_LEGACY_ARCH
22-
- (void)setBridgelessBundleURLGetter:(RCTBridgelessBundleURLGetter)getter
23-
andSetter:(RCTBridgelessBundleURLSetter)setter
24-
andDefaultGetter:(RCTBridgelessBundleURLGetter)defaultGetter;
68+
- (void)setBridgelessBundleURLGetter:(nullable RCTBridgelessBundleURLGetter)getter
69+
andSetter:(nullable RCTBridgelessBundleURLSetter)setter
70+
andDefaultGetter:(nullable RCTBridgelessBundleURLGetter)defaultGetter;
2571
- (void)resetBundleURL;
26-
@property NSURL *bundleURL;
72+
@property (nonatomic, nullable) NSURL *bundleURL;
73+
@property (nonatomic, nullable) RCTCustomBundleConfiguration *customBundleConfig;
2774
@end

packages/react-native/React/Base/RCTBundleManager.m

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,94 @@
66
*/
77

88
#import "RCTBundleManager.h"
9+
#import <React/RCTBundleURLProvider.h>
910
#import "RCTAssert.h"
1011
#import "RCTBridge+Private.h"
1112
#import "RCTBridge.h"
13+
#import "RCTLog.h"
14+
15+
@implementation RCTCustomBundleConfiguration
16+
17+
- (instancetype)initWithBundleFilePath:(NSURL *)bundleFilePath
18+
{
19+
return [self initWithBundleFilePath:bundleFilePath packagerServerScheme:nil packagerServerHost:nil bundlePath:nil];
20+
}
21+
22+
- (instancetype)initWithPackagerServerScheme:(NSString *)packagerServerScheme
23+
packagerServerHost:(NSString *)packagerServerHost
24+
bundlePath:(NSString *)bundlePath
25+
{
26+
return [self initWithBundleFilePath:nil
27+
packagerServerScheme:packagerServerScheme
28+
packagerServerHost:packagerServerHost
29+
bundlePath:bundlePath];
30+
}
31+
32+
- (instancetype)initWithBundleFilePath:(NSURL *)bundleFilePath
33+
packagerServerScheme:(NSString *)packagerServerScheme
34+
packagerServerHost:(NSString *)packagerServerHost
35+
bundlePath:(NSString *)bundlePath
36+
{
37+
if (self = [super init]) {
38+
_bundleFilePath = bundleFilePath;
39+
_packagerServerScheme = packagerServerScheme;
40+
_packagerServerHost = packagerServerHost;
41+
_bundlePath = bundlePath;
42+
_packagerOptionsUpdater = ^NSMutableArray<NSURLQueryItem *> *(NSMutableArray<NSURLQueryItem *> *options)
43+
{
44+
return options;
45+
};
46+
}
47+
48+
return self;
49+
}
50+
51+
- (NSString *)getPackagerServerScheme
52+
{
53+
if (!_packagerServerScheme) {
54+
return [[RCTBundleURLProvider sharedSettings] packagerScheme];
55+
}
56+
57+
return _packagerServerScheme;
58+
}
59+
60+
- (NSString *)getPackagerServerHost
61+
{
62+
if (!_packagerServerHost) {
63+
return [[RCTBundleURLProvider sharedSettings] packagerServerHostPort];
64+
}
65+
66+
return _packagerServerHost;
67+
}
68+
69+
- (NSURL *)getBundleURL:(NSURL * (^)(void))fallbackURLProvider
70+
{
71+
if (_packagerServerScheme && _packagerServerHost) {
72+
NSArray<NSURLQueryItem *> *jsBundleURLQuery =
73+
[[RCTBundleURLProvider sharedSettings] createJSBundleURLQuery:_packagerServerHost
74+
packagerScheme:_packagerServerScheme];
75+
76+
NSArray<NSURLQueryItem *> *updatedBundleURLQuery = _packagerOptionsUpdater((NSMutableArray *)jsBundleURLQuery);
77+
NSString *path = [NSString stringWithFormat:@"/%@.bundle", _bundlePath];
78+
return [[RCTBundleURLProvider class] resourceURLForResourcePath:path
79+
packagerHost:_packagerServerHost
80+
scheme:_packagerServerScheme
81+
queryItems:updatedBundleURLQuery];
82+
}
83+
84+
if (_bundleFilePath) {
85+
if (!_bundleFilePath.fileURL) {
86+
RCTLogError(@"Bundle file path must be a file URL");
87+
return nil;
88+
}
89+
90+
return _bundleFilePath;
91+
}
92+
93+
return fallbackURLProvider();
94+
}
95+
96+
@end
1297

1398
@implementation RCTBundleManager {
1499
#ifndef RCT_REMOVE_LEGACY_ARCH
@@ -18,6 +103,7 @@ @implementation RCTBundleManager {
18103
RCTBridgelessBundleURLSetter _bridgelessBundleURLSetter;
19104
RCTBridgelessBundleURLGetter _bridgelessBundleURLDefaultGetter;
20105
}
106+
@synthesize customBundleConfig;
21107

22108
#ifndef RCT_REMOVE_LEGACY_ARCH
23109
- (void)setBridge:(RCTBridge *)bridge

packages/react-native/React/Base/RCTBundleURLProvider.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,19 @@ NS_ASSUME_NONNULL_BEGIN
9797
resourceExtension:(NSString *)extension
9898
offlineBundle:(NSBundle *)offlineBundle;
9999

100+
- (NSArray<NSURLQueryItem *> *)createJSBundleURLQuery:(NSString *)packagerHost
101+
packagerScheme:(NSString *__nullable)scheme;
102+
103+
+ (NSArray<NSURLQueryItem *> *)createJSBundleURLQuery:(NSString *)packagerHost
104+
packagerScheme:(NSString *__nullable)scheme
105+
enableDev:(BOOL)enableDev
106+
enableMinification:(BOOL)enableMinification
107+
inlineSourceMap:(BOOL)inlineSourceMap
108+
modulesOnly:(BOOL)modulesOnly
109+
runModule:(BOOL)runModule
110+
additionalOptions:
111+
(NSDictionary<NSString *, NSString *> *__nullable)additionalOptions;
112+
100113
/**
101114
* The IP address or hostname of the packager.
102115
*/

0 commit comments

Comments
 (0)