Skip to content

Commit 781df04

Browse files
coadofacebook-github-bot
authored andcommitted
iOS: Add new RCTCustomBundleConfiguration for modifying bundle URL (facebook#54006)
Summary: ## Summary: Following the [RFC](react-native-community/discussions-and-proposals#933), this PR introduces a new `RCTBundleConfiguration` 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. The `RCTBundleConfiguration` 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 `RCTBundleConfiguration` the `Connect to Metro...` message shouldn't be suggested. ## Changelog: [IOS][ADDED] - Add new `RCTBundleConfiguration` for modifying bundle URL on `RCTReactNativeFactory`. Test Plan: Test plan included in the last diff in the stack. Differential Revision: D84058022 Pulled By: coado
1 parent a18f7a7 commit 781df04

File tree

4 files changed

+230
-11
lines changed

4 files changed

+230
-11
lines changed

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

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,71 @@
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 RCTBundleConfiguration : NSObject
21+
22+
+ (nonnull instancetype)defaultConfiguration;
23+
24+
/**
25+
* The URL of the bundle to load from the file system
26+
*/
27+
@property (nonatomic, readonly, nullable) NSURL *bundleFilePath;
28+
29+
/**
30+
* The server scheme (e.g. http or https) to use when loading from the packager
31+
*/
32+
@property (nonatomic, readonly, nullable) NSString *packagerServerScheme;
33+
34+
/**
35+
* The server host (e.g. localhost) to use when loading from the packager
36+
*/
37+
@property (nonatomic, readonly, nullable) NSString *packagerServerHost;
38+
39+
/**
40+
* A block that modifies the packager options when loading from the packager
41+
*/
42+
@property (nonatomic, copy, nullable) RCTPackagerOptionsUpdater packagerOptionsUpdater;
43+
44+
/**
45+
* The relative path to the bundle.
46+
*/
47+
@property (nonatomic, readonly, nullable) NSString *bundlePath;
48+
49+
- (nonnull instancetype)initWithBundleFilePath:(nullable NSURL *)bundleFilePath;
50+
51+
- (nonnull instancetype)initWithPackagerServerScheme:(nullable NSString *)packagerServerScheme
52+
packagerServerHost:(nullable NSString *)packagerServerHost
53+
bundlePath:(nullable NSString *)bundlePath;
54+
55+
- (nullable NSURL *)getBundleURL:(NSURL *_Nullable (^_Nullable)(void))fallbackURLProvider;
56+
57+
- (nullable NSString *)getPackagerServerScheme;
58+
59+
- (nullable NSString *)getPackagerServerHost;
60+
61+
@end
1462

1563
/**
1664
* A class that allows NativeModules/TurboModules to read/write the bundleURL, with or without the bridge.
1765
*/
1866
@interface RCTBundleManager : NSObject
67+
68+
- (nullable instancetype)initWithBundleConfig:(nullable RCTBundleConfiguration *)bundleConfig;
69+
1970
#ifndef RCT_REMOVE_LEGACY_ARCH
20-
- (void)setBridge:(RCTBridge *)bridge;
71+
- (void)setBridge:(nullable RCTBridge *)bridge;
2172
#endif // RCT_REMOVE_LEGACY_ARCH
22-
- (void)setBridgelessBundleURLGetter:(RCTBridgelessBundleURLGetter)getter
23-
andSetter:(RCTBridgelessBundleURLSetter)setter
24-
andDefaultGetter:(RCTBridgelessBundleURLGetter)defaultGetter;
73+
- (void)setBridgelessBundleURLGetter:(nullable RCTBridgelessBundleURLGetter)getter
74+
andSetter:(nullable RCTBridgelessBundleURLSetter)setter
75+
andDefaultGetter:(nullable RCTBridgelessBundleURLGetter)defaultGetter;
2576
- (void)resetBundleURL;
26-
@property NSURL *bundleURL;
77+
@property (nonatomic, nullable) NSURL *bundleURL;
78+
@property (nonatomic, nonnull) RCTBundleConfiguration *bundleConfig;
2779
@end

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

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,93 @@
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 RCTBundleConfiguration
16+
17+
+ (instancetype)defaultConfiguration
18+
{
19+
return [[self alloc] initWithBundleFilePath:nil packagerServerScheme:nil packagerServerHost:nil bundlePath:nil];
20+
}
21+
22+
- (instancetype)initWithBundleFilePath:(NSURL *)bundleFilePath
23+
{
24+
return [self initWithBundleFilePath:bundleFilePath packagerServerScheme:nil packagerServerHost:nil bundlePath:nil];
25+
}
26+
27+
- (instancetype)initWithPackagerServerScheme:(NSString *)packagerServerScheme
28+
packagerServerHost:(NSString *)packagerServerHost
29+
bundlePath:(NSString *)bundlePath
30+
{
31+
return [self initWithBundleFilePath:nil
32+
packagerServerScheme:packagerServerScheme
33+
packagerServerHost:packagerServerHost
34+
bundlePath:bundlePath];
35+
}
36+
37+
- (instancetype)initWithBundleFilePath:(NSURL *)bundleFilePath
38+
packagerServerScheme:(NSString *)packagerServerScheme
39+
packagerServerHost:(NSString *)packagerServerHost
40+
bundlePath:(NSString *)bundlePath
41+
{
42+
if (self = [super init]) {
43+
_bundleFilePath = bundleFilePath;
44+
_packagerServerScheme = packagerServerScheme;
45+
_packagerServerHost = packagerServerHost;
46+
_bundlePath = bundlePath;
47+
_packagerOptionsUpdater = ^NSMutableArray<NSURLQueryItem *> *(NSMutableArray<NSURLQueryItem *> *options)
48+
{
49+
return options;
50+
};
51+
}
52+
53+
return self;
54+
}
55+
56+
- (NSString *)getPackagerServerScheme
57+
{
58+
if (!_packagerServerScheme) {
59+
return [[RCTBundleURLProvider sharedSettings] packagerScheme];
60+
}
61+
62+
return _packagerServerScheme;
63+
}
64+
65+
- (NSString *)getPackagerServerHost
66+
{
67+
if (!_packagerServerHost) {
68+
return [[RCTBundleURLProvider sharedSettings] packagerServerHostPort];
69+
}
70+
71+
return _packagerServerHost;
72+
}
73+
74+
- (NSURL *)getBundleURL:(NSURL * (^)(void))fallbackURLProvider
75+
{
76+
if (_packagerServerScheme && _packagerServerHost) {
77+
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:_bundlePath
78+
packagerServerScheme:_packagerServerScheme
79+
packagerServerHost:_packagerServerHost
80+
packagerOptionsUpdater:_packagerOptionsUpdater];
81+
}
82+
83+
if (_bundleFilePath) {
84+
if (!_bundleFilePath.fileURL) {
85+
RCTLogError(@"Bundle file path must be a file URL");
86+
return nil;
87+
}
88+
89+
return _bundleFilePath;
90+
}
91+
92+
return fallbackURLProvider();
93+
}
94+
95+
@end
1296

1397
@implementation RCTBundleManager {
1498
#ifndef RCT_REMOVE_LEGACY_ARCH
@@ -19,6 +103,20 @@ @implementation RCTBundleManager {
19103
RCTBridgelessBundleURLGetter _bridgelessBundleURLDefaultGetter;
20104
}
21105

106+
- (instancetype)initWithBundleConfig:(RCTBundleConfiguration *)bundleConfig
107+
{
108+
if (self = [super init]) {
109+
self.bundleConfig = bundleConfig ? bundleConfig : [RCTBundleConfiguration defaultConfiguration];
110+
}
111+
112+
return self;
113+
}
114+
115+
- (instancetype)init
116+
{
117+
return [self initWithBundleConfig:[RCTBundleConfiguration defaultConfiguration]];
118+
}
119+
22120
#ifndef RCT_REMOVE_LEGACY_ARCH
23121
- (void)setBridge:(RCTBridge *)bridge
24122
{

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#import <Foundation/Foundation.h>
99

10+
#import "RCTBundleManager.h"
1011
#import "RCTDefines.h"
1112

1213
RCT_EXTERN NSString *_Nonnull const RCTBundleURLProviderUpdatedNotification;
@@ -88,6 +89,16 @@ NS_ASSUME_NONNULL_BEGIN
8889
*/
8990
- (NSURL *__nullable)jsBundleURLForFallbackExtension:(NSString *__nullable)extension;
9091

92+
/**
93+
* Returns the jsBundleURL for a given bundle entrypoint,
94+
* the packager scheme, server host and options updater
95+
* for modifying default packager options.
96+
*/
97+
- (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot
98+
packagerServerScheme:(NSString *)packagerServerScheme
99+
packagerServerHost:(NSString *)packagerServerHost
100+
packagerOptionsUpdater:(RCTPackagerOptionsUpdater)packagerOptionsUpdater;
101+
91102
/**
92103
* Returns the resourceURL for a given bundle entrypoint and
93104
* the fallback offline resource file if the packager is not running.
@@ -97,6 +108,19 @@ NS_ASSUME_NONNULL_BEGIN
97108
resourceExtension:(NSString *)extension
98109
offlineBundle:(NSBundle *)offlineBundle;
99110

111+
/**
112+
* Returns the query items for given options used to create the jsBundleURL.
113+
*/
114+
+ (NSArray<NSURLQueryItem *> *)createJSBundleURLQuery:(NSString *)packagerHost
115+
packagerScheme:(NSString *__nullable)scheme
116+
enableDev:(BOOL)enableDev
117+
enableMinification:(BOOL)enableMinification
118+
inlineSourceMap:(BOOL)inlineSourceMap
119+
modulesOnly:(BOOL)modulesOnly
120+
runModule:(BOOL)runModule
121+
additionalOptions:
122+
(NSDictionary<NSString *, NSString *> *__nullable)additionalOptions;
123+
100124
/**
101125
* The IP address or hostname of the packager.
102126
*/

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

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,54 @@ + (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot
313313
additionalOptions:(NSDictionary<NSString *, NSString *> *__nullable)additionalOptions
314314
{
315315
NSString *path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot];
316+
NSArray<NSURLQueryItem *> *queryItems = [self createJSBundleURLQuery:packagerHost
317+
packagerScheme:scheme
318+
enableDev:enableDev
319+
enableMinification:enableMinification
320+
inlineSourceMap:inlineSourceMap
321+
modulesOnly:modulesOnly
322+
runModule:runModule
323+
additionalOptions:additionalOptions];
324+
325+
return [RCTBundleURLProvider resourceURLForResourcePath:path
326+
packagerHost:packagerHost
327+
scheme:scheme
328+
queryItems:[queryItems copy]];
329+
}
330+
331+
- (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot
332+
packagerServerScheme:(NSString *)packagerServerScheme
333+
packagerServerHost:(NSString *)packagerServerHost
334+
packagerOptionsUpdater:(RCTPackagerOptionsUpdater)packagerOptionsUpdater
335+
{
336+
NSArray<NSURLQueryItem *> *queryItems = [RCTBundleURLProvider createJSBundleURLQuery:packagerServerHost
337+
packagerScheme:packagerServerScheme
338+
enableDev:[self enableDev]
339+
enableMinification:[self enableMinification]
340+
inlineSourceMap:[self inlineSourceMap]
341+
modulesOnly:NO
342+
runModule:YES
343+
additionalOptions:nil];
344+
345+
NSArray<NSURLQueryItem *> *updatedQueryItems = packagerOptionsUpdater((NSMutableArray *)queryItems);
346+
NSString *path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot];
347+
348+
return [RCTBundleURLProvider resourceURLForResourcePath:path
349+
packagerHost:packagerServerHost
350+
scheme:packagerServerScheme
351+
queryItems:updatedQueryItems];
352+
}
353+
354+
+ (NSArray<NSURLQueryItem *> *)createJSBundleURLQuery:(NSString *)packagerHost
355+
packagerScheme:(NSString *__nullable)scheme
356+
enableDev:(BOOL)enableDev
357+
enableMinification:(BOOL)enableMinification
358+
inlineSourceMap:(BOOL)inlineSourceMap
359+
modulesOnly:(BOOL)modulesOnly
360+
runModule:(BOOL)runModule
361+
additionalOptions:
362+
(NSDictionary<NSString *, NSString *> *__nullable)additionalOptions
363+
{
316364
BOOL lazy = enableDev;
317365
NSMutableArray<NSURLQueryItem *> *queryItems = [[NSMutableArray alloc] initWithArray:@[
318366
[[NSURLQueryItem alloc] initWithName:@"platform" value:RCTPlatformName],
@@ -345,10 +393,7 @@ + (NSURL *__nullable)jsBundleURLForBundleRoot:(NSString *)bundleRoot
345393
}
346394
}
347395

348-
return [[self class] resourceURLForResourcePath:path
349-
packagerHost:packagerHost
350-
scheme:scheme
351-
queryItems:[queryItems copy]];
396+
return queryItems;
352397
}
353398

354399
+ (NSURL *)resourceURLForResourcePath:(NSString *)path

0 commit comments

Comments
 (0)