Skip to content

Conversation

christophpurrer
Copy link
Contributor

Summary:
Changelog: [Internal]

Adds a test case to assert Turbo Module eventEmitter properties in GTests

Differential Revision: D83280050

@meta-cla meta-cla bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 3, 2025
Copy link

meta-codesync bot commented Oct 3, 2025

@christophpurrer has exported this pull request. If you are a Meta employee, you can view the originating Diff in D83280050.

Copy link

meta-codesync bot commented Oct 3, 2025

@christophpurrer has exported this pull request. If you are a Meta employee, you can view the originating Diff in D83280050.

christophpurrer added a commit to christophpurrer/react-native-macos that referenced this pull request Oct 3, 2025
…ebook#54060)

Summary:

Changelog: [Internal]

Adds a test case to assert Turbo Module eventEmitter properties in GTests

Differential Revision: D83280050
@christophpurrer christophpurrer force-pushed the export-D83280050 branch 2 times, most recently from 98871d9 to 97ce630 Compare October 3, 2025 23:56
christophpurrer added a commit to christophpurrer/react-native-macos that referenced this pull request Oct 3, 2025
…ebook#54060)

Summary:

Changelog: [Internal]

Adds a test case to assert Turbo Module eventEmitter properties in GTests

Differential Revision: D83280050
christophpurrer added a commit to christophpurrer/react-native-macos that referenced this pull request Oct 4, 2025
…ebook#54060)

Summary:

Changelog: [Internal]

Adds a test case to assert Turbo Module eventEmitter properties in GTests

Differential Revision: D83280050
christophpurrer added a commit to christophpurrer/react-native-macos that referenced this pull request Oct 4, 2025
…ebook#54060)

Summary:

Changelog: [Internal]

Adds a test case to assert Turbo Module eventEmitter properties in GTests

Differential Revision: D83280050
christophpurrer added a commit to christophpurrer/react-native-macos that referenced this pull request Oct 4, 2025
…ebook#54060)

Summary:

Changelog: [Internal]

Adds a test case to assert Turbo Module eventEmitter properties in GTests

Differential Revision: D83280050
)

Summary:

Changelog: [Internal]

Per documentation the base class to extend from is the non `JSI` one:

https://reactnative.dev/docs/the-new-architecture/pure-cxx-modules#3-write-the-native-code

Reviewed By: rubennorte

Differential Revision: D83815509
Summary:

Changelog: [Internal]

Right now for a simple spec as
```
import type {CodegenTypes, TurboModule} from 'react-native';

import {TurboModuleRegistry} from 'react-native';

export type ScreenshotManagerOptions = CodegenTypes.UnsafeObject;

export interface Spec extends TurboModule {
  +getConstants: () => {};
  takeScreenshot(
    id: string,
    options: ScreenshotManagerOptions,
  ): Promise<string>;
}

const NativeModule = TurboModuleRegistry.get<Spec>('ScreenshotManager');
export function takeScreenshot(
  id: string,
  options: ScreenshotManagerOptions,
): Promise<string> {
  if (NativeModule != null) {
    return NativeModule.takeScreenshot(id, options);
  }
  return Promise.reject();
}
```

we generate **TWO** `facebook::react::TurboModule` sub classes (`NativeScreenshotManagerCxxSpecJSI` and `NativeScreenshotManagerCxxSpec`) to construct ONE C++ TM.

In particular header
```
#pragma once

#include <ReactCommon/TurboModule.h>
#include <react/bridging/Bridging.h>

namespace facebook::react {

class JSI_EXPORT NativeScreenshotManagerCxxSpecJSI : public TurboModule {
 protected:
  NativeScreenshotManagerCxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);

 public:
  virtual jsi::Object getConstants(jsi::Runtime& rt) = 0;
  virtual jsi::Value
  takeScreenshot(jsi::Runtime& rt, jsi::String id, jsi::Object options) = 0;
};

template <typename T>
class JSI_EXPORT NativeScreenshotManagerCxxSpec : public TurboModule {
 public:
  jsi::Value create(jsi::Runtime& rt, const jsi::PropNameID& propName)
      override {
    return delegate_.create(rt, propName);
  }

  std::vector<jsi::PropNameID> getPropertyNames(
      jsi::Runtime& runtime) override {
    return delegate_.getPropertyNames(runtime);
  }

  static constexpr std::string_view kModuleName = "ScreenshotManager";

 protected:
  NativeScreenshotManagerCxxSpec(std::shared_ptr<CallInvoker> jsInvoker)
      : TurboModule(
            std::string{NativeScreenshotManagerCxxSpec::kModuleName},
            jsInvoker),
        delegate_(reinterpret_cast<T*>(this), jsInvoker) {}

 private:
  class Delegate : public NativeScreenshotManagerCxxSpecJSI {
   public:
    Delegate(T* instance, std::shared_ptr<CallInvoker> jsInvoker)
        : NativeScreenshotManagerCxxSpecJSI(std::move(jsInvoker)),
          instance_(instance) {}

    jsi::Object getConstants(jsi::Runtime& rt) override {
      static_assert(
          bridging::getParameterCount(&T::getConstants) == 1,
          "Expected getConstants(...) to have 1 parameters");

      return bridging::callFromJs<jsi::Object>(
          rt, &T::getConstants, jsInvoker_, instance_);
    }
    jsi::Value takeScreenshot(
        jsi::Runtime& rt,
        jsi::String id,
        jsi::Object options) override {
      static_assert(
          bridging::getParameterCount(&T::takeScreenshot) == 3,
          "Expected takeScreenshot(...) to have 3 parameters");

      return bridging::callFromJs<jsi::Value>(
          rt,
          &T::takeScreenshot,
          jsInvoker_,
          instance_,
          std::move(id),
          std::move(options));
    }

   private:
    friend class NativeScreenshotManagerCxxSpec;
    T* instance_;
  };

  Delegate delegate_;
};

} // namespace facebook::react
```
and cpp
```
#include "AppSpecsJSI.h"

namespace facebook::react {

static jsi::Value __hostFunction_NativeScreenshotManagerCxxSpecJSI_getConstants(
    jsi::Runtime& rt,
    TurboModule& turboModule,
    const jsi::Value* args,
    size_t count) {
  return static_cast<NativeScreenshotManagerCxxSpecJSI*>(&turboModule)
      ->getConstants(rt);
}

static jsi::Value
__hostFunction_NativeScreenshotManagerCxxSpecJSI_takeScreenshot(
    jsi::Runtime& rt,
    TurboModule& turboModule,
    const jsi::Value* args,
    size_t count) {
  return static_cast<NativeScreenshotManagerCxxSpecJSI*>(&turboModule)
      ->takeScreenshot(
          rt,
          count <= 0 ? throw jsi::JSError(
                           rt, "Expected argument in position 0 to be passed")
                     : args[0].asString(rt),
          count <= 1 ? throw jsi::JSError(
                           rt, "Expected argument in position 1 to be passed")
                     : args[1].asObject(rt));
}

NativeScreenshotManagerCxxSpecJSI::NativeScreenshotManagerCxxSpecJSI(
    std::shared_ptr<CallInvoker> jsInvoker)
    : TurboModule("ScreenshotManager", jsInvoker) {
  methodMap_["getConstants"] = MethodMetadata{
      0, __hostFunction_NativeScreenshotManagerCxxSpecJSI_getConstants};
  methodMap_["takeScreenshot"] = MethodMetadata{
      2, __hostFunction_NativeScreenshotManagerCxxSpecJSI_takeScreenshot};
}

} // namespace facebook::react
```

The goal of this change is to simplify that and only have **ONE** `facebook::react::TurboModule` base class for a concrete Cxx TM as this header

```
/*
 * 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.
 */

#pragma once

template <typename T>
class JSI_EXPORT NativeScreenshotManagerCxxSpec : public TurboModule {
public:
  static constexpr std::string_view kModuleName = "ScreenshotManager";

protected:
  NativeScreenshotManagerCxxSpec(std::shared_ptr<CallInvoker> jsInvoker) : TurboModule(std::string{NativeScreenshotManagerCxxSpec::kModuleName}, jsInvoker) {
    methodMap_["getConstants"] = MethodMetadata {.argCount = 0, .invoker = __getConstantsJSI};
    methodMap_["takeScreenshot"] = MethodMetadata {.argCount = 2, .invoker = __takeScreenshotJSI};
  }
 
private:
  static jsi::Value __getConstantsJSI(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* /*args*/, size_t /*count*/) {
    static_assert(
      bridging::getParameterCount(&T::getConstants) == 1,
      "Expected getConstants(...) to have 1 parameters");
    auto* self = static_cast<T*>(&turboModule);
    return bridging::callFromJs<jsi::Object>(rt, &T::getConstants, self->jsInvoker_, self);    
  }

  static jsi::Value __takeScreenshotJSI(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
    static_assert(
      bridging::getParameterCount(&T::takeScreenshot) == 3,
      "Expected takeScreenshot(...) to have 3 parameters");
    auto* self = static_cast<T*>(&turboModule);
    return bridging::callFromJs<jsi::Value>(rt, &T::takeScreenshot, self->jsInvoker_, self,
      count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt),
      count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt));
  }
};
```

This reduces the generated code from 101 lines to 31 % - **REDUCTION of 2/3**

## Use of a private member ` std::shared_ptr<CallInvoker> jsInvoker_;` on GitHub

https://github.com/search?q=std%3A%3Ashared_ptr%3CCallInvoker%3E+jsInvoker_%3B+NOT+path%3AReactCommon+NOT+path%3ATurboModule.h+NOT+path%3ACallbackWrapper.h+NOT+path%3A.svn_base+language%3AC%2B%2B+NOT+path%3A.cpp&type=code

-> None in a code-gen TurboModule

Differential Revision: D83810977
…ebook#54060)

Summary:

Changelog: [Internal]

Adds a test case to assert Turbo Module eventEmitter properties in GTests

Differential Revision: D83280050
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. fb-exported meta-exported p: Facebook Partner: Facebook Partner
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants