Skip to content

Commit

Permalink
Simplify new app template for bridgeless (#40929)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
cortinico authored and facebook-github-bot committed Oct 16, 2023
1 parent 50522cf commit 6d1fbc6
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 199 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.<platform>`
* @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,
Expand All @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -95,17 +91,28 @@ class RNTesterApplication : Application(), ReactApplication {
}
}
},
object : ReactPackage {
object : ReactPackage, ViewManagerOnDemandReactPackage {
override fun createNativeModules(
reactContext: ReactApplicationContext
): List<NativeModule> {
return emptyList()
}
): List<NativeModule> = emptyList()

override fun getViewManagerNames(reactContext: ReactApplicationContext) =
listOf("RNTMyNativeView", "RNTMyLegacyNativeView")

override fun createViewManagers(
reactContext: ReactApplicationContext
): List<ViewManager<*, *>> =
listOf(MyNativeViewManager(), MyLegacyViewManager(reactContext))

override fun createViewManager(
reactContext: ReactApplicationContext,
viewManagerName: String
): ViewManager<*, out ReactShadowNode<*>> =
if (viewManagerName == "RNTMyNativeView") {
MyNativeViewManager()
} else {
MyLegacyViewManager(reactContext)
}
})
}

Expand All @@ -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?) {}
}
}
Loading

0 comments on commit 6d1fbc6

Please sign in to comment.