Skip to content

Commit

Permalink
Attach a script to React-RCTAppDelegate to register Legacy Components…
Browse files Browse the repository at this point in the history
… in the interop layer (#36335)

Summary:
Pull Request resolved: #36335

This change depends on [this PR](react-native-community/cli#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
  • Loading branch information
cipolleschi authored and facebook-github-bot committed Mar 3, 2023
1 parent 5746533 commit e4257a4
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 3 deletions.
14 changes: 14 additions & 0 deletions Libraries/AppDelegate/RCTAppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
#import <React/CoreModulesPlugins.h>
#import <React/RCTCxxBridgeDelegate.h>
#import <React/RCTFabricSurfaceHostingProxyRootView.h>
#import <React/RCTLegacyViewManagerInteropComponentView.h>
#import <React/RCTSurfacePresenter.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <ReactCommon/RCTTurboModuleManager.h>
#import <react/config/ReactNativeConfig.h>
#import <react/renderer/runtimescheduler/RuntimeScheduler.h>
#import <react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.h>
#import "RCTLegacyInteropComponents.h"

static NSString *const kRNConcurrentRoot = @"concurrentRoot";

Expand Down Expand Up @@ -60,6 +62,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
self.bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:self.bridge
contextContainer:_contextContainer];
self.bridge.surfacePresenter = self.bridgeAdapter.surfacePresenter;

[self unstable_registerLegacyComponents];
#endif

NSDictionary *initProps = [self prepareInitialProps];
Expand All @@ -70,6 +74,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];

return YES;
}

Expand Down Expand Up @@ -172,6 +177,15 @@ - (BOOL)fabricEnabled
return YES;
}

#pragma mark - New Arch Utilities

- (void)unstable_registerLegacyComponents
{
for (NSString *legacyComponent in [RCTLegacyInteropComponents legacyInteropComponents]) {
[RCTLegacyViewManagerInteropComponentView supportLegacyViewManagerWithName:legacyComponent];
}
}

#endif

@end
18 changes: 18 additions & 0 deletions Libraries/AppDelegate/RCTLegacyInteropComponents.h
Original file line number Diff line number Diff line change
@@ -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 <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface RCTLegacyInteropComponents : NSObject

+ (NSArray<NSString *> *)legacyInteropComponents;

@end

NS_ASSUME_NONNULL_END
17 changes: 17 additions & 0 deletions Libraries/AppDelegate/RCTLegacyInteropComponents.mm
Original file line number Diff line number Diff line change
@@ -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<NSString *> *)legacyInteropComponents
{
return @[];
}

@end
11 changes: 11 additions & 0 deletions Libraries/AppDelegate/React-RCTAppDelegate.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
89 changes: 89 additions & 0 deletions scripts/codegen/generate-legacy-interop-components.js
Original file line number Diff line number Diff line change
@@ -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<NSString *> *)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();
3 changes: 3 additions & 0 deletions scripts/react_native_pods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit e4257a4

Please sign in to comment.