diff --git a/React/Base/RCTBridgeDelegate.h b/React/Base/RCTBridgeDelegate.h index c237b9880..5133d6fb0 100644 --- a/React/Base/RCTBridgeDelegate.h +++ b/React/Base/RCTBridgeDelegate.h @@ -72,4 +72,9 @@ - (void)loadSourceForBridge:(RCTBridge *)bridge withBlock:(RCTSourceLoadBlock)loadCallback; +/** + * Retrieve the list of lazy-native-modules names for the given bridge. + */ +- (NSDictionary *)extraLazyModuleClassesForBridge:(RCTBridge *)bridge; + @end diff --git a/React/CxxBridge/RCTCxxBridge.mm b/React/CxxBridge/RCTCxxBridge.mm index 9124fc185..29d5361bd 100644 --- a/React/CxxBridge/RCTCxxBridge.mm +++ b/React/CxxBridge/RCTCxxBridge.mm @@ -301,6 +301,7 @@ struct RCTInstanceCallback : public InstanceCallback { [self registerExtraModules]; // Initialize all native modules that cannot be loaded lazily (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO]; + [self registerExtraLazyModules]; [_performanceLogger markStopForTag:RCTPLNativeModuleInit]; @@ -600,6 +601,57 @@ struct RCTInstanceCallback : public InstanceCallback { RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @""); } +- (void)registerExtraLazyModules +{ +#if RCT_DEBUG + // This is debug-only and only when Chrome is attached, since it expects all modules to be already + // available on start up. Otherwise, we can let the lazy module discovery to load them on demand. + Class executorClass = [_parentBridge executorClass]; + if (executorClass && [NSStringFromClass(executorClass) isEqualToString:@"RCTWebSocketExecutor"]) { + NSDictionary *moduleClasses = nil; + if ([self.delegate respondsToSelector:@selector(extraLazyModuleClassesForBridge:)]) { + moduleClasses = [self.delegate extraLazyModuleClassesForBridge:_parentBridge]; + } + + if (!moduleClasses) { + return; + } + + // This logic is mostly copied from `registerModulesForClasses:`, but with one difference: + // we must use the names provided by the delegate method here. + for (NSString *moduleName in moduleClasses) { + Class moduleClass = moduleClasses[moduleName]; + if (RCTJSINativeModuleEnabled() && [moduleClass conformsToProtocol:@protocol(RCTJSINativeModule)]) { + continue; + } + + // Check for module name collisions + RCTModuleData *moduleData = _moduleDataByName[moduleName]; + if (moduleData) { + if (moduleData.hasInstance) { + // Existing module was preregistered, so it takes precedence + continue; + } else if ([moduleClass new] == nil) { + // The new module returned nil from init, so use the old module + continue; + } else if ([moduleData.moduleClass new] != nil) { + // Both modules were non-nil, so it's unclear which should take precedence + RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the " + "name '%@', but name was already registered by class %@", + moduleClass, moduleName, moduleData.moduleClass); + } + } + + moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass bridge:self]; + + _moduleDataByName[moduleName] = moduleData; + [_moduleClassesByID addObject:moduleClass]; + [_moduleDataByID addObject:moduleData]; + } + } +#endif +} + - (NSArray *)_initializeModules:(NSArray> *)modules withDispatchGroup:(dispatch_group_t)dispatchGroup lazilyDiscovered:(BOOL)lazilyDiscovered