From f028b15273704df6f630632f7d7baccb9d7d7243 Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Mon, 18 Dec 2023 05:24:14 -0800 Subject: [PATCH] Make the Fabric Interop Layer automatic (#41656) Summary: This change makes all the legacy components to go through the interop layer. It also introduce the `RCTFabricInteropLayerEnabled()` and the `RCTEnableFabricInteropLayer(BOOL)` functions to work as feature flags behind the change to completely disable the Interop layer. ## Changelog [iOS][Changed] - Make the Fabric Interop Layer automatic when the Nw architecture is enabled. Reviewed By: cortinico Differential Revision: D51586461 --- .../Libraries/AppDelegate/RCTAppDelegate.mm | 12 -- .../AppDelegate/RCTLegacyInteropComponents.h | 18 --- .../AppDelegate/RCTLegacyInteropComponents.mm | 17 --- .../AppDelegate/React-RCTAppDelegate.podspec | 15 -- packages/react-native/React/Base/RCTBridge.h | 4 + packages/react-native/React/Base/RCTBridge.mm | 11 ++ ...CTLegacyViewManagerInteropComponentView.mm | 30 ++++ .../Mounting/RCTComponentViewFactory.mm | 21 +-- .../generate-legacy-interop-components.js | 137 ------------------ packages/rn-tester/react-native.config.js | 4 - 10 files changed, 49 insertions(+), 220 deletions(-) delete mode 100644 packages/react-native/Libraries/AppDelegate/RCTLegacyInteropComponents.h delete mode 100644 packages/react-native/Libraries/AppDelegate/RCTLegacyInteropComponents.mm delete mode 100644 packages/react-native/scripts/codegen/generate-legacy-interop-components.js diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm index 13aee73117cb1f..3b29780304d69a 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm @@ -11,7 +11,6 @@ #import #import #import "RCTAppSetupUtils.h" -#import "RCTLegacyInteropComponents.h" #if RN_DISABLE_OSS_PLUGIN_HEADER #import @@ -22,7 +21,6 @@ #import #import #import -#import #import #import #import @@ -98,7 +96,6 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( RCTEnableTurboModuleInteropBridgeProxy(YES); [self createReactHost]; - [self unstable_registerLegacyComponents]; [RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self; RCTFabricSurface *surface = [_reactHost createSurfaceWithModuleName:self.moduleName initialProperties:initProps]; @@ -116,10 +113,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( contextContainer:_contextContainer]; self.bridge.surfacePresenter = self.bridgeAdapter.surfacePresenter; - [self unstable_registerLegacyComponents]; [RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self; } - rootView = [self createRootViewWithBridge:self.bridge moduleName:self.moduleName initProps:initProps]; } self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; @@ -267,13 +262,6 @@ - (Class)getModuleClassFromName:(const char *)name #pragma mark - New Arch Utilities -- (void)unstable_registerLegacyComponents -{ - for (NSString *legacyComponent in [RCTLegacyInteropComponents legacyInteropComponents]) { - [RCTLegacyViewManagerInteropComponentView supportLegacyViewManagerWithName:legacyComponent]; - } -} - - (void)createReactHost { __weak __typeof(self) weakSelf = self; diff --git a/packages/react-native/Libraries/AppDelegate/RCTLegacyInteropComponents.h b/packages/react-native/Libraries/AppDelegate/RCTLegacyInteropComponents.h deleted file mode 100644 index 9d3bccc886dc1b..00000000000000 --- a/packages/react-native/Libraries/AppDelegate/RCTLegacyInteropComponents.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface RCTLegacyInteropComponents : NSObject - -+ (NSArray *)legacyInteropComponents; - -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/react-native/Libraries/AppDelegate/RCTLegacyInteropComponents.mm b/packages/react-native/Libraries/AppDelegate/RCTLegacyInteropComponents.mm deleted file mode 100644 index a330712cae3a7c..00000000000000 --- a/packages/react-native/Libraries/AppDelegate/RCTLegacyInteropComponents.mm +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import "RCTLegacyInteropComponents.h" - -@implementation RCTLegacyInteropComponents - -+ (NSArray *)legacyInteropComponents -{ - return @[ @"RNTMyLegacyNativeView", @"RNTMyNativeView" ]; -} - -@end diff --git a/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec b/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec index fe4a4bbd021528..f0ce494e422bc4 100644 --- a/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec +++ b/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec @@ -94,19 +94,4 @@ Pod::Spec.new do |s| else s.dependency "React-jsc" end - - rel_path_from_pods_root_to_app = Pathname.new(ENV['APP_PATH']).relative_path_from(Pod::Config.instance.installation_root) - rel_path_from_pods_to_app = Pathname.new(ENV['APP_PATH']).relative_path_from(File.join(Pod::Config.instance.installation_root, 'Pods')) - - s.script_phases = { - :name => "Generate Legacy Components Interop", - :script => " -WITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\" -source $WITH_ENVIRONMENT -${NODE_BINARY} ${REACT_NATIVE_PATH}/scripts/codegen/generate-legacy-interop-components.js -p #{rel_path_from_pods_to_app} -o ${REACT_NATIVE_PATH}/Libraries/AppDelegate - ", - :execution_position => :before_compile, - :input_files => ["#{rel_path_from_pods_root_to_app}/react-native.config.js"], - :output_files => ["${REACT_NATIVE_PATH}/Libraries/AppDelegate/RCTLegacyInteropComponents.mm"], - } end diff --git a/packages/react-native/React/Base/RCTBridge.h b/packages/react-native/React/Base/RCTBridge.h index ace401ec56d3fd..db6fd81646474f 100644 --- a/packages/react-native/React/Base/RCTBridge.h +++ b/packages/react-native/React/Base/RCTBridge.h @@ -53,6 +53,10 @@ void RCTEnableTurboModuleInterop(BOOL enabled); BOOL RCTTurboModuleInteropBridgeProxyEnabled(void); void RCTEnableTurboModuleInteropBridgeProxy(BOOL enabled); +// Turn on the fabric interop layer +BOOL RCTFabricInteropLayerEnabled(void); +void RCTEnableFabricInteropLayer(BOOL enabled); + // Turn on TurboModule sync execution of void methods BOOL RCTTurboModuleSyncVoidMethodsEnabled(void); void RCTEnableTurboModuleSyncVoidMethods(BOOL enabled); diff --git a/packages/react-native/React/Base/RCTBridge.mm b/packages/react-native/React/Base/RCTBridge.mm index 263231720cffb6..7551195d31d36a 100644 --- a/packages/react-native/React/Base/RCTBridge.mm +++ b/packages/react-native/React/Base/RCTBridge.mm @@ -118,6 +118,17 @@ void RCTEnableTurboModuleInteropBridgeProxy(BOOL enabled) turboModuleInteropBridgeProxyEnabled = enabled; } +static BOOL fabricInteropLayerEnabled = YES; +BOOL RCTFabricInteropLayerEnabled() +{ + return fabricInteropLayerEnabled; +} + +void RCTEnableFabricInteropLayer(BOOL enabled) +{ + fabricInteropLayerEnabled = enabled; +} + static RCTBridgeProxyLoggingLevel bridgeProxyLoggingLevel = kRCTBridgeProxyLoggingLevelNone; RCTBridgeProxyLoggingLevel RCTTurboModuleInteropBridgeProxyLogLevel(void) { diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm index 86aee3223d7659..83a61713ce81c3 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm @@ -8,6 +8,7 @@ #import "RCTLegacyViewManagerInteropComponentView.h" #import +#import #import #import #import @@ -106,6 +107,12 @@ - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector return supported; } ++ (NSMutableDictionary *)_supportedLegacyViewComponents +{ + static NSMutableDictionary *suppoerted = [NSMutableDictionary new]; + return suppoerted; +} + + (BOOL)isSupported:(NSString *)componentName { // Step 1: check if ViewManager with specified name is supported. @@ -122,6 +129,29 @@ + (BOOL)isSupported:(NSString *)componentName } } + // Step 3: check if the module has been registered + NSArray *registeredModules = RCTGetModuleClasses(); + NSMutableDictionary *supportedLegacyViewComponents = + [RCTLegacyViewManagerInteropComponentView _supportedLegacyViewComponents]; + if (supportedLegacyViewComponents[componentName] != NULL) { + return YES; + } + + for (Class moduleClass in registeredModules) { + id bridgeModule = (id)moduleClass; + NSString *moduleName = [[bridgeModule moduleName] isEqualToString:@""] + ? [NSStringFromClass(moduleClass) stringByReplacingOccurrencesOfString:@"Manager" withString:@""] + : [bridgeModule moduleName]; + + if (supportedLegacyViewComponents[moduleName] == NULL) { + supportedLegacyViewComponents[moduleName] = moduleClass; + } + + if ([moduleName isEqualToString:componentName]) { + return YES; + } + } + return NO; } diff --git a/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm b/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm index 0b15d7112a853c..51545202a3b5b9 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm +++ b/packages/react-native/React/Fabric/Mounting/RCTComponentViewFactory.mm @@ -8,6 +8,7 @@ #import "RCTComponentViewFactory.h" #import +#import #import #import @@ -110,12 +111,11 @@ - (BOOL)registerComponentIfPossible:(const std::string &)name // when the component is registered in both Fabric and in the // interop layer, so they can remove that NSString *componentNameString = RCTNSStringFromString(name); - BOOL isRegisteredInInteropLayer = [RCTLegacyViewManagerInteropComponentView isSupported:componentNameString]; // Fallback 1: Call provider function for component view class. Class klass = RCTComponentViewClassWithName(name.c_str()); if (klass) { - [self registerComponentViewClass:klass andWarnIfNeeded:isRegisteredInInteropLayer]; + [self registerComponentViewClass:klass]; return YES; } @@ -126,13 +126,13 @@ - (BOOL)registerComponentIfPossible:(const std::string &)name NSString *objcName = [NSString stringWithCString:name.c_str() encoding:NSUTF8StringEncoding]; klass = self.thirdPartyFabricComponentsProvider.thirdPartyFabricComponents[objcName]; if (klass) { - [self registerComponentViewClass:klass andWarnIfNeeded:isRegisteredInInteropLayer]; + [self registerComponentViewClass:klass]; return YES; } } // Fallback 3: Try to use Paper Interop. - if (isRegisteredInInteropLayer) { + if (RCTFabricInteropLayerEnabled() && [RCTLegacyViewManagerInteropComponentView isSupported:componentNameString]) { RCTLogNewArchitectureValidation( RCTNotAllowedInBridgeless, self, @@ -221,17 +221,4 @@ - (RCTComponentViewDescriptor)createComponentViewWithComponentHandle:(facebook:: return _providerRegistry.createComponentDescriptorRegistry(parameters); } -#pragma mark - Private - -- (void)registerComponentViewClass:(Class)componentViewClass - andWarnIfNeeded:(BOOL)isRegisteredInInteropLayer -{ - [self registerComponentViewClass:componentViewClass]; - if (isRegisteredInInteropLayer) { - RCTLogWarn( - @"Component with class %@ has been registered in both the New Architecture Renderer and in the Interop Layer.\nPlease remove it from the Interop Layer", - componentViewClass); - } -} - @end diff --git a/packages/react-native/scripts/codegen/generate-legacy-interop-components.js b/packages/react-native/scripts/codegen/generate-legacy-interop-components.js deleted file mode 100644 index 2ef14fef4239e8..00000000000000 --- a/packages/react-native/scripts/codegen/generate-legacy-interop-components.js +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -'use strict'; - -const fs = require('fs'); -const p = require('path'); -const yargs = require('yargs'); - -const CONFIG_FILE_NAME = 'react-native.config.js'; -const PROJECT_FIELD = 'project'; -const IOS_FIELD = 'ios'; -const LEGACY_COMPONENTS_FIELD = 'unstable_reactLegacyComponentNames'; -const OUTPUT_FILE_NAME = 'RCTLegacyInteropComponents.mm'; -const MAX_CHARS_IN_ONE_LINE = 80; - -const argv = yargs - .option('p', { - alias: 'path', - description: 'Path to React Native application', - }) - .option('o', { - alias: 'outputPath', - description: 'Path where generated artifacts will be output to', - }) - .usage('Usage: $0 -p [path to app]') - .demandOption(['p']).argv; - -const appRoot = argv.path; -const outputPath = argv.outputPath; - -function fileBody(components, isOneLine) { - // Generate components array string with proper formatting - const componentsString = components.join(isOneLine ? '' : '\n'); - const componentsArray = isOneLine - ? `@[${componentsString} ];` - : `@[ -${componentsString} - ];`; - // eslint-disable duplicate-license-header - return `/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import "RCTLegacyInteropComponents.h" - -@implementation RCTLegacyInteropComponents - -+ (NSArray *)legacyInteropComponents -{ - return ${componentsArray} -} - -@end -`; - // eslint-enable duplicate-license-header -} - -function extractComponentsNames(reactNativeConfig) { - if (!reactNativeConfig) { - console.log('No reactNativeConfig in the react-native.config.js file'); - return null; - } - - const project = reactNativeConfig[PROJECT_FIELD]; - - if (!project) { - console.log(`No ${PROJECT_FIELD} in the react-native config`); - return null; - } - - const ios = project[IOS_FIELD]; - - if (!ios) { - console.log( - `No ${IOS_FIELD} in the ${PROJECT_FIELD} object of the config file`, - ); - return null; - } - - const componentNames = ios[LEGACY_COMPONENTS_FIELD]; - - if (!componentNames) { - console.log( - `No '${LEGACY_COMPONENTS_FIELD}' field in the ${PROJECT_FIELD}.${IOS_FIELD} object`, - ); - return null; - } - return componentNames; -} - -function generateRCTLegacyInteropComponents() { - const cwd = process.cwd(); - const configFilePath = p.join(cwd, appRoot, CONFIG_FILE_NAME); - console.log( - `Looking for a react-native.config.js file at ${configFilePath}...`, - ); - let reactNativeConfig = null; - try { - reactNativeConfig = require(configFilePath); - } catch (error) { - console.log(`No ${configFilePath}. Skip LegacyInterop generation`); - return; - } - - const componentNames = extractComponentsNames(reactNativeConfig); - if (!componentNames) { - console.log('Skip LegacyInterop generation'); - return; - } - console.log(`Components found: ${componentNames}`); - const isOneLine = componentNames.join().length < MAX_CHARS_IN_ONE_LINE; - let componentsArray = componentNames.map( - (name, index) => `${isOneLine ? ' ' : '\t\t\t'}@"${name}",`, - ); - // Remove the last comma - if (componentsArray.length > 0) { - componentsArray[componentsArray.length - 1] = componentsArray[ - componentsArray.length - 1 - ].slice(0, -1); - } - - const filePath = `${outputPath}/${OUTPUT_FILE_NAME}`; - fs.writeFileSync(filePath, fileBody(componentsArray, isOneLine)); - console.log(`${filePath} updated!`); -} - -generateRCTLegacyInteropComponents(); diff --git a/packages/rn-tester/react-native.config.js b/packages/rn-tester/react-native.config.js index 449669b10b9171..0f9017c59ea8c8 100644 --- a/packages/rn-tester/react-native.config.js +++ b/packages/rn-tester/react-native.config.js @@ -19,10 +19,6 @@ module.exports = { project: { ios: { sourceDir: '.', - unstable_reactLegacyComponentNames: [ - 'RNTMyLegacyNativeView', - 'RNTMyNativeView', - ], }, android: { sourceDir: '../../',