diff --git a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java
index 1516c841e1d..b960aaed3fa 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java
@@ -116,4 +116,15 @@ public class ReactFeatureFlags {
/** Feature Flag to control RN Android scrollEventThrottle prop. */
public static boolean enableScrollEventThrottle = false;
+
+ /**
+ * Feature flag that controls how turbo modules are exposed to JS
+ *
+ *
+ * - 0 = as a HostObject
+ *
- 1 = as a plain object, backed with a HostObject as prototype
+ *
- 2 = as a plain object, with all methods eagerly configured
+ *
+ */
+ public static int turboModuleBindingMode = 0;
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp
index 04238f8eb8f..a4d1c1289d9 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp
+++ b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp
@@ -20,6 +20,17 @@
namespace facebook {
namespace react {
+constexpr static auto ReactFeatureFlagsJavaDescriptor =
+ "com/facebook/react/config/ReactFeatureFlags";
+
+static int getFeatureFlagValue(const char *name) {
+ static const auto reactFeatureFlagsJavaDescriptor =
+ jni::findClassStatic(ReactFeatureFlagsJavaDescriptor);
+ const auto field =
+ reactFeatureFlagsJavaDescriptor->getStaticField(name);
+ return reactFeatureFlagsJavaDescriptor->getStaticFieldValue(field);
+}
+
TurboModuleManager::TurboModuleManager(
jni::alias_ref jThis,
RuntimeExecutor runtimeExecutor,
@@ -169,8 +180,13 @@ void TurboModuleManager::installJSIBindings() {
return nullptr;
};
+ TurboModuleBindingMode bindingMode = static_cast(
+ getFeatureFlagValue("turboModuleBindingMode"));
TurboModuleBinding::install(
- runtime, std::move(turboModuleProvider), longLivedObjectCollection_);
+ runtime,
+ std::move(turboModuleProvider),
+ bindingMode,
+ longLivedObjectCollection_);
});
}
diff --git a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.cpp b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.cpp
index 3e27164946a..89ac90bcd83 100644
--- a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.cpp
+++ b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.cpp
@@ -15,5 +15,26 @@ TurboModule::TurboModule(
std::shared_ptr jsInvoker)
: name_(std::move(name)), jsInvoker_(std::move(jsInvoker)) {}
+jsi::Value TurboModule::get(
+ jsi::Runtime &runtime,
+ const jsi::PropNameID &propName,
+ const MethodMetadata &meta) {
+ auto result = jsi::Function::createFromHostFunction(
+ runtime,
+ propName,
+ static_cast(meta.argCount),
+ [this, meta](
+ jsi::Runtime &rt,
+ const jsi::Value &thisVal,
+ const jsi::Value *args,
+ size_t count) { return meta.invoker(rt, *this, args, count); });
+ // If we have a JS wrapper, cache the result of this lookup
+ // We don't cache misses, to allow for methodMap_ to dynamically be extended
+ if (jsRepresentation_) {
+ jsRepresentation_->setProperty(runtime, propName, result);
+ }
+ return result;
+}
+
} // namespace react
} // namespace facebook
diff --git a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h
index a55b8bec9ff..e0788dcbf73 100644
--- a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h
+++ b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h
@@ -53,16 +53,7 @@ class JSI_EXPORT TurboModule : public facebook::jsi::HostObject {
// Method was not found, let JS decide what to do.
return facebook::jsi::Value::undefined();
} else {
- MethodMetadata meta = p->second;
- return facebook::jsi::Function::createFromHostFunction(
- runtime,
- propName,
- static_cast(meta.argCount),
- [this, meta](
- jsi::Runtime &rt,
- const jsi::Value &thisVal,
- const jsi::Value *args,
- size_t count) { return meta.invoker(rt, *this, args, count); });
+ return get(runtime, propName, p->second);
}
}
}
@@ -80,7 +71,16 @@ class JSI_EXPORT TurboModule : public facebook::jsi::HostObject {
size_t count);
};
+ facebook::jsi::Value get(
+ facebook::jsi::Runtime &runtime,
+ const facebook::jsi::PropNameID &propName,
+ const MethodMetadata &meta);
+
std::unordered_map methodMap_;
+
+ private:
+ friend class TurboModuleBinding;
+ std::unique_ptr jsRepresentation_;
};
/**
diff --git a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp
index d24ef17707e..77c8d222318 100644
--- a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp
+++ b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp
@@ -24,13 +24,16 @@ namespace react {
TurboModuleBinding::TurboModuleBinding(
const TurboModuleProviderFunctionType &&moduleProvider,
+ TurboModuleBindingMode bindingMode,
std::shared_ptr longLivedObjectCollection)
: moduleProvider_(std::move(moduleProvider)),
- longLivedObjectCollection_(std::move(longLivedObjectCollection)) {}
+ longLivedObjectCollection_(std::move(longLivedObjectCollection)),
+ bindingMode_(bindingMode) {}
void TurboModuleBinding::install(
jsi::Runtime &runtime,
const TurboModuleProviderFunctionType &&moduleProvider,
+ TurboModuleBindingMode bindingMode,
std::shared_ptr longLivedObjectCollection) {
runtime.global().setProperty(
runtime,
@@ -41,6 +44,7 @@ void TurboModuleBinding::install(
1,
[binding = TurboModuleBinding(
std::move(moduleProvider),
+ bindingMode,
std::move(longLivedObjectCollection))](
jsi::Runtime &rt,
const jsi::Value &thisVal,
@@ -76,7 +80,33 @@ jsi::Value TurboModuleBinding::getModule(
module = moduleProvider_(moduleName);
}
if (module) {
- return jsi::Object::createFromHostObject(runtime, std::move(module));
+ // Default behaviour
+ if (bindingMode_ == TurboModuleBindingMode::HostObject) {
+ return jsi::Object::createFromHostObject(runtime, std::move(module));
+ }
+
+ auto &jsRepresentation = module->jsRepresentation_;
+ if (!jsRepresentation) {
+ jsRepresentation = std::make_unique(runtime);
+ if (bindingMode_ == TurboModuleBindingMode::Prototype) {
+ // Option 1: create plain object, with it's prototype mapped back to the
+ // hostobject. Any properties accessed are stored on the plain object
+ auto hostObject =
+ jsi::Object::createFromHostObject(runtime, std::move(module));
+ jsRepresentation->setProperty(
+ runtime, "__proto__", std::move(hostObject));
+ } else {
+ // Option 2: eagerly install all hostfunctions at this point, avoids
+ // prototype
+ for (auto it = module->methodMap_.cbegin();
+ it != module->methodMap_.cend();
+ ++it) {
+ auto propName = jsi::PropNameID::forUtf8(runtime, it->first);
+ module->get(runtime, propName, it->second);
+ }
+ }
+ }
+ return jsi::Value(runtime, *jsRepresentation);
} else {
return jsi::Value::null();
}
diff --git a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.h b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.h
index 8d7742f3ff0..1791d4e272e 100644
--- a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.h
+++ b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.h
@@ -18,6 +18,12 @@ namespace react {
class JSCallInvoker;
+enum class TurboModuleBindingMode : uint8_t {
+ HostObject = 0,
+ Prototype = 1,
+ Eager = 2,
+};
+
/**
* Represents the JavaScript binding for the TurboModule system.
*/
@@ -30,11 +36,13 @@ class TurboModuleBinding {
static void install(
jsi::Runtime &runtime,
const TurboModuleProviderFunctionType &&moduleProvider,
+ TurboModuleBindingMode bindingMode,
std::shared_ptr longLivedObjectCollection);
private:
TurboModuleBinding(
const TurboModuleProviderFunctionType &&moduleProvider,
+ TurboModuleBindingMode bindingMode,
std::shared_ptr longLivedObjectCollection);
virtual ~TurboModuleBinding();
@@ -50,6 +58,7 @@ class TurboModuleBinding {
TurboModuleProviderFunctionType moduleProvider_;
std::shared_ptr longLivedObjectCollection_;
+ TurboModuleBindingMode bindingMode_;
};
} // namespace react
diff --git a/ReactCommon/react/nativemodule/core/platform/ios/RCTTurboModuleManager.mm b/ReactCommon/react/nativemodule/core/platform/ios/RCTTurboModuleManager.mm
index e58ce0dc126..a6689b4e7ff 100644
--- a/ReactCommon/react/nativemodule/core/platform/ios/RCTTurboModuleManager.mm
+++ b/ReactCommon/react/nativemodule/core/platform/ios/RCTTurboModuleManager.mm
@@ -776,12 +776,14 @@ static Class getFallbackClassFromName(const char *name)
if (RCTGetTurboModuleCleanupMode() == kRCTGlobalScope ||
RCTGetTurboModuleCleanupMode() == kRCTGlobalScopeUsingRetainJSCallback) {
runtimeExecutor([turboModuleProvider = std::move(turboModuleProvider)](jsi::Runtime &runtime) {
- react::TurboModuleBinding::install(runtime, std::move(turboModuleProvider), nullptr);
+ react::TurboModuleBinding::install(
+ runtime, std::move(turboModuleProvider), TurboModuleBindingMode::HostObject, nullptr);
});
} else if (RCTGetTurboModuleCleanupMode() == kRCTTurboModuleManagerScope) {
runtimeExecutor([turboModuleProvider = std::move(turboModuleProvider),
longLivedObjectCollection = _longLivedObjectCollection](jsi::Runtime &runtime) {
- react::TurboModuleBinding::install(runtime, std::move(turboModuleProvider), longLivedObjectCollection);
+ react::TurboModuleBinding::install(
+ runtime, std::move(turboModuleProvider), TurboModuleBindingMode::HostObject, longLivedObjectCollection);
});
}
}