Skip to content

Commit

Permalink
react-native code-gen > Add a C++ only TurboModule example (for Andro…
Browse files Browse the repository at this point in the history
…id/iOS/macOS/Windows) (#35138)

Summary:
Pull Request resolved: #35138

Changelog:

[General][Added] - Add a C++ only TurboModule example (for Android/iOS/macOS/Windows)

react-native@0.69 introduced a new bridging layer to ease integration for pure C++ TurboModules using C++ std:: types directly instead of the lower level jsi:: types:
https://github.com/facebook/react-native/tree/v0.69.0/ReactCommon/react/bridging

This bridging layer can be used in JSI functions or more conveniently in C++ TurboModules.

Here is a example of an C++ only TurboModule which will work on Android and iOS and macOS/Windows (using microsoft/react-native-macos|windows) only using flow/TypeScript and standard C++ types.

C++ only TurboModules are very handy as they do not require to work with JSI APIs - instead std:: or custom C++ can by used.

Differential Revision: D39011736

fbshipit-source-id: ae51d375c2056b977c27a9141a681101df68e211
  • Loading branch information
christophpurrer authored and facebook-github-bot committed Nov 7, 2022
1 parent 6152763 commit 96d84db
Show file tree
Hide file tree
Showing 15 changed files with 789 additions and 55 deletions.
40 changes: 40 additions & 0 deletions packages/rn-tester/BUCK
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
load("@fbsource//tools/build_defs:glob_defs.bzl", "subdir_glob")
load("@fbsource//xplat/hermes/defs:hermes.bzl", "HERMES_BYTECODE_VERSION")
load("//tools/build_defs:fb_native_wrapper.bzl", "fb_native")
load("//tools/build_defs:fb_xplat_platform_specific_rule.bzl", "fb_xplat_platform_specific_rule")
Expand All @@ -10,6 +11,7 @@ load("//tools/build_defs/apple:flag_defs.bzl", "get_objc_arc_preprocessor_flags"
load("//tools/build_defs/oss:metro_defs.bzl", "rn_library")
load(
"//tools/build_defs/oss:rn_defs.bzl",
"ANDROID",
"APPLE",
"YOGA_APPLE_TARGET",
"js_library_glob",
Expand Down Expand Up @@ -49,6 +51,7 @@ rn_library(
srcs = js_library_glob(
[
"js",
"NativeCxxModuleExample",
"NativeModuleExample",
"NativeComponentExample",
"RCTTest",
Expand Down Expand Up @@ -319,3 +322,40 @@ rn_xplat_cxx_library2(
"//xplat/js/react-native-github:RCTFabricComponentViewsBase",
],
)

rn_xplat_cxx_library2(
name = "NativeCxxModuleExample",
srcs = glob(["NativeCxxModuleExample/*.cpp"]),
header_namespace = "",
exported_headers = subdir_glob(
[
("NativeCxxModuleExample", "*.h"),
],
prefix = "NativeCxxModuleExample",
),
fbandroid_compiler_flags = [
"-fexceptions",
"-frtti",
],
platforms = (ANDROID, APPLE),
visibility = ["PUBLIC"],
deps = [
":NativeCxxModuleExampleSpecJSI",
],
)

rn_library(
name = "NativeCxxModuleExampleSpec",
srcs = glob(["NativeCxxModuleExample/*.js"]),
codegen_modules = True,
labels = [
"pfh:ReactNative_CommonInfrastructurePlaceholder",
],
native_module_android_package_name = "com.facebook.fbreact.specs",
native_module_spec_name = "NativeCxxModuleExampleSpec",
visibility = ["PUBLIC"],
deps = [
"//xplat/js/RKJSModules/vendor/react:react",
"//xplat/js/react-native-github:react-native",
],
)
110 changes: 110 additions & 0 deletions packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* 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.
*/

#include "NativeCxxModuleExample.h"

namespace facebook::react {

NativeCxxModuleExample::NativeCxxModuleExample(
std::shared_ptr<CallInvoker> jsInvoker)
: NativeCxxModuleExampleCxxSpec(std::move(jsInvoker)) {}

void NativeCxxModuleExample::getValueWithCallback(
jsi::Runtime &rt,
AsyncCallback<std::string> callback) {
callback({"value from callback!"});
}

std::vector<std::optional<ObjectStruct>> NativeCxxModuleExample::getArray(
jsi::Runtime &rt,
std::vector<std::optional<ObjectStruct>> arg) {
return arg;
}

bool NativeCxxModuleExample::getBool(jsi::Runtime &rt, bool arg) {
return arg;
}

ConstantsStruct NativeCxxModuleExample::getConstants(jsi::Runtime &rt) {
return ConstantsStruct{true, 69, "react-native"};
}

int32_t NativeCxxModuleExample::getEnum(jsi::Runtime &rt, int32_t arg) {
return arg;
}

std::map<std::string, std::optional<int32_t>> NativeCxxModuleExample::getMap(
jsi::Runtime &rt,
std::map<std::string, std::optional<int32_t>> arg) {
return arg;
}

double NativeCxxModuleExample::getNumber(jsi::Runtime &rt, double arg) {
return arg;
}

ObjectStruct NativeCxxModuleExample::getObject(
jsi::Runtime &rt,
ObjectStruct arg) {
return arg;
}

std::set<float> NativeCxxModuleExample::getSet(
jsi::Runtime &rt,
std::set<float> arg) {
return arg;
}

std::string NativeCxxModuleExample::getString(
jsi::Runtime &rt,
std::string arg) {
return arg;
}

std::string NativeCxxModuleExample::getUnion(
jsi::Runtime &rt,
float x,
std::string y,
jsi::Object z) {
std::string result = "x: " + std::to_string(x) + ", y: " + y + ", z: { ";
if (z.hasProperty(rt, "value")) {
result += "value: ";
result += std::to_string(z.getProperty(rt, "value").getNumber());
} else if (z.hasProperty(rt, "low")) {
result += "low: ";
result += z.getProperty(rt, "low").getString(rt).utf8(rt);
}
result += " }";
return result;
}

ValueStruct NativeCxxModuleExample::getValue(
jsi::Runtime &rt,
double x,
std::string y,
ObjectStruct z) {
ValueStruct result{x, y, z};
return result;
}

AsyncPromise<std::string> NativeCxxModuleExample::getValueWithPromise(
jsi::Runtime &rt,
bool error) {
auto promise = AsyncPromise<std::string>(rt, jsInvoker_);
if (error) {
promise.reject("intentional promise rejection");
} else {
promise.resolve("result!");
}
return promise;
}

void NativeCxxModuleExample::voidFunc(jsi::Runtime &rt) {
// Nothing to do
}

} // namespace facebook::react
66 changes: 66 additions & 0 deletions packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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

#if __has_include(<React-Codegen/AppSpecsJSI.h>)
#include <React-Codegen/AppSpecsJSI.h>
#else
#include <NativeCxxModuleExampleSpec/NativeCxxModuleExampleSpecJSI.h>
#endif
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "NativeCxxModuleExample_ConstantsStruct.h"
#include "NativeCxxModuleExample_ObjectStruct.h"
#include "NativeCxxModuleExample_ValueStruct.h"

namespace facebook::react {

class NativeCxxModuleExample
: public NativeCxxModuleExampleCxxSpec<NativeCxxModuleExample> {
public:
NativeCxxModuleExample(std::shared_ptr<CallInvoker> jsInvoker);

void getValueWithCallback(
jsi::Runtime &rt,
AsyncCallback<std::string> callback);

std::vector<std::optional<ObjectStruct>> getArray(
jsi::Runtime &rt,
std::vector<std::optional<ObjectStruct>> arg);

bool getBool(jsi::Runtime &rt, bool arg);

ConstantsStruct getConstants(jsi::Runtime &rt);

int32_t getEnum(jsi::Runtime &rt, int32_t arg);

std::map<std::string, std::optional<int32_t>> getMap(
jsi::Runtime &rt,
std::map<std::string, std::optional<int32_t>> arg);

double getNumber(jsi::Runtime &rt, double arg);

ObjectStruct getObject(jsi::Runtime &rt, ObjectStruct arg);

std::set<float> getSet(jsi::Runtime &rt, std::set<float> arg);

std::string getString(jsi::Runtime &rt, std::string arg);

std::string getUnion(jsi::Runtime &rt, float x, std::string y, jsi::Object z);

ValueStruct
getValue(jsi::Runtime &rt, double x, std::string y, ObjectStruct z);

AsyncPromise<std::string> getValueWithPromise(jsi::Runtime &rt, bool error);

void voidFunc(jsi::Runtime &rt);
};

} // namespace facebook::react
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* 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.
*
* @flow strict-local
* @format
*/

import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';

import {TurboModuleRegistry} from 'react-native';

/** Flow enum support will be added when the sun rises in the west
export enum EnumInt {
A = 23,
B = 42,
}
*/

export type UnionFloat = 1.44 | 2.88 | 5.76;
export type UnionString = 'One' | 'Two' | 'Three';
export type UnionObject = {value: number} | {low: string};

export type ObjectStruct = $ReadOnly<{
a: number,
b: string,
c?: ?string,
}>;

export type ValueStruct = $ReadOnly<{
x: number,
y: string,
z: ObjectStruct,
}>;

export interface Spec extends TurboModule {
+getArray: (arg: Array<ObjectStruct | null>) => Array<ObjectStruct | null>;
+getBool: (arg: boolean) => boolean;
+getConstants: () => {|
const1: boolean,
const2: number,
const3: string,
|};
+getEnum: (arg: number /*EnumInt*/) => number /*EnumInt*/;
+getMap: (arg: {[key: string]: ?number}) => {[key: string]: ?number};
+getNumber: (arg: number) => number;
+getObject: (arg: ObjectStruct) => ObjectStruct;
+getSet: (arg: Array<number>) => Array<number>;
+getString: (arg: string) => string;
+getUnion: (x: UnionFloat, y: UnionString, z: UnionObject) => string;
+getValue: (x: number, y: string, z: ObjectStruct) => ObjectStruct;
+getValueWithCallback: (callback: (value: string) => void) => void;
+getValueWithPromise: (error: boolean) => Promise<string>;
+voidFunc: () => void;
}

export default (TurboModuleRegistry.get<Spec>(
'NativeCxxModuleExampleCxx',
): ?Spec);
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 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.

require "json"

package = JSON.parse(File.read(File.join(__dir__, "../package.json")))

Pod::Spec.new do |s|
s.name = "NativeCxxModuleExample"
s.version = package["version"]
s.summary = package["description"]
s.description = "NativeCxxModuleExample"
s.homepage = "https://github.com/facebook/react-native.git"
s.license = "MIT"
s.platforms = { :ios => "12.4" }
s.compiler_flags = '-Wno-nullability-completeness'
s.author = "Meta Platforms, Inc. and its affiliates"
s.source = { :git => "https://github.com/facebook/react-native.git", :tag => "#{s.version}" }
s.source_files = "**/*.{h,cpp}"
s.requires_arc = true
s.pod_target_xcconfig = {
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
}

install_modules_dependencies(s)

s.dependency "ReactCommon/turbomodule/core"

# Enable codegen for this library
use_react_native_codegen!(s, {
:react_native_path => "../../..",
:js_srcs_dir => "./",
:library_type => "modules",
})
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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

#include <react/bridging/Bridging.h>
#include <optional>
#include <string>

namespace facebook::react {

struct ConstantsStruct {
bool const1;
int32_t const2;
std::string const3;
bool operator==(const ConstantsStruct &other) const {
return const1 == other.const1 && const2 == other.const2 &&
const3 == other.const3;
}
};

template <>
struct Bridging<ConstantsStruct> {
static jsi::Object toJs(jsi::Runtime &rt, const ConstantsStruct &value) {
auto result = facebook::jsi::Object(rt);
result.setProperty(rt, "const1", bridging::toJs(rt, value.const1));
result.setProperty(rt, "const2", bridging::toJs(rt, value.const2));
result.setProperty(rt, "const3", bridging::toJs(rt, value.const3));
return result;
}
};

} // namespace facebook::react
Loading

0 comments on commit 96d84db

Please sign in to comment.