Commit 544c319
iOS: Add new
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: coadoRCTCustomBundleConfiguration for modifying bundle URL (facebook#54006)1 parent fcb51b1 commit 544c319
File tree
4 files changed
+192
-11
lines changed- packages/react-native/React/Base
4 files changed
+192
-11
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
13 | | - | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
14 | 60 | | |
15 | 61 | | |
16 | 62 | | |
17 | 63 | | |
18 | 64 | | |
19 | 65 | | |
20 | | - | |
| 66 | + | |
21 | 67 | | |
22 | | - | |
23 | | - | |
24 | | - | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
25 | 71 | | |
26 | | - | |
| 72 | + | |
| 73 | + | |
27 | 74 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
9 | 10 | | |
10 | 11 | | |
11 | 12 | | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
12 | 97 | | |
13 | 98 | | |
14 | 99 | | |
| |||
18 | 103 | | |
19 | 104 | | |
20 | 105 | | |
| 106 | + | |
21 | 107 | | |
22 | 108 | | |
23 | 109 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
97 | 97 | | |
98 | 98 | | |
99 | 99 | | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
100 | 113 | | |
101 | 114 | | |
102 | 115 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
313 | 313 | | |
314 | 314 | | |
315 | 315 | | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
316 | 354 | | |
317 | 355 | | |
318 | 356 | | |
| |||
345 | 383 | | |
346 | 384 | | |
347 | 385 | | |
348 | | - | |
349 | | - | |
350 | | - | |
351 | | - | |
| 386 | + | |
352 | 387 | | |
353 | 388 | | |
354 | 389 | | |
| |||
0 commit comments