Skip to content

Commit

Permalink
feat: Fully automatic autolinking for Nitrogen (#117)
Browse files Browse the repository at this point in the history
* move to folders

* Refactor autolinking

* Generate code

* fix: Fix `map` undefined in `unregisterHybridObjectConstructor`

* Create `<ModuleName>OnLoad.mm`

* feat: Add register calls

* hmm

* Update createHybridObjectInitializer.ts

* i am stupid

* Add `autolinking` to `nitro.json`

* Fix missing includes

* feat: Create Swift part for iOS autolinking... 👀

* fix duplicate symbols...

* feat: Properly create Swift types

* Rename to `Autolinking`

* docs: Update docs on new autogenerate setup

* Android base

* It works

* Base

* Initialize natives

* fix: Set up autolinking Cpp file

* Update NitroImageOnLoad.hpp

* feat: Try

* fix: Fix Promise using `global_ref`

* Update index.ts

* fix: Replace `CriticalNative` with `FastNative`

* feat: Generate the code to register JNI.. almost there!

* fix: Fix JNI paths (`L*;`)

* Update nitrogen.md
  • Loading branch information
mrousavy authored Sep 12, 2024
1 parent 31f7fc1 commit 0f04af9
Show file tree
Hide file tree
Showing 113 changed files with 993 additions and 517 deletions.
62 changes: 38 additions & 24 deletions docs/docs/nitrogen.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ Create a `nitro.json` file in the root directory of your Nitro Module (next to `
"android": {
"androidNamespace": ["math"],
"androidCxxLibName": "NitroMath"
}
},
"autolinking": {}
}
```

Expand Down Expand Up @@ -193,34 +194,47 @@ To implement `Math` now, you just need to implement the spec:
</TabItem>
</Tabs>

### 4. Register (native)

Then, register `HybridMath` somewhere on app startup so JS can initialize it.

<Tabs groupId="native-language">
<TabItem value="swift" label="Swift" default>
```swift
HybridObjectRegistry.registerHybridObjectConstructor("Math") {
return HybridMath()
}
```
</TabItem>
<TabItem value="kotlin" label="Kotlin">
```kotlin
HybridObjectRegistry.registerHybridObjectConstructor("Math") {
HybridMath()
### 4. Register the Hybrid Objects

Nitro needs to be able to initialize an instance of your Hybrid Object - so we need to tell it how to do that.
In your `nitro.json`, register `HybridMath` in the `"autolinking"` section:
<Tabs>
<TabItem value="swift-kotlin" label="Swift/Kotlin" default>
```json
{
...
"autolinking": {
"Math": {
"swift": "HybridMath",
"kotlin": "HybridMath"
}
}
}
```

:::warning
- Make sure `HybridMath` is default-constructible. That is, it has a public initializer that takes no arguments.
- Make sure the Java/Kotlin class `HybridMath` is inside the package/namespace `com.margelo.nitro.<<androidNamespace>>` (as configured in `nitro.json`).
- Make sure the Java/Kotlin class `HybridMath` is annotated with `@DoNotStrip` to avoid it from being compiled out in production builds.
:::
</TabItem>
<TabItem value="c++" label="C++">
```cpp
HybridObjectRegistry::registerHybridObjectConstructor(
"Math",
[]() -> std::shared_ptr<HybridObject> {
return std::make_shared<HybridMath>();
<TabItem value="cpp" label="C++">
```json
{
...
"autolinking": {
"Math": {
"cpp": "HybridMath"
}
}
);
}
```

:::warning
- Make sure `HybridMath` is default-constructible. That is, it has a public constructor that takes no arguments.
- Make sure the `HybridMath` class is defined in a header named `HybridMath.hpp` - this is what Nitro will import.
- Also make sure `HybridMath` is either in the global namespace, or in `margelo::nitro::<cxxNamespace>` (configured in `nitro.json`).
:::
</TabItem>
</Tabs>

Expand Down
62 changes: 36 additions & 26 deletions docs/docs/using-nitro-in-your-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ After installing Nitro, you can start creating your [Hybrid Objects](hybrid-obje

Nitrogen will ✨ automagically ✨ generate native specifications for each Hybrid Object based on a given TypeScript definition.

### Installing Nitrogen
### 1. Installing Nitrogen

First, install Nitrogen:

Expand Down Expand Up @@ -130,7 +130,7 @@ After installing Nitro, you can start creating your [Hybrid Objects](hybrid-obje
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroImage+autolinking.cmake)
```

### Creating Nitro specs
### 2. Creating Nitro specs

After Nitrogen has been successfully set up, create your first spec. Let's create `Math.nitro.ts`:
Expand Down Expand Up @@ -184,35 +184,45 @@ After installing Nitro, you can start creating your [Hybrid Objects](hybrid-obje
</TabItem>
</Tabs>

### Registering the Hybrid Objects

To allow JS to actually initialize the `Math` Hybrid Object, we need to tell Nitro how to create instances of it.
For this, simply use the `HybridObjectRegistry` somewhere on app startup (e.g. in `AppDelegate`):

<Tabs groupId="native-language">
<TabItem value="swift" label="Swift" default>
```swift
HybridObjectRegistry.registerHybridObjectConstructor("Math") {
return HybridMath()
}
```
</TabItem>
<TabItem value="kotlin" label="Kotlin">
```kotlin
HybridObjectRegistry.registerHybridObjectConstructor("Math") {
HybridMath()
### 3. Registering the Hybrid Objects

Nitro needs to be able to initialize an instance of your Hybrid Object - so we need to tell it how to do that.
In your `nitro.json`, register `HybridMath` in the `"autolinking"` section:
<Tabs>
<TabItem value="swift-kotlin" label="Swift/Kotlin" default>
```json
{
...
"autolinking": {
"Math": {
"swift": "HybridMath",
"kotlin": "HybridMath"
}
}
}
```

:::warning
- Make sure `HybridMath` is default-constructible. That is, it has a public initializer that takes no arguments.
:::
</TabItem>
<TabItem value="c++" label="C++">
```cpp
HybridObjectRegistry::registerHybridObjectConstructor(
"Math",
[]() -> std::shared_ptr<HybridObject> {
return std::make_shared<HybridMath>();
<TabItem value="cpp" label="C++">
```json
{
...
"autolinking": {
"Math": {
"cpp": "HybridMath"
}
}
);
}
```

:::warning
- Make sure `HybridMath` is default-constructible. That is, it has a public constructor that takes no arguments.
- Make sure the `HybridMath` class is defined in a header named `HybridMath.hpp` - this is what Nitro will import.
- Also make sure `HybridMath` is either in the global namespace, or in `margelo::nitro::<cxxNamespace>` (configured in `nitro.json`).
:::
</TabItem>
</Tabs>

Expand Down
8 changes: 4 additions & 4 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1820,11 +1820,11 @@ SPEC CHECKSUMS:
DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5
FBLazyVector: 38bb611218305c3bc61803e287b8a81c6f63b619
fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120
glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f
glog: 69ef571f3de08433d766d614c73a9838a06bf7eb
hermes-engine: 3b6e0717ca847e2fc90a201e59db36caf04dee88
NitroImage: a230ad8b4dce2b6bd12458570398cffe5463b877
NitroImage: 0cffeee137c14b8c8df97649626646528cb89b28
NitroModules: 47b80650d063f9b8ae7a3e02564cd282ad3d2ac9
RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47
RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740
RCTDeprecation: 34cbf122b623037ea9facad2e92e53434c5c7422
RCTRequired: 24c446d7bcd0f517d516b6265d8df04dc3eb1219
RCTTypeSafety: ef5e91bd791abd3a99b2c75fd565791102a66352
Expand Down Expand Up @@ -1885,4 +1885,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 83ff0c12eb91f4379210b71045e485dacb487fe9

COCOAPODS: 1.14.3
COCOAPODS: 1.15.2
184 changes: 1 addition & 183 deletions packages/nitrogen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,186 +19,4 @@ npm i nitrogen -D

## Usage

### 1. Create a react-native library

Use tools like [react-native-builder-bob](https://github.com/callstack/react-native-builder-bob) to create a react-native library.

The library must have C++ set up (on Android, a `build.gradle` with `externalNativeBuild`), TypeScript installed, and [**react-native-nitro-modules**](../react-native-nitro-modules/) installed as a dependency.

See [**react-native-nitro-image**](../react-native-nitro-image/) for an example.

### 2. Create a `nitro.json` config file

The `nitro.json` config file specifies native namespaces, library name, and other information that will be used for generating the code.

Every nitro module must have a `nitro.json` file at it's root level (i.e. the folder where `package.json` is).

You can automatically generate a `nitro.json` file by running:

```bash
npm run nitrogen init <moduleName> # e.g. "image"
```

Your `nitro.json` file should look like this:

```json
{
"cxxNamespace": ["image"],
"ios": {
"iosModulename": "NitroImage"
},
"android": {
"androidNamespace": ["image"],
"androidCxxLibName": "NitroImage"
}
}
```

### 3. Create a TypeScript spec

The TypeScript spec is the single source of truth. It's interfaces, enums or other type declarations will be converted to C++ (or Swift/Kotlin) types using a code generator. Nitrogen will run on all `*.nitro.ts` files relative to your `nitro.json` config.

```ts
// Image.nitro.ts
export interface Image extends HybridObject<{ ios: 'c++', android: 'c++' }> {
readonly width: number
readonly height: number
}
```

### 4. Run `nitrogen`

Inside your library's root folder (i.e. the folder where `nitro.json` is), run nitrogen:

```sh
npm run nitrogen
```

This will generate all native files inside `./nitrogen/generated`.

You can also specify a custom output directory via the `--out` parameter.

### 5. Add build setup

In addition to all the C++, Swift and Kotlin files, Nitrogen also generates build setup files that you can import in your `.podspec`/`CMakeLists.txt`/`build.gradle` to automatically add all nitro-generated files to the project.

#### iOS

In your `.podspec` file, add this **to the end** of your spec:

```ruby
Pod::Spec.new do |spec|
# ...

# Add all files generated by Nitrogen
load 'nitrogen/generated/ios/NitroImage+autolinking.rb'
add_nitrogen_files(spec)
end
```

#### Android

Add Nitro's core dependency to your `build.gradle` file:

```gradle
dependencies {
...
implementation project(":react-native-nitro-modules")
}
```

In your `build.gradle` file, add this:

```gradle
apply from: '../nitrogen/generated/android/NitroImage+autolinking.gradle'
```

And in your `CMakeLists.txt` file, add this:

```cmake
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroImage+autolinking.cmake)
```

### 6. Implement the specs

In C++:

```cpp
class HybridImage: public HybridImageSpec {
public:
HybridImage(): HybridObject(TAG) { }

public:
double getWidth() override { return 13; }
double getHeight() override { return 27; }
};
```
In Swift:
```swift
class HybridImage: HybridImageSpec {
public var hybridContext = margelo.nitro.HybridContext()
public var memorySize: Int {
return getSizeOf(self)
}
var width: Double {
return 13
}
var height: Double {
return 27
}
}
```

In Kotlin:

```kotlin
class HybridImage: HybridImageSpec {
override val memorySize: ULong
get() = 0

override val width: Double
get() = 13
override val height: Double
get() = 27
}
```

### 7. Register your Hybrid Object using the `HybridObjectRegistry`

To expose `HybridImage` to JS, register it in the [`HybridObjectRegistry`](../react-native-nitro-modules/cpp/registry/HybridObjectRegistry.hpp) at app startup:

In C++:

```cpp
#include <NitroModules/HybridObjectRegistry.hpp>

// Call this at app startup to register the HybridObjects
void load() {
HybridObjectRegistry::registerHybridObjectConstructor(
"Image",
[]() -> std::shared_ptr<HybridObject> {
return std::make_shared<HybridImage>();
}
);
}
```

In Objective-C++ (for Swift Hybrid Objects):

```mm
HybridObjectRegistry::registerHybridObjectConstructor("Image", []() -> std::shared_ptr<HybridObject> {
auto image = NitroImage::HybridImage::init();
return std::make_shared<HybridImageSpecSwift>(image);
});
```

In Kotlin/Java:

```java
HybridObjectRegistry.registerHybridObjectConstructor("Image", () -> {
return new Image();
});
```
See the [Nitrogen documentation](https://mrousavy.github.io/nitro/docs/nitrogen) for more information.
9 changes: 9 additions & 0 deletions packages/nitrogen/src/autolinking/Autolinking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { Platform } from '../getPlatformSpecs.js'
import type { SourceFile } from '../syntax/SourceFile.js'

type AutolinkingFile = Omit<SourceFile, 'language'>

export interface Autolinking {
platform: Platform
sourceFiles: AutolinkingFile[]
}
Loading

0 comments on commit 0f04af9

Please sign in to comment.