diff --git a/Libraries/ReactNative/UIManager.js b/Libraries/ReactNative/UIManager.js index 1887bedf8..f921a0550 100644 --- a/Libraries/ReactNative/UIManager.js +++ b/Libraries/ReactNative/UIManager.js @@ -37,6 +37,7 @@ UIManager.takeSnapshot = function() { 'Use ReactNative.takeSnapshot instead.', ); }; +const triedLoadingConfig = new Set(); UIManager.getViewManagerConfig = function(viewManagerName: string) { if ( viewManagerConfigs[viewManagerName] === undefined && @@ -51,9 +52,59 @@ UIManager.getViewManagerConfig = function(viewManagerName: string) { } } + const config = viewManagerConfigs[viewManagerName]; + if (config) { + return config; + } + + if (UIManager.lazilyLoadView && !triedLoadingConfig.has(viewManagerName)) { + const result = UIManager.lazilyLoadView(viewManagerName); + triedLoadingConfig.add(viewManagerName); + if (result.viewConfig) { + UIManager[viewManagerName] = result.viewConfig; + lazifyViewManagerConfig(viewManagerName); + } + } + return viewManagerConfigs[viewManagerName]; }; +function lazifyViewManagerConfig(viewName) { + const viewConfig = UIManager[viewName]; + if (viewConfig.Manager) { + viewManagerConfigs[viewName] = viewConfig; + defineLazyObjectProperty(viewConfig, 'Constants', { + get: () => { + const viewManager = NativeModules[viewConfig.Manager]; + const constants = {}; + viewManager && + Object.keys(viewManager).forEach(key => { + const value = viewManager[key]; + if (typeof value !== 'function') { + constants[key] = value; + } + }); + return constants; + }, + }); + defineLazyObjectProperty(viewConfig, 'Commands', { + get: () => { + const viewManager = NativeModules[viewConfig.Manager]; + const commands = {}; + let index = 0; + viewManager && + Object.keys(viewManager).forEach(key => { + const value = viewManager[key]; + if (typeof value === 'function') { + commands[key] = index++; + } + }); + return commands; + }, + }); + } +} + /** * Copies the ViewManager constants and commands into UIManager. This is * only needed for iOS, which puts the constants in the ViewManager @@ -61,39 +112,7 @@ UIManager.getViewManagerConfig = function(viewManagerName: string) { */ if (Platform.OS === 'ios') { Object.keys(UIManager).forEach(viewName => { - const viewConfig = UIManager[viewName]; - if (viewConfig.Manager) { - viewManagerConfigs[viewName] = viewConfig; - defineLazyObjectProperty(viewConfig, 'Constants', { - get: () => { - const viewManager = NativeModules[viewConfig.Manager]; - const constants = {}; - viewManager && - Object.keys(viewManager).forEach(key => { - const value = viewManager[key]; - if (typeof value !== 'function') { - constants[key] = value; - } - }); - return constants; - }, - }); - defineLazyObjectProperty(viewConfig, 'Commands', { - get: () => { - const viewManager = NativeModules[viewConfig.Manager]; - const commands = {}; - let index = 0; - viewManager && - Object.keys(viewManager).forEach(key => { - const value = viewManager[key]; - if (typeof value === 'function') { - commands[key] = index++; - } - }); - return commands; - }, - }); - } + lazifyViewManagerConfig(viewName); }); } else if (UIManager.ViewManagerNames) { // We want to add all the view managers to the UIManager. diff --git a/Libraries/ReactNative/UIManagerProperties.js b/Libraries/ReactNative/UIManagerProperties.js index a579b6047..d0584554c 100644 --- a/Libraries/ReactNative/UIManagerProperties.js +++ b/Libraries/ReactNative/UIManagerProperties.js @@ -64,4 +64,5 @@ module.exports = [ 'focus', 'genericBubblingEventTypes', 'genericDirectEventTypes', + 'lazilyLoadView', ]; diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index efc9a9447..9429f135d 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -68,7 +68,7 @@ NSString *const RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotif NSHashTable *_shadowViewsWithUpdatedChildren; // UIManager queue only. // Keyed by viewName - NSDictionary *_componentDataByName; + NSMutableDictionary *_componentDataByName; } @synthesize bridge = _bridge; @@ -148,18 +148,16 @@ RCT_EXPORT_MODULE() _observerCoordinator = [RCTUIManagerObserverCoordinator new]; - // Get view managers from bridge - NSMutableDictionary *componentDataByName = [NSMutableDictionary new]; + // Get view managers from bridge= + _componentDataByName = [NSMutableDictionary new]; for (Class moduleClass in _bridge.moduleClasses) { if ([moduleClass isSubclassOfClass:[RCTViewManager class]]) { RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:moduleClass bridge:_bridge]; - componentDataByName[componentData.name] = componentData; + _componentDataByName[componentData.name] = componentData; } } - _componentDataByName = [componentDataByName copy]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveNewContentSizeMultiplier) name:RCTAccessibilityManagerDidUpdateMultiplierNotification @@ -1485,6 +1483,62 @@ RCT_EXPORT_METHOD(clearJSResponder) }]; } +static NSMutableDictionary *moduleConstantsForComponent( + NSMutableDictionary *directEvents, + NSMutableDictionary *bubblingEvents, + RCTComponentData *componentData) { + NSMutableDictionary *moduleConstants = [NSMutableDictionary new]; + + // Register which event-types this view dispatches. + // React needs this for the event plugin. + NSMutableDictionary *bubblingEventTypes = [NSMutableDictionary new]; + NSMutableDictionary *directEventTypes = [NSMutableDictionary new]; + + // Add manager class + moduleConstants[@"Manager"] = RCTBridgeModuleNameForClass(componentData.managerClass); + + // Add native props + NSDictionary *viewConfig = [componentData viewConfig]; + moduleConstants[@"NativeProps"] = viewConfig[@"propTypes"]; + moduleConstants[@"baseModuleName"] = viewConfig[@"baseModuleName"]; + moduleConstants[@"bubblingEventTypes"] = bubblingEventTypes; + moduleConstants[@"directEventTypes"] = directEventTypes; + + // Add direct events + for (NSString *eventName in viewConfig[@"directEvents"]) { + if (!directEvents[eventName]) { + directEvents[eventName] = @{ + @"registrationName": [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"], + }; + } + directEventTypes[eventName] = directEvents[eventName]; + if (RCT_DEBUG && bubblingEvents[eventName]) { + RCTLogError(@"Component '%@' re-registered bubbling event '%@' as a " + "direct event", componentData.name, eventName); + } + } + + // Add bubbling events + for (NSString *eventName in viewConfig[@"bubblingEvents"]) { + if (!bubblingEvents[eventName]) { + NSString *bubbleName = [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"]; + bubblingEvents[eventName] = @{ + @"phasedRegistrationNames": @{ + @"bubbled": bubbleName, + @"captured": [bubbleName stringByAppendingString:@"Capture"], + } + }; + } + bubblingEventTypes[eventName] = bubblingEvents[eventName]; + if (RCT_DEBUG && directEvents[eventName]) { + RCTLogError(@"Component '%@' re-registered direct event '%@' as a " + "bubbling event", componentData.name, eventName); + } + } + + return moduleConstants; +} + - (NSDictionary *)constantsToExport { NSMutableDictionary *constants = [NSMutableDictionary new]; @@ -1492,62 +1546,43 @@ RCT_EXPORT_METHOD(clearJSResponder) NSMutableDictionary *bubblingEvents = [NSMutableDictionary new]; [_componentDataByName enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) { - NSMutableDictionary *moduleConstants = [NSMutableDictionary new]; - - // Register which event-types this view dispatches. - // React needs this for the event plugin. - NSMutableDictionary *bubblingEventTypes = [NSMutableDictionary new]; - NSMutableDictionary *directEventTypes = [NSMutableDictionary new]; - - // Add manager class - moduleConstants[@"Manager"] = RCTBridgeModuleNameForClass(componentData.managerClass); - - // Add native props - NSDictionary *viewConfig = [componentData viewConfig]; - moduleConstants[@"NativeProps"] = viewConfig[@"propTypes"]; - moduleConstants[@"baseModuleName"] = viewConfig[@"baseModuleName"]; - moduleConstants[@"bubblingEventTypes"] = bubblingEventTypes; - moduleConstants[@"directEventTypes"] = directEventTypes; - - // Add direct events - for (NSString *eventName in viewConfig[@"directEvents"]) { - if (!directEvents[eventName]) { - directEvents[eventName] = @{ - @"registrationName": [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"], - }; - } - directEventTypes[eventName] = directEvents[eventName]; - if (RCT_DEBUG && bubblingEvents[eventName]) { - RCTLogError(@"Component '%@' re-registered bubbling event '%@' as a " - "direct event", componentData.name, eventName); - } - } - - // Add bubbling events - for (NSString *eventName in viewConfig[@"bubblingEvents"]) { - if (!bubblingEvents[eventName]) { - NSString *bubbleName = [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"]; - bubblingEvents[eventName] = @{ - @"phasedRegistrationNames": @{ - @"bubbled": bubbleName, - @"captured": [bubbleName stringByAppendingString:@"Capture"], - } - }; - } - bubblingEventTypes[eventName] = bubblingEvents[eventName]; - if (RCT_DEBUG && directEvents[eventName]) { - RCTLogError(@"Component '%@' re-registered direct event '%@' as a " - "bubbling event", componentData.name, eventName); - } - } - - RCTAssert(!constants[name], @"UIManager already has constants for %@", componentData.name); - constants[name] = moduleConstants; + RCTAssert(!constants[name], @"UIManager already has constants for %@", componentData.name); + NSMutableDictionary *moduleConstants = moduleConstantsForComponent(directEvents, bubblingEvents, componentData); + constants[name] = moduleConstants; }]; return constants; } +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(lazilyLoadView:(NSString *)name) +{ + if (_componentDataByName[name]) { + return @{}; + } + + id delegate = self.bridge.delegate; + if (![delegate respondsToSelector:@selector(bridge:didNotFindModule:)]) { + return @{}; + } + + NSString *moduleName = [name stringByAppendingString:@"Manager"]; + BOOL result = [delegate bridge:self.bridge didNotFindModule:moduleName]; + if (!result) { + return @{}; + } + + id module = [self.bridge moduleForName:moduleName]; + RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:[module class] bridge:self.bridge]; + _componentDataByName[componentData.name] = componentData; + NSMutableDictionary *directEvents = [NSMutableDictionary new]; + NSMutableDictionary *bubblingEvents = [NSMutableDictionary new]; + NSMutableDictionary *moduleConstants = moduleConstantsForComponent(directEvents, bubblingEvents, componentData); + return + @{ + @"viewConfig": moduleConstants, + }; +} + RCT_EXPORT_METHOD(configureNextLayoutAnimation:(NSDictionary *)config withCallback:(RCTResponseSenderBlock)callback errorCallback:(__unused RCTResponseSenderBlock)errorCallback)