diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm index b58482512a87ad..6ce95474a26d2b 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm @@ -255,7 +255,41 @@ - (RCTRootViewFactory *)createRCTRootViewFactory { return [weakSelf createBridgeWithDelegate:delegate launchOptions:launchOptions]; }; + + configuration.sourceURLForBridge = ^NSURL * _Nullable(RCTBridge * _Nonnull bridge) { + return [weakSelf sourceURLForBridge:bridge]; + }; + if ([self respondsToSelector:@selector(extraModulesForBridge:)]) { + configuration.extraModulesForBridge = ^NSArray> * _Nonnull(RCTBridge * _Nonnull bridge) { + return [weakSelf extraModulesForBridge:bridge]; + }; + } + + if ([self respondsToSelector:@selector(extraLazyModuleClassesForBridge:)]) { + configuration.extraLazyModuleClassesForBridge = ^NSDictionary * _Nonnull(RCTBridge * _Nonnull bridge) { + return [weakSelf extraLazyModuleClassesForBridge:bridge]; + }; + } + + if ([self respondsToSelector:@selector(bridge:didNotFindModule:)]) { + configuration.bridgeDidNotFindModule = ^BOOL(RCTBridge * _Nonnull bridge, NSString * _Nonnull moduleName) { + return [weakSelf bridge:bridge didNotFindModule:moduleName]; + }; + } + + if ([self respondsToSelector:@selector(loadSourceForBridge:withBlock:)]) { + configuration.loadSourceForBridgeBlock = ^void(RCTBridge * _Nonnull bridge, RCTSourceLoadBlock _Nonnull loadCallback) { + [weakSelf loadSourceForBridge:bridge withBlock:loadCallback]; + }; + } + + if ([self respondsToSelector:@selector(loadSourceForBridge:onProgress:onComplete:)]) { + configuration.loadSourceForBridgeProgressBlock = ^(RCTBridge * _Nonnull bridge, RCTSourceLoadProgressBlock _Nonnull onProgress, RCTSourceLoadBlock _Nonnull loadCallback) { + [weakSelf loadSourceForBridge:bridge onProgress:onProgress onComplete:loadCallback]; + }; + } + return [[RCTRootViewFactory alloc] initWithConfiguration:configuration andTurboModuleManagerDelegate:self]; } diff --git a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h index 5047802ebc5eda..2dda171c9df7e9 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h +++ b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.h @@ -23,6 +23,13 @@ typedef UIView *_Nonnull ( ^RCTCreateRootViewWithBridgeBlock)(RCTBridge *bridge, NSString *moduleName, NSDictionary *initProps); typedef RCTBridge *_Nonnull ( ^RCTCreateBridgeWithDelegateBlock)(id delegate, NSDictionary *launchOptions); +typedef NSURL *_Nullable (^RCTSourceURLForBridgeBlock)(RCTBridge *bridge); +typedef NSArray> *_Nonnull (^RCTExtraModulesForBridgeBlock)(RCTBridge *bridge); +typedef NSDictionary *_Nonnull (^RCTExtraLazyModuleClassesForBridge)(RCTBridge *bridge); +typedef BOOL (^RCTBridgeDidNotFindModuleBlock)(RCTBridge *bridge, NSString *moduleName); +typedef void (^RCTLoadSourceForBridgeBlock)(RCTBridge *bridge, RCTSourceLoadBlock loadCallback); +typedef void (^RCTLoadSourceForBridgeProgressBlock) + (RCTBridge *bridge, RCTSourceLoadProgressBlock onProgress, RCTSourceLoadBlock loadCallback); #pragma mark - RCTRootViewFactory Configuration @interface RCTRootViewFactoryConfiguration : NSObject @@ -81,6 +88,57 @@ typedef RCTBridge *_Nonnull ( */ @property (nonatomic, nullable) RCTCreateBridgeWithDelegateBlock createBridgeWithDelegate; +/** + * Block that returns the location of the JavaScript source file. When running from the packager + * this should be an absolute URL, e.g. `http://localhost:8081/index.ios.bundle`. + * When running from a locally bundled JS file, this should be a `file://` url + * pointing to a path inside the app resources, e.g. `file://.../main.jsbundle`. + */ +@property (nonatomic, nullable) RCTSourceURLForBridgeBlock sourceURLForBridge; + +/** + * The bridge initializes any registered RCTBridgeModules automatically, however + * if you wish to instantiate your own module instances, you can return them + * from this block. + * + * Note: You should always return a new instance for each call, rather than + * returning the same instance each time the bridge is reloaded. Module instances + * should not be shared between bridges, and this may cause unexpected behavior. + * + * It is also possible to override standard modules with your own implementations + * by returning a class with the same `moduleName` from this method, but this is + * not recommended in most cases - if the module methods and behavior do not + * match exactly, it may lead to bugs or crashes. + */ +@property (nonatomic, nullable) RCTExtraModulesForBridgeBlock extraModulesForBridge; + +/** + * Retrieve the list of lazy-native-modules names for the given bridge. + */ +@property (nonatomic, nullable) RCTExtraLazyModuleClassesForBridge extraLazyModuleClassesForBridge; + +/** + * The bridge will call this block when a module been called from JS + * cannot be found among registered modules. + * It should return YES if the module with name 'moduleName' was registered + * in the implementation, and the system must attempt to look for it again among registered. + * If the module was not registered, return NO to prevent further searches. + */ +@property (nonatomic, nullable) RCTBridgeDidNotFindModuleBlock bridgeDidNotFindModule; + +/** + * The bridge will automatically attempt to load the JS source code from the + * location specified by the `sourceURLForBridge:` method, however, if you want + * to handle loading the JS yourself, you can do so by implementing this method. + */ +@property (nonatomic, nullable) RCTLoadSourceForBridgeProgressBlock loadSourceForBridgeProgressBlock; + +/** + * Similar to loadSourceForBridge:onProgress:onComplete: but without progress + * reporting. + */ +@property (nonatomic, nullable) RCTLoadSourceForBridgeBlock loadSourceForBridgeBlock; + @end #pragma mark - RCTRootViewFactory diff --git a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm index c71ea624423433..54061290bd3fc1 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm @@ -241,11 +241,52 @@ - (void)didCreateContextContainer:(std::shared_ptrinsert("ReactNativeConfig", _reactNativeConfig); } +- (NSArray> *)extraModulesForBridge:(RCTBridge *)bridge { + if (_configuration.extraModulesForBridge != nil) { + return _configuration.extraModulesForBridge(bridge); + } + return nil; +} + +- (NSDictionary *)extraLazyModuleClassesForBridge:(RCTBridge *)bridge { + if (_configuration.extraLazyModuleClassesForBridge != nil) { + return _configuration.extraLazyModuleClassesForBridge(bridge); + } + return nil; +} + - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { + if (_configuration.sourceURLForBridge != nil) { + return _configuration.sourceURLForBridge(bridge); + } return [self bundleURL]; } +- (BOOL)bridge:(RCTBridge *)bridge didNotFindModule:(NSString *)moduleName +{ + if (_configuration.bridgeDidNotFindModule != nil) { + return _configuration.bridgeDidNotFindModule(bridge, moduleName); + } + return NO; +} + +- (void)loadSourceForBridge:(RCTBridge *)bridge withBlock:(RCTSourceLoadBlock)loadCallback +{ + if (_configuration.loadSourceForBridgeBlock != nil) { + _configuration.loadSourceForBridgeBlock(bridge, loadCallback); + } +} + +- (void)loadSourceForBridge:(RCTBridge *)bridge + onProgress:(RCTSourceLoadProgressBlock)onProgress + onComplete:(RCTSourceLoadBlock)loadCallback +{ + if (_configuration.loadSourceForBridgeProgressBlock != nil) { + _configuration.loadSourceForBridgeProgressBlock(bridge, onProgress, loadCallback); + } +} + - (NSURL *)bundleURL { return self->_configuration.bundleURL;