Nitrogen
Nitrogen is Nitro's code-generator. It parses TypeScript code using an AST parser to generate native interfaces from TypeScript definitions.
@iOS(...)
@Android(...)
export declare class Math extends HybridObject {
readonly pi: number
add(a: number, b: number): number
}
export * from '#nitro/Math'
public protocol HybridMathSpec_protocol: HybridObject {
var pi: Double { get }
func add(a: Double, b: Double) throws -> Double
}
When HybridMathSpec is not implemented properly on the native side (e.g. if add(..) is missing, or if a type is incorrect), the app will not compile, which ensures full type-safety and null-safety at compile-time.
Nitrogen is optional
Nitrogen is a fully optional CLI that does some of the work for you.
You can also build Nitro Modules and create Hybrid Objects without nitrogen, by just registering the HybridClass yourself.
Who uses Nitrogen?
Nitrogen should be used by library-authors, and generated specs should be committed to the repository/package.
If you build an app that uses libraries built with Nitro, you do not need to run nitrogen yourself.
Configuration
Nitrogen should be installed as a dev-dependency in the Nitro Module (library).
- npm
- yarn
- pnpm
- bun
npm i nitrogen --save-dev
yarn add nitrogen -D
pnpm add nitrogen -D
bun i nitrogen -d
Each Nitro Module needs to have a nitro.json configuration file.
Create a nitro.json file in the root directory of your Nitro Module (next to package.json), and add the following content:
{
"$schema": "https://nitro.margelo.com/nitro.schema.json",
"cxxNamespace": ["math"],
"ios": {
"iosModuleName": "NitroMath"
},
"android": {
"androidNamespace": ["math"],
"androidCxxLibName": "NitroMath"
}
}
Usage
Nitrogen parses all TypeScript files that end in .nitro.ts.
1. Write TypeScript specs
A Nitro spec file consists of one or more declare class statements and combined exports.
declare classdescribes your native class via instance methods, properties (get + set),constructor(...), or static methods.- The
@iOSand@Androiddecorators ondeclare classdescribe the connection between the JS types and the native implementation - for example,language: 'swift'means thatMathis implemented in Swift on iOS.implementation: ...defines the actual Swift class that implements the spec natively, which is required for it to have aconstructor()or static methods. - Since
declare classis type-only, no JS runtime code exists. Nitrogen generates this, which must be re-exported viaexport * from '#nitro/Math'.
See Platform decorators for all @iOS and @Android options, including multiple implementations, inheritance, and native protocol/interface requirements.
import { iOS, type HybridObject } from 'nitro-core'
@iOS({
language: 'swift',
implementation: { className: 'HybridMath' }
})
export declare class Math extends HybridObject {
// Public constructor: new Math()
constructor()
// Static method: Math.add(5, 3)
static add(a: number, b: number): number
// Get-only instance property: math.pi
readonly pi: number
// Instance method: math.multiply(5, 3)
multiply(a: number, b: number): number
}
export * from '#nitro/Math'
2. Generate native specs
Now run nitrogen:
- npm
- yarn
- pnpm
- bun
npx nitrogen
yarn nitrogen
pnpm nitrogen
bun nitrogen
This generates the shared C++ specs, platform bindings, and JavaScript class exports in ./nitrogen/generated/. Run Nitrogen before compiling TypeScript so the generated modules and their declarations exist:
🔧 Loading nitro.json config...
🚀 Nitrogen runs at ~/Projects/nitro/example/dummy
🔍 Nitrogen found 1 spec in ~/Projects/nitro/example/dummy
⏳ Parsing Math.nitro.ts...
⚙️ Generating specs for HybridObject "Math"...
shared: Generating C++ code...
🎉 Generated 1/1 HybridObject in 0.6s!
💡 Your code is in ./nitrogen/generated
‼️ Added 8 files - refresh SwiftPM (`npx react-native spm` for React Native) or sync Gradle to update files!
Commit ./nitrogen/generated/ and include it in your npm package, including the .cjs runtime modules and .d.cts declarations in shared/javascript/. If you use a files list in package.json, add nitrogen/generated to it. Consumers use these files without running Nitrogen themselves.
3. Add generated sources to your library
All the generated sources (./nitrogen/generated/) need to be part of your library's code - so we need to add it to the iOS/Android build files.
- With the Nitro template
- Manually
If you created a library using the Nitro Module template, your library already includes nitrogen's generated sources.
JS imports/exports
To resolve the #nitro/* alias, add an imports mapping to your library's package.json:
{
"imports": {
"#nitro/*": "./nitrogen/generated/shared/javascript/*.cjs"
}
}
React Native's default typescript-config does not allow overridden imports in package.json, so enable it in your tsconfig.json:
{
"extends": "@react-native/typescript-config",
"compilerOptions": {
"resolvePackageJsonImports": true
}
}
iOS
iOS builds use Swift Package Manager. Create a Package.swift next to your library's nitro.json:
// swift-tools-version: 6.4
import PackageDescription
let package = Package(
name: "NitroMath",
platforms: [.iOS(.v16)]
)
addNitrogenTargets(to: package)
Run nitrogen again after adding this file. Nitrogen appends the addNitrogenTargets helper between // nitrogen:begin and // nitrogen:end, and updates that section on subsequent runs. Keep your package declaration and helper call outside the generated section.
The helper adds your Swift sources from ios/, C++ sources from cpp/, and the generated specs and bridges. It also configures Swift/C++ interoperability, C++20, and native dependencies. To use different source directories, pass paths relative to the package root:
addNitrogenTargets(
to: package,
swiftSources: ["Sources/NitroMath"],
cxxSources: ["native/cpp"]
)
Add swiftpmConfig to your library's package.json so React Native links the generated library product and its dependencies:
{
"swiftpmConfig": {
"name": "NitroMath",
"dependencies": ["react-native-nitro-modules"]
}
}
name must match ios.iosModuleName in nitro.json. Add the npm names of any other Nitro modules your library depends on to dependencies.
Keep a .podspec with your library's metadata so React Native CLI can discover the iOS dependency, as in the template podspec. Native sources and dependencies belong in Package.swift; remove the old add_nitrogen_files(...) call. Commit the updated manifest and include both Package.swift and the .podspec in your npm package.
In the consuming React Native app, refresh SwiftPM after installing the library or changing its native sources:
npx react-native spm
For an existing CocoaPods app, first run npx react-native spm add --deintegrate from the app directory. See Minimum Requirements for the required React Native and Swift versions.
4. Implement the Hybrid Objects
To implement Math now, you just need to implement the spec:
- Swift
- Kotlin
- C++
import NitroModules
final class HybridMath : HybridMathSpec {
static func add(a: Double, b: Double) throws -> Double {
return a + b
}
var pi: Double { Double.pi }
func multiply(a: Double, b: Double) throws -> Double {
return a * b
}
}
class HybridMath : HybridMathSpec() {
companion object {
fun add(a: Double, b: Double): Double {
return a + b
}
}
override val pi: Double = kotlin.math.PI
override fun multiply(a: Double, b: Double): Double {
return a * b
}
}
#include <NitroMath/HybridMathSpec.hpp>
class HybridMath: public HybridMathSpec {
public:
HybridMath() = default;
static double add(double a, double b) {
return a + b;
}
double getPi() override { return 3.141592653589793; }
double multiply(double a, double b) override {
return a * b;
}
};
5. Use the Hybrid Objects
Static methods are directly callable on the Math type:
import { Math } from './Math.nitro'
const result = Math.add(5, 7) // 12
Since Math has a constructor(), we can create instances of it with new:
import { Math } from './Math.nitro'
const math = new Math()
math.pi // 3.1415
math.multiply(5, 3) // 15
Lastly, expose the class to consumers through your library's entry point:
export { Math } from './specs/Math.nitro'