From 6d1fbc628d1b4d62c04728eabb4ad801a23150f2 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 16 Oct 2023 10:46:30 -0700 Subject: [PATCH] Simplify new app template for bridgeless (#40929) Summary: This diff reduces the footprint that bridgeless is imposing on the new app template. Specifically: - I've created a `.toReactHost` method that converts a DefaultReactNativeHost to a DefaultReactHost - I've updated RN Tester to use the same setup as the New App template which reduces code duplication. I also had to remove a couple of `UnstableReactNativeAPI` as those were bleeding in the new app template. I don't think we should ask users to opt-in in `UnstableReactNativeAPI` in the New App template itself as this means that all the apps will get this opt-in. Instead we should keep it only for specific APIs that we want the users to opt into. Changelog: [Internal] [Changed] - Simplify new app template for bridgeless Reviewed By: cipolleschi, luluwu2032 Differential Revision: D50227693 --- .../main/java/com/facebook/react/ReactHost.kt | 2 - .../react/defaults/DefaultReactHost.kt | 64 +++++++- .../react/defaults/DefaultReactNativeHost.kt | 16 ++ .../java/com/helloworld/MainApplication.kt | 11 +- .../react/uiapp/RNTesterApplication.kt | 65 +++------ .../react/uiapp/RNTesterReactHostDelegate.kt | 138 ------------------ 6 files changed, 97 insertions(+), 199 deletions(-) delete mode 100644 packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterReactHostDelegate.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactHost.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactHost.kt index 9465464c1c8a7f..a1c3ab897cdfb5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactHost.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactHost.kt @@ -13,7 +13,6 @@ import android.os.Bundle import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.queue.ReactQueueConfiguration import com.facebook.react.common.LifecycleState -import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.devsupport.interfaces.DevSupportManager import com.facebook.react.interfaces.TaskInterface import com.facebook.react.interfaces.fabric.ReactSurface @@ -26,7 +25,6 @@ import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler * * The implementation of this interface should be Thread Safe */ -@UnstableReactNativeAPI interface ReactHost { /** The current [LifecycleState] for React Host */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHost.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHost.kt index d727a0bc6e61aa..d481e33c0bd1d9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHost.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHost.kt @@ -8,7 +8,9 @@ package com.facebook.react.defaults import android.content.Context +import com.facebook.react.JSEngineResolutionAlgorithm import com.facebook.react.ReactHost +import com.facebook.react.ReactNativeHost import com.facebook.react.ReactPackage import com.facebook.react.bridge.JSBundleLoader import com.facebook.react.common.annotations.UnstableReactNativeAPI @@ -18,10 +20,29 @@ import com.facebook.react.runtime.JSCInstance import com.facebook.react.runtime.ReactHostImpl import com.facebook.react.runtime.hermes.HermesInstance -@UnstableReactNativeAPI +/** + * A utility class that allows you to simplify the setup of a [ReactHost] for new apps in Open + * Source. + * + * [ReactHost] is an interface responsible of handling the lifecycle of a React Native app when + * running in bridgeless mode. + */ object DefaultReactHost { private var reactHost: ReactHost? = null + /** + * Util function to create a default [ReactHost] to be used in your application. This method is + * used by the New App template. + * + * @param context the Android [Context] to use for creating the [ReactHost] + * @param packageList the list of [ReactPackage]s to use for creating the [ReactHost] + * @param jsMainModulePath the path to your app's main module on Metro. Usually `index` or + * `index.` + * @param jsBundleAssetPath the path to the JS bundle relative to the assets directory. Will be + * composed in a `asset://...` URL + * @param isHermesEnabled whether to use Hermes as the JS engine, default to true. + */ + @OptIn(UnstableReactNativeAPI::class) @JvmStatic fun getDefaultReactHost( context: Context, @@ -47,13 +68,42 @@ object DefaultReactHost { // TODO: T164788699 find alternative of accessing ReactHostImpl for initialising reactHost reactHost = ReactHostImpl( - context, - defaultReactHostDelegate, - componentFactory, - true, - reactJsExceptionHandler, - true) + context, + defaultReactHostDelegate, + componentFactory, + true, + reactJsExceptionHandler, + true) + .apply { + jsEngineResolutionAlgorithm = + if (isHermesEnabled) { + JSEngineResolutionAlgorithm.HERMES + } else { + JSEngineResolutionAlgorithm.JSC + } + } } return reactHost as ReactHost } + + /** + * Util function to create a default [ReactHost] to be used in your application. This method is + * used by the New App template. + * + * This method takes in input a [ReactNativeHost] (bridge-mode) and uses its configuration to + * create an equivalent [ReactHost] (bridgeless-mode). + * + * @param context the Android [Context] to use for creating the [ReactHost] + * @param reactNativeHost the [ReactNativeHost] to use for creating the [ReactHost] + */ + @JvmStatic + fun getDefaultReactHost( + context: Context, + reactNativeHost: ReactNativeHost, + ): ReactHost { + require(reactNativeHost is DefaultReactNativeHost) { + "You can call getDefaultReactHost only with instances of DefaultReactNativeHost" + } + return reactNativeHost.toReactHost(context) + } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactNativeHost.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactNativeHost.kt index 23c0bada20a220..77cc1a7d1de6b9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactNativeHost.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactNativeHost.kt @@ -8,7 +8,9 @@ package com.facebook.react.defaults import android.app.Application +import android.content.Context import com.facebook.react.JSEngineResolutionAlgorithm +import com.facebook.react.ReactHost import com.facebook.react.ReactNativeHost import com.facebook.react.ReactPackageTurboModuleManagerDelegate import com.facebook.react.bridge.JSIModulePackage @@ -68,4 +70,18 @@ protected constructor( */ protected open val isHermesEnabled: Boolean? get() = null + + /** + * Converts this [ReactNativeHost] (bridge-mode) to a [ReactHost] (bridgeless-mode). + * + * @param context the Android [Context] to use for creating the [ReactHost] + */ + fun toReactHost(context: Context): ReactHost = + DefaultReactHost.getDefaultReactHost( + context, + packages, + jsMainModuleName, + bundleAssetName ?: "index", + isHermesEnabled ?: true, + ) } diff --git a/packages/react-native/template/android/app/src/main/java/com/helloworld/MainApplication.kt b/packages/react-native/template/android/app/src/main/java/com/helloworld/MainApplication.kt index 8f4e5ccc8c01f1..5c868b449d9087 100644 --- a/packages/react-native/template/android/app/src/main/java/com/helloworld/MainApplication.kt +++ b/packages/react-native/template/android/app/src/main/java/com/helloworld/MainApplication.kt @@ -6,13 +6,11 @@ import com.facebook.react.ReactApplication import com.facebook.react.ReactHost import com.facebook.react.ReactNativeHost import com.facebook.react.ReactPackage -import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load -import com.facebook.react.defaults.DefaultReactHost +import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost import com.facebook.react.defaults.DefaultReactNativeHost import com.facebook.soloader.SoLoader -@UnstableReactNativeAPI class MainApplication : Application(), ReactApplication { override val reactNativeHost: ReactNativeHost = @@ -32,12 +30,7 @@ class MainApplication : Application(), ReactApplication { } override val reactHost: ReactHost - get() = - DefaultReactHost.getDefaultReactHost( - context = this, - packageList = PackageList(this).packages, - jsMainModulePath = "index", - isHermesEnabled = BuildConfig.IS_HERMES_ENABLED) + get() = getDefaultReactHost(this.applicationContext, reactNativeHost) override fun onCreate() { super.onCreate() diff --git a/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterApplication.kt b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterApplication.kt index 9a394adca9eb1e..ab124afc7e5750 100644 --- a/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterApplication.kt +++ b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterApplication.kt @@ -10,29 +10,25 @@ package com.facebook.react.uiapp import android.app.Application import com.facebook.fbreact.specs.SampleLegacyModule import com.facebook.fbreact.specs.SampleTurboModule -import com.facebook.react.JSEngineResolutionAlgorithm import com.facebook.react.ReactApplication import com.facebook.react.ReactHost import com.facebook.react.ReactNativeHost import com.facebook.react.ReactPackage import com.facebook.react.TurboReactPackage +import com.facebook.react.ViewManagerOnDemandReactPackage import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.common.assets.ReactFontManager -import com.facebook.react.common.mapbuffer.ReadableMapBuffer import com.facebook.react.config.ReactFeatureFlags -import com.facebook.react.defaults.DefaultComponentsRegistry.Companion.register import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load +import com.facebook.react.defaults.DefaultReactHost import com.facebook.react.defaults.DefaultReactNativeHost -import com.facebook.react.fabric.ComponentFactory -import com.facebook.react.interfaces.exceptionmanager.ReactJsExceptionHandler import com.facebook.react.module.model.ReactModuleInfo import com.facebook.react.module.model.ReactModuleInfoProvider -import com.facebook.react.runtime.ReactHostImpl import com.facebook.react.shell.MainReactPackage import com.facebook.react.uiapp.component.MyLegacyViewManager import com.facebook.react.uiapp.component.MyNativeViewManager +import com.facebook.react.uimanager.ReactShadowNode import com.facebook.react.uimanager.ViewManager import com.facebook.soloader.SoLoader @@ -95,17 +91,28 @@ class RNTesterApplication : Application(), ReactApplication { } } }, - object : ReactPackage { + object : ReactPackage, ViewManagerOnDemandReactPackage { override fun createNativeModules( reactContext: ReactApplicationContext - ): List { - return emptyList() - } + ): List = emptyList() + + override fun getViewManagerNames(reactContext: ReactApplicationContext) = + listOf("RNTMyNativeView", "RNTMyLegacyNativeView") override fun createViewManagers( reactContext: ReactApplicationContext ): List> = listOf(MyNativeViewManager(), MyLegacyViewManager(reactContext)) + + override fun createViewManager( + reactContext: ReactApplicationContext, + viewManagerName: String + ): ViewManager<*, out ReactShadowNode<*>> = + if (viewManagerName == "RNTMyNativeView") { + MyNativeViewManager() + } else { + MyLegacyViewManager(reactContext) + } }) } @@ -114,43 +121,15 @@ class RNTesterApplication : Application(), ReactApplication { } } + override val reactHost: ReactHost + get() = DefaultReactHost.getDefaultReactHost(this.applicationContext, reactNativeHost) + override fun onCreate() { ReactFontManager.getInstance().addCustomFont(this, "Rubik", R.font.rubik) super.onCreate() SoLoader.init(this, /* native exopackage */ false) if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - load() + load(bridgelessEnabled = true) } } - - @UnstableReactNativeAPI - override val reactHost: ReactHost by lazy { - // Create an instance of ReactHost to manager the instance of ReactInstance, - // which is similar to how we use ReactNativeHost to manager instance of ReactInstanceManager - val reactHostDelegate = RNTesterReactHostDelegate(applicationContext) - val reactJsExceptionHandler = RNTesterReactJsExceptionHandler() - val componentFactory = ComponentFactory() - register(componentFactory) - ReactHostImpl( - this.applicationContext, - reactHostDelegate, - componentFactory, - true, - reactJsExceptionHandler, - true) - .apply { - jsEngineResolutionAlgorithm = - if (BuildConfig.IS_HERMES_ENABLED_IN_FLAVOR) { - JSEngineResolutionAlgorithm.HERMES - } else { - JSEngineResolutionAlgorithm.JSC - } - reactHostDelegate.reactHost = this - } - } - - @UnstableReactNativeAPI - class RNTesterReactJsExceptionHandler : ReactJsExceptionHandler { - override fun reportJsException(errorMap: ReadableMapBuffer?) {} - } } diff --git a/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterReactHostDelegate.kt b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterReactHostDelegate.kt deleted file mode 100644 index 7ed21ca7e6958c..00000000000000 --- a/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterReactHostDelegate.kt +++ /dev/null @@ -1,138 +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. - */ - -package com.facebook.react.uiapp - -import android.content.Context -import com.facebook.fbreact.specs.SampleLegacyModule -import com.facebook.fbreact.specs.SampleTurboModule -import com.facebook.react.JSEngineResolutionAlgorithm -import com.facebook.react.ReactPackage -import com.facebook.react.ReactPackageTurboModuleManagerDelegate -import com.facebook.react.TurboReactPackage -import com.facebook.react.ViewManagerOnDemandReactPackage -import com.facebook.react.bridge.JSBundleLoader -import com.facebook.react.bridge.NativeModule -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.common.annotations.UnstableReactNativeAPI -import com.facebook.react.config.ReactFeatureFlags -import com.facebook.react.defaults.DefaultTurboModuleManagerDelegate -import com.facebook.react.fabric.ReactNativeConfig -import com.facebook.react.module.model.ReactModuleInfo -import com.facebook.react.module.model.ReactModuleInfoProvider -import com.facebook.react.runtime.BindingsInstaller -import com.facebook.react.runtime.JSCInstance -import com.facebook.react.runtime.JSEngineInstance -import com.facebook.react.runtime.ReactHostDelegate -import com.facebook.react.runtime.ReactHostImpl -import com.facebook.react.runtime.hermes.HermesInstance -import com.facebook.react.shell.MainReactPackage -import com.facebook.react.uiapp.component.MyLegacyViewManager -import com.facebook.react.uiapp.component.MyNativeViewManager -import com.facebook.react.uimanager.ViewManager - -@UnstableReactNativeAPI -class RNTesterReactHostDelegate internal constructor(context: Context) : ReactHostDelegate { - var reactHost: ReactHostImpl? = null - - override val jsMainModulePath: String = BuildConfig.JS_MAIN_MODULE_NAME - - override val jsBundleLoader: JSBundleLoader = - JSBundleLoader.createAssetLoader(context, "assets:" + BuildConfig.BUNDLE_ASSET_NAME, true) - - @get:Synchronized override val bindingsInstaller: BindingsInstaller? = null - - override val turboModuleManagerDelegateBuilder: ReactPackageTurboModuleManagerDelegate.Builder = - DefaultTurboModuleManagerDelegate.Builder() - - override val jsEngineInstance: JSEngineInstance = - if (reactHost?.jsEngineResolutionAlgorithm == JSEngineResolutionAlgorithm.JSC) { - JSCInstance() - } else { - HermesInstance() - } - - override fun handleInstanceException(error: Exception) {} - - override fun getReactNativeConfig(moduleProvider: (String) -> NativeModule?): ReactNativeConfig = - ReactNativeConfig.DEFAULT_CONFIG - - override val reactPackages: List by lazy { - listOf( - MainReactPackage(), - object : TurboReactPackage() { - override fun getModule( - name: String, - reactContext: ReactApplicationContext - ): NativeModule? = - when { - !ReactFeatureFlags.useTurboModules -> null - name == SampleTurboModule.NAME -> SampleTurboModule(reactContext) - name == SampleLegacyModule.NAME -> SampleLegacyModule(reactContext) - else -> null - } - - // Note: Specialized annotation processor for @ReactModule isn't - // configured in OSS - // yet. For now, hardcode this information, though it's not necessary - // for most modules - override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = - ReactModuleInfoProvider { - if (ReactFeatureFlags.useTurboModules) { - mapOf( - SampleTurboModule.NAME to - ReactModuleInfo( - SampleTurboModule.NAME, - "SampleTurboModule", - false, // canOverrideExistingModule - false, // needsEagerInit - false, // isCxxModule - true // isTurboModule - ), - SampleLegacyModule.NAME to - ReactModuleInfo( - SampleLegacyModule.NAME, - "SampleLegacyModule", - false, // canOverrideExistingModule - false, // needsEagerInit - false, // isCxxModule - false // isTurboModule - ), - ) - } else { - emptyMap() - } - } - }, - object : ViewManagerOnDemandReactPackage, ReactPackage { - override fun createNativeModules( - reactContext: ReactApplicationContext - ): List = emptyList() - - override fun createViewManagers( - reactContext: ReactApplicationContext - ): List> = - listOf(MyNativeViewManager(), MyLegacyViewManager(reactContext)) - - override fun getViewManagerNames( - reactContext: ReactApplicationContext - ): Collection = - listOf(MyNativeViewManager.REACT_CLASS, MyLegacyViewManager.REACT_CLASS) - - override fun createViewManager( - reactContext: ReactApplicationContext, - viewManagerName: String - ): ViewManager<*, *>? { - return when (viewManagerName) { - MyNativeViewManager.REACT_CLASS -> MyNativeViewManager() - MyLegacyViewManager.REACT_CLASS -> MyLegacyViewManager(reactContext) - else -> null - } - } - }) - } -}