Insanely fast native C++, Swift or Kotlin modules with a statically compiled binding layer to JSI.
npm install react-native-nitro-modulesreact-native-nitro-modules is a core library that contains highly efficient statically compiled JS to C++ bindings.
It uses JSI to generate C++ templates that can bridge virtually any JS type to a C++ type with minimal overhead.
Install react-native-nitro-modules as a dependency in your react-native app:
``sh`
npm i react-native-nitro-modules
cd ios && pod install
If you are building a nitro module yourself, add react-native-nitro-modules as a peerDependency into your library's package.json:
`json`
{
...
"peerDependencies": {
...
"react-native-nitro-modules": "*"
},
}
Then install react-native-nitro-modules as a normal dependency in your library's example/ app as seen above.
react-native-nitro-modules can either be used with-, or without nitrogen, or mixed (some objects are automatically generated, some manually).
When using Nitrogen, all the bindings are automatically generated. You only need to implement C++, Swift or Kotlin interfaces inside your codebase.
All C++ bindings are bridged to JS using "Hybrid Objects".
A Hybrid Object can have both methods and properties (get and set).
Create a C++ Hybrid Object by inheriting from HybridObject:
`cpp
#include
using namespace margelo::nitro;
class MyHybridObject: public HybridObject {
public:
explicit MyHybridObject(): HybridObject(TAG) {}
public:
// Property (get)
double getNumber() { return 13; }
// Property (set)
void setNumber(double value) { }
// Method
double add(double left, double right) { return left + right; }
public:
void loadHybridMethods() override {
// Call base method to make sure we properly inherit toString() and equals()
HybridObject::loadHybridMethods();
// Register all methods that need to be exposed to JS
registerHybrids(this, [](Prototype& prototype) {
prototype.registerHybridGetter("number", &MyHybridObject::getNumber);
prototype.registerHybridSetter("number", &MyHybridObject::setNumber);
prototype.registerHybridMethod("add", &MyHybridObject::add);
});
}
private:
static constexpr auto TAG = "MyHybrid";
};
`
The MyHybridObject can then be registered in the HybridObjectRegistry at app startup:
`cpp
#include
// Call this at app startup to register the HybridObjects
void load() {
HybridObjectRegistry::registerHybridObjectConstructor(
"MyHybrid",
[]() -> std::shared_ptr
return std::make_shared
}
);
}
`
Inside your MyHybridObject, you can use standard C++ types which will automatically be converted to JS using Nitro's JSIConverter interface.
The following C++ / JS types are supported out of the box:
| JS Type | C++ Type | Swift Type | Kotlin Type |
|---|---|---|---|
number | double / int / float | Double | Double |
boolean | bool | Bool | Boolean |
string | std::string | String | String |
bigint | int64_t / uint64_t | Int64 | Long |
T[] | std::vector<T> | [T] | Array<T> / PrimitiveArray |
[A, B, C, ...] | std::tuple<A, B, C, ...> | (A, B, C) 🟡 (#38) | ❌ |
A | B | C | ... | std::variant<A, B, C, ...> | Variant_A_B_C | Variant_A_B_C |
Record<string, T> | std::unordered_map<std::string, T> | Dictionary<String, T> | Map<std::string, T> |
T? | std::optional<T> | T? | T? |
null | NullType | NullType | NullType |
(T...) => void | std::function<void (T...)> | @escaping (T...) -> Void | (T...) -> Unit |
(T...) => R | std::function<std::shared_ptr<Promise<R>> (T...)> | (T...) -> Promise<T> | (T...) -> Promise<T> |
Sync<(T...) => R> | std::function<R (T...)> | @escaping (T...) -> R | (T...) -> R |
Error | std::exception_ptr | Error | Throwable |
Promise<T> | std::shared_ptr<Promise<T>> | Promise<T> | Promise<T> |
AnyMap | std::shared_ptr<AnyMap> | AnyMap | AnyMap |
ArrayBuffer | std::shared_ptr<ArrayBuffer> | ArrayBuffer | ArrayBuffer |
Date | std::chrono::system_clock::time_point | Date | java.time.Instant |
..any HybridObject | std::shared_ptr<HybridObject> | HybridObject | HybridObject |
..any interface | struct T | struct T | data class T |
..any enum | enum T | enum T | enum T |
..any union | enum T | enum T | enum T |
Since the JSIConverter is just a template, you can extend it with any other custom types by overloading the interface.
For example, to add support for an enum, overload JSIConverter:
`cpp
#include
enum class MyEnum {
FIRST = 0,
SECOND = 1
};
namespace margelo::nitro {
template <>
struct JSIConverter
static inline MyEnum fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
int intValue = JSIConverter
return static_cast
}
static inline jsi::Value toJSI(jsi::Runtime& runtime, MyEnum arg) {
int intValue = static_cast
return JSIConverter
}
};
}
`
Once the JSIConverter for MyEnum is defined, you can use the type MyEnum in C++ methods, getters and setters of HybridObjects.
And on the JS side, you can simply treat the returned number (int) as a MyEnum:
`jsMyEnum
enum MyEnum {
FIRST = 0,
SECOND = 1
}
const value = myHybridObject.getEnumValue() // <-- typed as instead of number`
Make sure to always include the header that defines the JSIConverter overload inside the MyHybridObject file, as this is where the JSIConverter overloads are accessed from.
Nitrogen can automatically generate such JSIConverter