From e4257a4dfbf73953b11bb7d2bec7c5f9ef11154f Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Fri, 3 Mar 2023 14:44:57 -0800 Subject: [PATCH] Attach a script to React-RCTAppDelegate to register Legacy Components in the interop layer (#36335) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36335 This change depends on [this PR](https://github.com/react-native-community/cli/pull/1849) of the CLI that introduces the `unstable_reactLegacyComponent` field in the `react-native.config.js` file. This change introduce a JS script that reads that fields and generated a method in an object to return a list of components to be registered. The `RCTAppDelegate` has been updated to read those components and to automatically register them into the interop layer. Notice that a user can just update the `react-native.config.js` and rebuild the app to integrate these changes, there is no need to reinstall the pods. The idea behind this logic is to let the user know which components they are using with the interop layer, rather than rely on some black magic that could leave them blind to the need of actually migrate their apps. ## Changelog: [iOS][Changed] - Implement mechanism to register legacy components in the iOS Fabric interop layer Reviewed By: cortinico, dmytrorykun Differential Revision: D43665973 fbshipit-source-id: b4e8d71fa1bbed7a6130ee4f83a6221394d5306e --- Libraries/AppDelegate/RCTAppDelegate.mm | 14 +++ .../AppDelegate/RCTLegacyInteropComponents.h | 18 ++++ .../AppDelegate/RCTLegacyInteropComponents.mm | 17 ++++ .../AppDelegate/React-RCTAppDelegate.podspec | 11 +++ package.json | 7 +- .../generate-legacy-interop-components.js | 89 +++++++++++++++++++ scripts/react_native_pods.rb | 3 + 7 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 Libraries/AppDelegate/RCTLegacyInteropComponents.h create mode 100644 Libraries/AppDelegate/RCTLegacyInteropComponents.mm create mode 100644 scripts/codegen/generate-legacy-interop-components.js diff --git a/Libraries/AppDelegate/RCTAppDelegate.mm b/Libraries/AppDelegate/RCTAppDelegate.mm index f1e0dee8303..92c6603af13 100644 --- a/Libraries/AppDelegate/RCTAppDelegate.mm +++ b/Libraries/AppDelegate/RCTAppDelegate.mm @@ -13,12 +13,14 @@ #import #import #import +#import #import #import #import #import #import #import +#import "RCTLegacyInteropComponents.h" static NSString *const kRNConcurrentRoot = @"concurrentRoot"; @@ -60,6 +62,8 @@ static NSString *const kRNConcurrentRoot = @"concurrentRoot"; self.bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:self.bridge contextContainer:_contextContainer]; self.bridge.surfacePresenter = self.bridgeAdapter.surfacePresenter; + + [self unstable_registerLegacyComponents]; #endif NSDictionary *initProps = [self prepareInitialProps]; @@ -70,6 +74,7 @@ static NSString *const kRNConcurrentRoot = @"concurrentRoot"; rootViewController.view = rootView; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; + return YES; } @@ -172,6 +177,15 @@ static NSString *const kRNConcurrentRoot = @"concurrentRoot"; return YES; } +#pragma mark - New Arch Utilities + +- (void)unstable_registerLegacyComponents +{ + for (NSString *legacyComponent in [RCTLegacyInteropComponents legacyInteropComponents]) { + [RCTLegacyViewManagerInteropComponentView supportLegacyViewManagerWithName:legacyComponent]; + } +} + #endif @end diff --git a/Libraries/AppDelegate/RCTLegacyInteropComponents.h b/Libraries/AppDelegate/RCTLegacyInteropComponents.h new file mode 100644 index 00000000000..9d3bccc886d --- /dev/null +++ b/Libraries/AppDelegate/RCTLegacyInteropComponents.h @@ -0,0 +1,18 @@ +/* + * 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/Libraries/AppDelegate/RCTLegacyInteropComponents.mm b/Libraries/AppDelegate/RCTLegacyInteropComponents.mm new file mode 100644 index 00000000000..d1d68376b08 --- /dev/null +++ b/Libraries/AppDelegate/RCTLegacyInteropComponents.mm @@ -0,0 +1,17 @@ +/* + * 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 @[]; +} + +@end diff --git a/Libraries/AppDelegate/React-RCTAppDelegate.podspec b/Libraries/AppDelegate/React-RCTAppDelegate.podspec index ccc57afe068..0f43d561921 100644 --- a/Libraries/AppDelegate/React-RCTAppDelegate.podspec +++ b/Libraries/AppDelegate/React-RCTAppDelegate.podspec @@ -77,5 +77,16 @@ Pod::Spec.new do |s| if is_new_arch_enabled s.dependency "React-RCTFabric" s.dependency "React-graphics" + + s.script_phases = { + :name => "Generate Legacy Components Interop", + :script => " +. ${PODS_ROOT}/../.xcode.env +${NODE_BINARY} ${REACT_NATIVE_PATH}/scripts/codegen/generate-legacy-interop-components.js -p #{ENV['APP_PATH']} -o ${REACT_NATIVE_PATH}/Libraries/AppDelegate + ", + :execution_position => :before_compile, + :input_files => ["#{ENV['APP_PATH']}/react-native.config.js"], + :output_files => ["${REACT_NATIVE_PATH}/Libraries/AppDelegate/RCTLegacyInteropComponents.mm"], + } end end diff --git a/package.json b/package.json index 048e5be439b..39fae1fedf4 100644 --- a/package.json +++ b/package.json @@ -42,9 +42,9 @@ "scripts/generate-codegen-artifacts.js", "scripts/generate-provider-cli.js", "scripts/generate-specs-cli.js", - "scripts/codegen/codegen-utils.js", - "scripts/codegen/generate-artifacts-executor.js", - "scripts/codegen/generate-specs-cli-executor.js", + "scripts/codegen", + "!scripts/codegen/__tests__", + "!scripts/codegen/__test_fixtures__", "scripts/hermes/hermes-utils.js", "scripts/hermes/prepare-hermes-for-build.js", "scripts/ios-configure-glog.sh", @@ -59,6 +59,7 @@ "scripts/react_native_pods_utils/script_phases.sh", "scripts/react_native_pods.rb", "scripts/cocoapods", + "!scripts/cocoapods/__tests__", "scripts/react-native-xcode.sh", "sdks/.hermesversion", "sdks/hermes-engine", diff --git a/scripts/codegen/generate-legacy-interop-components.js b/scripts/codegen/generate-legacy-interop-components.js new file mode 100644 index 00000000000..ebe3ce1fb3e --- /dev/null +++ b/scripts/codegen/generate-legacy-interop-components.js @@ -0,0 +1,89 @@ +/** + * 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 yargs = require('yargs'); +const fs = require('fs'); + +const CONFIG_FILE_NAME = 'react-native.config.js'; +const LEGACY_COMPONENTS_FIELD = 'unstable_reactLegacyComponent'; +const OUTPUT_FILE_NAME = 'RCTLegacyInteropComponents.mm'; + +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) { + // 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 @[ +${components} + ]; +} + +@end +`; + // eslint-enable duplicate-license-header +} + +function generateRCTLegacyInteropComponents() { + const configFilePath = `${appRoot}/${CONFIG_FILE_NAME}`; + let reactNativeConfig = null; + try { + reactNativeConfig = require(configFilePath); + } catch (error) { + console.log(`No ${configFilePath}. Skip LegacyInterop generation`); + } + + if (reactNativeConfig && reactNativeConfig[LEGACY_COMPONENTS_FIELD]) { + let componentsArray = reactNativeConfig[LEGACY_COMPONENTS_FIELD].map( + name => `\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.join('\n'))); + } else { + console.log( + `No '${LEGACY_COMPONENTS_FIELD}' field. Skip LegacyInterop generation`, + ); + } +} + +generateRCTLegacyInteropComponents(); diff --git a/scripts/react_native_pods.rb b/scripts/react_native_pods.rb index aec2cdb24bb..b3894cc85f6 100644 --- a/scripts/react_native_pods.rb +++ b/scripts/react_native_pods.rb @@ -64,6 +64,9 @@ def use_react_native! ( ios_folder: 'ios' ) + # Set the app_path as env variable so the podspecs can access it. + ENV['APP_PATH'] = app_path + # Current target definition is provided by Cocoapods and it refers to the target # that has invoked the `use_react_native!` function. ReactNativePodsUtils.detect_use_frameworks(current_target_definition)