build-nitro-modules について
React Native Nitro Moduleの構築を一貫して支援するスキルです。リポジトリのひな形作成、TypeScriptのHybridObject仕様、ネイティブ実装、公開準備を扱い、Nitro固有のAPI設計やコード生成、検証に重点を置きます。
主な機能
- 01Nitrogenによるモジュールリポジトリ作成
- 02TypeScriptのHybridObject仕様の作成
- 03C++、Swift、Kotlinコードの生成
- 04ネイティブ実装とサンプルアプリ設定の案内
- 05コード生成、検証、リリース準備を網羅
活用シーン
- 01React Native Nitro Moduleの新規作成
- 02型付きHybridObject APIの定義
- 03複数プラットフォーム向けネイティブバインディングの実装
- 04公開に向けたモジュールリポジトリの準備
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ margelo/ react-native-skills --skill build-nitro-modulesインストール後、Claude Code で/build-nitro-modulesを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/example-android-config.mdexample-app-setup.mdexample-metro-install.mdnative-implement-cpp.mdnative-implement-kotlin.mdnative-implement-swift.mdnative-nitrogen-codegen.mdrelease-it-publishing.mdrepo-structure-and-workflow.mdsetup-monorepo-init.mdspec-hybrid-object.mdspec-hybrid-view.mdspec-nitro-json.mdspec-package-publish.mdvision-camera-golden-standard.md詳細
Nitro モジュールの構築
概要
React Native Nitro モジュールを構築するためのエンドツーエンドのスキルです。Nitrogen によるモノレポのひな形作成、TypeScript HybridObject の仕様の作成、ネイティブコードの生成、プラットフォーム実装(C++/Swift/Kotlin)、サンプルアプリの接続、公開準備を扱います。
Nitro モジュールでは、コード生成パイプライン(nitrogen)が .nitro.ts 仕様ファイルを読み込み、ネイティブ C++/Swift/Kotlin の定型コードを生成します。その後、実装を記述します。これは従来の Turbo Module とは根本的に異なります。
nitrogen/generated/ 以下のファイルは生成物です。これらのファイルを手動で編集するのではなく、.nitro.ts 仕様またはネイティブ実装のソースを変更してから、nitrogen を再実行してください。これらのファイルは git にコミットできます。多くの Nitro ライブラリでコミットされていますが、リポジトリのポリシーによってコミットしない選択もできます。ただし、利用者がネイティブライブラリをビルドできるよう、npm パッケージには含める必要があります。
API 設計との併用
公開する TypeScript、JavaScript、React、React Native API を設計する際は、まず api-design を使用してください。このスキルでは、HybridObject の状態、生成される仕様、ネイティブリソースの所有権、ゼロコピーのデータ、スレッド処理、プラットフォーム実装、コード生成、実機での検証といった Nitro 固有の制約を追加します。
一般的な公開 API のルールと API の最新性チェックは api-design に任せてください。このスキルでは、モバイルのツールチェーンと生成テンプレートに関する判断についてのみ、Nitro 固有の最新性チェックを追加します。バージョン、設定項目、ネイティブ実装の詳細を決める前に、Nitro、React Native、Gradle、Xcode、Swift、Kotlin、NDK、パッケージツールの最新のドキュメントやソースを確認してください。
ユーザーが JavaScript のみを使う React または React Native ライブラリを構築している場合、Nitro、HybridObjects、ネイティブモジュール、コード生成、C++/Swift/Kotlin バインディング、react-native-nitro-modules がタスクに関係しない限り、このスキルを適用しないでください。
Swift を使用する HybridObject、AVFoundation/セッションコード、DispatchQueue の使用、Swift の並行処理、またはスレッドに依存する Swift の状態を実装またはレビューする場合は、swift と併用してください。Kotlin を使用する HybridObject、Android のスレッド処理、コルーチン、Kotlin の null 許容性、sealed な結果モデル、または Android サービスへのアクセスを実装またはレビューする場合は、kotlin と併用してください。C++ を使用する HybridObject、共有ネイティブエンジン、CMake、RAII による所有権管理、または生成された C++ 仕様バインディングを実装またはレビューする場合は、cpp と併用してください。
リポジトリとリリースの参考資料
リポジトリの作成、レイアウトの再編成、サンプル/ドキュメント/CI の追加、またはワークフローポリシーの変更を行う場合に限り、repo-structure-and-workflow.md を読み込んでください。
bun release/release-it を設定またはレビューする場合に限り、release-it-publishing.md を読み込んでください。
Nitro API 設計ルール
- ネイティブモジュールの実装には、TurboModules や手書きの JSI よりも Nitro を優先してください。Nitro は、JSI の生のライフタイム、スレッド処理、ランタイム破棄に伴う多くの危険を回避できるため、通常はより高速で安全です。Nitro の Raw JSI Methods が必要な場合に限り、生の JSI を使用してください。
- ルートの HybridObject は、オートリンクのためにデフォルト構築可能にしてください。引数に依存するオブジェクトは、ファクトリーメソッドで作成してください。
- ネイティブの状態には HybridObjects を使用してください。ネイティブリソース、事前ウォームアップ済みのエンジン、ファイル、画像、データベース、センサーセッション、ストリームなど、状態を持つオブジェクトが対象です。
- ネイティブのセットアップが必要な場合は、ファクトリーメソッドを非同期にし、
createCameraSession(...): Promise<CameraSession>のように、準備完了の HybridObject で解決してください。 - JS 向けの HybridObject spec は、生成された spec を実装する複数の具体的なネイティブクラスを持てます。これを使って、バックエンドの戦略を 1 つの TypeScript 型の背後に隠してください。たとえば、
CameraVideoOutputの実装を、ムービーファイル出力、またはビデオデータ出力とアセットライターの組み合わせのどちらかにできます。ファクトリーがネイティブ実装を選び、共通の spec 型を返します。 - JS に公開するファクトリーオブジェクトには、生成された spec 型名ではなく、プロダクトやドメインを表す名詞を使ってください。たとえば、
VisionCamera = createHybridObject<CameraFactory>('CameraFactory')やImages = createHybridObject<ImageFactory>('ImageFactory')としてエクスポートします。これにより、CameraFactory/ImageFactory型との衝突を避けつつ、機械的に小文字化したりHybridプレフィックスを付けたりせずに済みます。 - 各 HybridObject の用途またはライフサイクルを 1 つに限定してください。
- ドメインを表す名詞だけを基準に HybridObject の境界を決めないでください。単発のコマンド、アプリが所有するライブセッション、ネイティブビュー、長期間稼働するエンジンは、同じ機能領域に属していても異なる契約です。
- オプション、結果、ライフサイクル、将来のプラットフォーム対応に違いがある場合は、安定した意味上の機能ごとに返却する HybridObjects を分けてください。たとえば、ルートの
DataScannerFactoryからcreateBarcodeScanner()とcreateTextScanner()を公開し、Android が現在バーコードのみをサポートしているためにテキスト用フィールドが null 許容となる、広範な 1 つのスキャナーを避けます。 - 現時点でその機能をサポートしていないプラットフォームでは、ファクトリーが
isTextScannerAvailable: falseを返す、またはcreateTextScanner()を拒否しても構いません。将来のプラットフォーム対応では、既存の機能を実装して可用性を true にするだけで済むようにしてください。肥大化した HybridObject の再設計が必要になる形にはしないでください。 - 概念が安定しているなら、プラットフォーム固有の機能も第一級の HybridObject として扱えます。たとえば、iOS 専用の
CameraObjectOutputは共通のCameraOutputを拡張し、@platform iOSとマークできます。Android ではcreateObjectOutput(...)を拒否し、すべての出力にオブジェクトスキャン用の null 許容フィールドを追加することは避けてください。 - ファクトリーメソッドを使ってワークフローを分け、返却する HybridObjects の契約をより明確にしてください。
createLiveScanner()がライブスキャナーセッションを返す場合、その実装が制御する基本的なセッション操作は、その型で保証する必要があります。ライブワークフローを提供できないバックエンドは、作成を失敗させるか、より限定的なオブジェクトを返してください。ライブ用オブジェクトに、使われないメソッド、null 許容の基本プロパティ、繰り返しのcan*チェックを持たせないでください。 - 古い状態を避けるため、設定済みのハンドルを返してください。
configure(...)がデバイス、出力、ストリーム、ネイティブグラフを関連付ける場合は、その設定済みリソースに対するコマンド用に新しい HybridObject ハンドルを返してください。たとえば、CameraSession.configure(...)は、暗黙の「現在のデバイス」状態を使うCameraSession上のメソッドではなく、setZoom(...)とfocusTo(...)を呼び出すためのCameraControllerハンドルを返すべきです。 - 再設定によってネイティブの対象が変わったハンドルは、置き換えるか無効化してください。コマンドの対象が最後に成功した設定に依存する場合、幅広い親 HybridObject にそのコマンドを持たせないでください。
- ネイティブ側でネゴシエーションを行う場合は、要求された意図と解決済みの状態を別々にモデル化してください。入力には優先順位付きの制約または設定を使用し、セッションが実際に選択した内容を示す解決済みの設定 HybridObject/struct を返すか、イベントとして通知します。呼び出し側がネイティブセッションを作成または開始せずに結果をプレビューできるよう、明示的なリゾルバーメソッドを用意してください。
- 機能フィールドは、ワークフローの判別、任意の設定、実際に変動するサポート状況の表現に使ってください。サポート対象のバックエンドで通常の使用時にメソッドが使えない、肥大化した HybridObject を取り繕うために機能フィールドを使わないでください。
- HybridObjects を API の主要なオブジェクトとして扱ってください。主要な HybridObject ごとに、独自の
.nitro.tsファイルを用意します。 - 継承ファミリーを 1 つの
.nitro.tsファイルにまとめるのは、そのファイル名がベースの HybridObject に由来し、子 HybridObject がメンバーをほとんど、またはまったく追加しない場合に限ってください。たとえば、ScannedCode.nitro.tsにおけるScannedCode、ScannedBarcode、ScannedQRCodeのような場合です。 - 名前付きコード生成型は、それぞれ独自の
.tsファイルに置いてください。文字列リテラルのユニオン/enum、struct/interface、オプションオブジェクト、イベントオブジェクト、コールバックオプション用の struct、ヘルパー型が対象です。Nitro がネイティブの struct と enum 相当の値を生成するには名前が必要です。これらを.nitro.tsの spec にインポートし、公開型はsrc/index.tsから再エクスポートしてください。 addErrorListener(listener: (error: Error) => void): ListenerSubscriptionのように、メソッドのシグネチャ内に簡潔な関数コールバックを記述してください。ScannerErrorListenerのような使い捨ての別名は、複数の API で公開概念として再利用される関数型でない限り作らないでください。- 複数のヘルパー型を 1 つのファイルにまとめるのは、
DynamicRangeと、それを定義する正確なリテラルユニオンのように、密接に関連する 1 つの論理的な構成要素を形成する場合に限ってください。 - 共通のネイティブ状態と特殊な結果形状を表すには、HybridObject の継承を使用してください。ID、境界、raw 値、形式、値型などの共通プロパティは、サブタイプごとに繰り返さず、ベースオブジェクトに置いてください。
- 異種のネイティブ結果ファミリーを表すには、HybridObject の継承を使用してください。たとえば、
ScannedItemが共通の状態とメソッドを持ち、ScannedBarcode、ScannedQRCode、ScannedFaceが特殊なプロパティを追加して拡張します。API はScannedItem[]を返せます。JS 側では判別プロパティを使って型を絞り込み、ネイティブコードでは共通の動作しか必要ない場合に、生成されたベース spec を受け取れます。 - 状態の種類を、すべてのサブタイプのフィールドを null 許容にした 1 つの Nitro struct または HybridObject としてモデル化しないでください。
barcodeとbarcodeTypeのような関係がコンパイル時に安全となるよう、HybridObject の継承、判別可能なユニオン、またはプラットフォームのプロトコル/インターフェース準拠を使用してください。 - 公開 Nitro HybridObjects を命令型 API として扱ってください。ユーザー向けの API として使う場合は、生成された Nitro API を 1:1 でエクスポートしてください。値の事前解析、文字列/enum の変換、オプションの再構成、隠れたデフォルトの注入、
.nitro.tsの spec が公開するものとは異なる内部メソッド形式の呼び出しを行う JS ラッパーを追加しないでください。 - React フック、React コンポーネント、UI 構成ヘルパーなど、意図的に高水準な API には JS/TS レイヤーを使用できます。また、HybridObject が内部実装の詳細にすぎず、ユーザー向けの考え方に合わない場合も同様です。その場合は境界を明確にしてください。ラッパーが公開 API で、Nitro オブジェクトは内部用です。
- JS が直接構築する必要がある公開ルート、ファクトリー、ビュー、グローバルユーティリティのみをオートリンクしてください。その他の HybridObjects はファクトリーメソッドから返せるため、それぞれに
nitro.jsonのオートリンクエントリは不要です。同じ JS 向け spec を実装する具体的なネイティブ実装をすべてオートリンクしないでください。 - ネイティブの拡張ポイントには、JS 向けのベース HybridObject spec と公開ネイティブプロトコル/インターフェースを組み合わせてください。ベース spec によって JS は型付き API 経由でオブジェクトを渡せます。ネイティブプロトコル/インターフェースによって、プラットフォーム固有のハンドルと動作を、ファーストパーティおよびサードパーティのネイティブコードに公開できます。
- JS から拡張可能な HybridObject を受け取る場合は、生成されたベース spec 型で受け取り、ネイティブ側でネイティブプロトコル/インターフェースにキャストし、準拠していない場合は明確なエラーをスローしてください。これにより、JS のポータビリティを保ちながら、ネイティブ統合では強い型付けを維持できます。
- ドメインの形状、オプショングループ、同じ型のパラメーター群には Nitro struct を使用してください。引数の数を減らすためだけに、互いに無関係なホットパスの値を struct で包まないでください。Nitro は struct を積極的に変換するため、不要なラッパーは明示的なパラメーターより遅くなる場合があります。
- TypeScript のオプショナルフィールドは、生成される Swift、Kotlin、C++ でネイティブのオプショナル型(
T?/std::optional<T>)になることを念頭に置いてください。Nitro のオプショナルフィールドは、不在が意図した公開/ネイティブ契約の一部である場合にのみ使用してください。JS ラッパーが変換して取り除くからという理由で使わないでください。 - Nitro メソッドが生成する struct を、実際に公開する入力形式として優先してください。デフォルト値や解決済みオプションが必要なら、spec/ネイティブ実装で明示的にモデル化するか、内部 HybridObject の上に意図的に高水準な API を設けてください。公開 HybridObject に対して、安易な JS 正規化レイヤーを追加しないでください。
- 高頻度のネイティブ結果、解析済みペイロード、画像、バッファー、多数の高コストなオプショナルフィールドを持つオブジェクトを、フラットな struct としてモデル化しないでください。Nitro struct は積極的に変換されるため、呼び出し側が読み取らない可能性のあるデータには、遅延プロパティやメソッドを持つ状態付き HybridObjects を優先してください。
- 各結果を Nitro struct と HybridObject のどちらにするか、明示的に判断してください。スカラー値だけを含む小さな不変の結果なら struct にできます。結果がネイティブ状態を所有する、コストの高いデータを遅延公開する、ゼロコピーのバイナリアクセスが必要、または動作/メソッドの追加が見込まれる場合は HybridObject を使用してください。
- 小さなネイティブデータ、または真にゼロコピーとなるネイティブデータへのアクセスには
ArrayBufferを使用してください。大きなメディア、写真、スキャン結果、モデル出力、バイト列のペイロードには HybridObject を返し、バイト列を JS に即時変換するのではなく、toArrayBuffer()、toBase64()、saveToTemporaryFile()などの遅延メソッドを公開してください。 - ゼロコピーだからという理由だけで
ArrayBufferを選ばないでください。デコード済みテキストのペイロードにはstringを、生のバイト列、バイナリペイロード、メディア、またはバイト単位のアクセスが API 契約である不透明なデータにはArrayBufferを使用してください。 - 変換コストが重要な場合は、生のネイティブ状態を HybridObjects の背後に保持してください。たとえば、ネイティブのバーコード/写真/結果オブジェクトは、検出ごとにすべてのフィールドを変換するのではなく、
rawValue、bounds、format、バイトデータなどを遅延して公開できます。 - 繰り返し発生するイベントには、
addOn...Listener(callback): ListenerSubscriptionを優先し、各サブスクリプション自身にクリーンアップを管理させてください。これにより、共有オブジェクトの状態を通じて別の呼び出し元のコールバックを置き換えてしまうことを避けられます。 - リスナーのサブスクリプションには、クリーンアップ以外のネイティブ状態を公開する場合を除き、HybridObject ではなく
remove: () => void関数フィールドを持つフラットな struct を使用してください。呼び出し側では引き続きsubscription.remove()を使います。 - リスナーのクリーンアップによって、それ以降のイベント通知を停止してください。進行中のネイティブイベントスナップショットですでに選択されたコールバックをキャンセルする必要はありません。その保証だけを目的にロックや複雑なサブスクリプション状態を追加しないでください。
- リスナーの追加、削除、コールバック呼び出しを、実装のデフォルトパターンとしてロックしないでください。単一の所有スレッド/キュー、または不変スナップショットを優先してください。所有権によって解消できない共有可変データの競合が実証されている場合に限り、ロックを追加します。
setOn...(callback | undefined)形式の API は、オブジェクトが所有する単一のホットパスコールバックに限って使用してください。コールバックの置き換えや削除が自然な操作である場合に適しています。setという動詞とドキュメントで、置き換えの動作が明確になるようにしてください。- ネイティブ API が単一のデリゲート/コールバックしか公開しない一方で、JS API が繰り返し発生するイベントを表す場合は、ホットパスで安全性を損なう、またはコストがかかりすぎる場合を除き、内部で多重化し、追加型のリスナーを公開してください。
Sync<(...) => ...>コールバックは、特定の JS ランタイムまたはワークレットスレッドで同期的に実行する必要がある、まれなスレッド依存のホットパスに限って使用してください。- Nitro Views では、生の
getHostComponentラッパーを公開してください。同じネイティブオブジェクトと参照を使う構成コードを繰り返し減らせる場合に限り、React コンポーネントやフックを追加してください。Nitro View コンポーネントの作成時またはレビュー時には、spec-hybrid-view.md を読み込んでください。 - 命名、プラットフォーム抽象化、同期/非同期の境界、リスナーのクリーンアップ、エラー、バリアント、TypeScript ファサード、JSDoc の契約には
api-designに従ってください。
Nitro ネイティブ実装ルール
- 奇妙なビルドエラーのデバッグを始める前に、Nitro の現在の最低要件を確認してください。現在の Nitro ドキュメントでは、React Native 0.75 以上、Xcode 16.4 以上、Swift 5.9 以上、Android の
compileSdkVersion34 以上、NDK 27 以上が要件として記載されています。Nitro Views では現在、React Native 0.78 以上と New Architecture が必要です。 - すべてのネイティブ HybridObject 実装は、そのプラットフォーム用に生成された
Hybrid*Specを実装するか継承する必要があります。独立したネイティブクラスを実装して、Nitro が検出してくれると期待してはいけません。 - 継承が本当に必要な場合を除き、ネイティブ実装クラスはデフォルトで
finalにしてください。これは特に Swift と Kotlin の HybridObject に当てはまります。 - ファイルごとに、トップレベルのネイティブ実装型を必ず1つだけにしてください。
Hybrid*Factory.swift/.ktファイルにはHybrid*Factory型のみを含め、ほかの struct/class/enum/interface/protocol は置かないでください。ヘルパーセッション、コーディネーター、デリゲート、オプションアダプター、プラットフォームラッパーはそれぞれ別のファイルに置いてください。 - ファイル分割を避けるために、ヘルパー型を主要な型の中にネストしないでください。ヘルパー型には、それぞれ専用の名前付きファイルを用意してください。
- ネイティブファイル名はスコープの契約として扱ってください。HybridObject 名を冠したファイルでは、その HybridObject の実装に集中し、拡張メソッド/プロパティ、プラットフォームヘルパー、コンバーター、デリゲート、プロトコル、再利用可能なユーティリティを寄せ集めないでください。
- ネイティブファイルのレビュー時には、行数を判断材料にしてください。1型1ファイルのルールとファクトリーにヘルパーを置かないルールを満たしたうえで、およそ300行未満であれば通常は許容できます。別々の名前を付けられるヘルパーが行数の大部分を占める場合は、ファイルを分割してください。
- ネイティブ変換ヘルパーは、意味のある最小の型に配置してください。ネイティブ変換が生成された1つの struct または enum のみを読み取る場合、複数のネイティブ値を返す場合でも、その型に配置してください。コレクションの構成は、重複排除、検証、空でないことの確認、バッチ処理、エラー集約など、コレクション変換に実質的な処理が加わる場合を除き、呼び出し元に残してください。
- Kotlin の
Intや Swift のIntなど、広範なネイティブ/共通レシーバーにドメイン変換を置かないでください。代わりに、BarcodeFormat.Companion.fromFormat(format: Int)やBarcodeFormat.from(format:)のように、ドメイン型の companion、static factory、または初期化子の方向に配置してください。 - すべての Kotlin 拡張
fun/val/varと Swift の拡張メソッド/プロパティは、可能な限り internal/package 可視性を使用し、別の名前付き拡張/コンバーターファイルに配置してください。Kotlin コンバーターには、BarcodeFormat+fromMLKitBarcodeFormat.ktのようなType+operation.kt形式の名前を使用してください。Hybrid*ファイルやほかの実装ファイルには、private/小規模/同一ファイルといった例外はありません。これにより、コード分割、保守性、将来のレビュー差分をすっきり保てます。 Hybrid*Factory.ktまたはHybrid*.swiftファイルに、実装の下へバーコード形式のマッピング、companion 拡張、プラットフォーム変換ユーティリティ、その他の拡張を含めないでください。Hybrid*Factoryファイルはオーケストレーションのみにしてください。生成されたオプションを解決し、検証/事前チェックヘルパーを呼び出し、ネイティブセッションまたはプラットフォーム API を作成/開始し、Promise を返すか reject してください。セッション/コーディネーター/デリゲートクラス、ネイティブオプションアダプターの struct/class、presenter の検索ヘルパー、builder/config アダプター、バーコードマッピング、権限の switch 文、Info.plist/manifest のチェック、機能チェックをファクトリーファイル内に定義しないでください。- ネイティブ変換は、変換元/変換先ごとに、焦点を絞った拡張ファイル1つに分割してください。例:
Barcode+toScannedCode.kt、TargetBarcodeFormat+toMLKitFormat.kt、BarcodeFormat+fromMLKitBarcodeFormat.kt。Array<TargetBarcodeFormat>.toMLKitFormats()のような具体的なコレクション拡張の裏に、単純なmap { it.toX() }を隠さないでください。 - プラットフォームの事前チェックは
Hybrid*ファクトリーや実装メソッドの外に切り出してください。認証ステータスの switch 文、Info.plist/manifest キーの検証、権限チェック、ハードウェア機能チェック、サービス利用可否チェックは、焦点を絞ったヘルパー/拡張ファイルに配置してください。HybridObject の呼び出し元は、短い guard または1行のtry/check呼び出しとして読めるようにしてください。 - インラインの事前チェックコードを、広範な
Utilsファイルで置き換えないでください。Bundle+CameraUsageDescription.swift、AVCaptureDevice+CameraAuthorization.swift、または焦点を絞った Android 権限/機能ヘルパーのような、小さく名前の付いたファイルを使用してください。 - Kotlin では、プラットフォームが対応するアノテーションまたは
@IntDefを公開している場合、CameraX のフラッシュ/撮影/ミラーモードのアノテーションなどを、Android プラットフォームのInt定数に付けてください。アノテーションのターゲットに応じて関数、戻り値、またはパラメーターに配置し、構文を選ぶ前に Java アノテーションか Kotlin アノテーションかを確認してください。 - Android のアノテーション付き
Intから変換する場合、対応していればドメインファクトリー/companion 関数のformat: Intパラメーターにアノテーションを付けてください。これにより、単なるIntよりも IDE と lint から適切なフィードバックを得られます。 - 無効な入力や必須の未対応動作は、早い段階で検証してください。現在のネイティブバックエンドが任意の設定を無視するからといって、クロスプラットフォームの設定全体を拒否しないでください。操作の主要な機能が引き続き実行できる場合は、その設定を無視するか劣化させ、機能または解決後の状態を通じて対応状況を報告してください。
- 実際の失敗を黙って無視しないでください。要求された結果を提供できない場合は、例外をスローする、Promise を reject する、または明示的なエラーコールバック/リスナーを通じて通知してください。
- JS に適切に伝わる Nitro/ランタイムエラー、または言語ネイティブの例外を優先してください。生成 API が特に必要とする場合を除き、公開経路で Objective-C 形式の
NSErrorを使用しないでください。 - Nitro Modules に空の iOS ブリッジヘッダー(
Bridge.hなど)を追加しないでください。Nitro は必要な Swift/C++ ブリッジを生成します。実際に手書きの Objective-C コードが必要な場合に限り、Objective-C/Objective-C++ ヘッダーを追加し、podspec には必要なファイルだけを含めてください。 - Android のコンテキストにアクセスする場合は、
NitroModules.applicationContextを遅延して使用し、利用できない場合は明確なエラーをスローしてください。 - Nitro メソッドを同期処理にするのは、低コストのオブジェクト作成、キャッシュ済みメタデータ、純粋な変換など、素早く決定的なローカル処理に限ってください。実装が複雑、負荷が高い、時間経過により失敗する可能性がある、すでにネイティブ非同期である、またはスレッド/プロセス境界をまたぐ場合は、Nitro API が
Promise<T>を返すようにしてください。 - プラットフォームのスレッドモデルに適したネイティブ Promise ヘルパーを使用してください。Swift では、AVFoundation/セッションキューなど DispatchQueue が所有する処理には
Promise.parallel(queue)を優先し、Swift のasync/awaitまたは Task ベースの API を一貫してラップする場合にのみPromise.asyncを使用してください。UIKit/VisionKit のメインスレッドコールバック API では、Nitro のエントリー/コールバック境界でDispatchQueue.main.asyncを直接使用する手動 Promise を優先してください。汎用的なメインスレッドへの切り替えとしてTask { @MainActor in ... }を使用しないでください。 - ネイティブ API が必要とする場合を除き、メイン/UI スレッドを避けてください。メインスレッド上の処理は UI/表示/ビューの変更に限定し、解析、変換、I/O、セッションのネゴシエーション、CPU 処理は、所有するキュー/ディスパッチャー/エグゼキューター、またはネイティブの非同期 API で実行してください。
Promise<T>インスタンスを手動で作成したり、受け渡したりすることは避けてください。Promise.async、Promise.parallel、Promise.resolved、Promise.rejectedを優先してください。これらは構造化された制御フローを通じて、必ず1回だけ完了します。ネイティブの完了/デリゲート/コールバック API をヘルパーで利用できない形でブリッジする場合に限り、手動 Promise が正当化されます。スコープを最小限にし、任意のヘルパーに渡さず、すべての経路で必ず1回だけ resolve または reject されるようにしてください。- コールバック/リスナー API のために手動 Promise を書く前に、焦点を絞った拡張ファイルに汎用の suspend/async アダプターがあるか確認し、なければ追加してください。Android では Google
Task<T>をsuspendCancellableCoroutineを使ってTask+await.ktで一度だけラップし、HybridObject のメソッド内でreturn Promise.async(scope) { task.await() }を使用してください。 - 再利用可能なコールバックから suspend へのアダプターで API を一度だけ表現できる場合、
Hybrid*メソッド内でaddOnSuccessListener/addOnFailureListener/addOnCanceledListenerを手動で配線しないでください。 - 生成されたプロパティの getter または setter の中に、スレッド切り替えを隠さないでください。ネイティブ状態を特定のキュー/スレッド上でしか読み書きできない場合は、代わりに非同期メソッド、リスナー/イベント、または明示的なライフサイクル操作として公開してください。
- 1つの処理内で、
Task、DispatchQueue、コルーチンディスパッチャー、エグゼキューター、JS/Nitro ランタイムを次々に切り替えることは避けてください。各 HybridObject またはセッションごとに、処理を担うネイティブの所有キュー/スレッド/ディスパッチャーを選び、Promise、ライフサイクル、またはコールバックの境界で一度だけ切り替えてください。切り替えの繰り返しは、HybridObject の境界またはライフサイクルハンドルが適切でない兆候です。 - Nitro のライフサイクル、準備完了状態、競合のバグを、
setTimeout、sleep、人為的な遅延、スレッド切り替えの追加、ネイティブメソッドの二重呼び出しで修正しないでください。Promise、リスナー/イベント、設定済み HybridObject の返却、明示的な状態遷移、ネイティブの完了コールバックを使って準備完了状態をモデル化してください。リトライは、外部ハードウェア、OS サービス、リモートサービス、ネットワークの不確実性に対してのみ使用し、回数を制限でき、キャンセル可能で、べき等であることを確認してください。 - ネイティブリソースまたは大規模な割り当てを所有する HybridObject では、メモリプレッシャー下で JS VM が回収できるように
memorySizeを実装してください。 - Nitro Views では、ビューが再利用前にリセットすべき状態を持つ場合、
prepareForRecycleを実装してください。ビュー仕様、実装、登録、リサイクルのワークフロー全体については、spec-hybrid-view.md を参照してください。 - 1つのライブラリ内で、C++ HybridObject と Swift/Kotlin HybridObject を組み合わせてください。OpenCV/フレーム処理/ストレージエンジンなどの共通処理やホットコードには C++ を、プラットフォームサービス、権限、ファイルパス、カメラ/セッション API、OS 統合には Swift/Kotlin を使用してください。
- C++ HybridObject は、Swift/Kotlin で実装された HybridObject を受け取り、生成された C++ の spec API を呼び出せます。例:C++ の
StorageFactoryは Swift/Kotlin のPlatformContextを受け取り、生成された C++ インターフェースを通じてgetTemporaryDirectory()やwriteFile(...)を呼び出せます。C++ からアクセスできるのは公開された spec API のみであり、Swift/Kotlin の非公開フィールドにはアクセスできません。 - その方向で現在の Nitrogen のサポートが確認されていない限り、Swift/Kotlin から C++ 実装の HybridObject を呼び出せることを前提にしないでください。
Nitro テストのルール
- 型の形状を確認するテストより、動作テストを優先してください。Nitrogen はコンパイル時にすでに仕様を適用するため、テストでは実際の機能の動作、入力、設定、失敗時の動作、実行順序に関するケースを対象にしてください。
- 利用可能な場合は、実際の React Native 環境でのエンドツーエンドテストに
react-native-harnessを使用してください。ネイティブ処理の比重が高いライブラリでは、ハードウェアや OS の動作に依存する API サーフェスについて、実機または CI のデバイスファームでのテストを優先してください。 - GitHub Actions では、React Native 経由でのみ公開される API について、単独のネイティブユニットテストより harness の E2E ジョブを優先してください。Kotlin JUnit、XCTest、その他のネイティブ専用テストを追加するのは、React Native 以外で使用可能なネイティブターゲットがパッケージにある場合、または RN harness でその動作をテストできない場合に限ってください。
まず確認 — 何かを始める前に
最初に、ユーザーが何をしたいのかを確認してください。
「一から新しい Nitro Module ライブラリを作成しますか。それとも、既存のライブラリに新しい HybridObjectを追加しますか?」
新しいライブラリを作成する場合 — コマンドを実行する前に、次のすべてを確認してください。
- ライブラリ名 — ライブラリの名前は何にしますか?(例:
react-native-math) packages/フォルダーを持つモノレポ — モノレポ内のpackages/<name>にライブラリを配置しますか?(強く推奨 — デフォルト:はい)- サンプルアプリ — モジュールをテストするサンプルアプリを作成しますか?また、どこに配置しますか?(推奨 — デフォルト:はい。複数のサンプルが必要、または必要になる可能性がある場合は
apps/exampleを使用します。小規模な単一サンプルのリポジトリで、生成された RN 設定の近くに配置する場合に限りexampleを使用します) - ネイティブ言語 — 対象プラットフォームと言語は何ですか?
- iOS:
swift(デフォルト)またはcpp - Android:
kotlin(デフォルト)またはcpp - クロスプラットフォームの C++ のみ:両方とも
cpp
- iOS:
- モジュールの目的 — 適切な spec メソッドを設計できるよう、モジュールの機能を簡潔に説明してください。
5 つすべての質問への回答が得られるまで、ビルド手順のステップ 1 より先に進まないでください。
既存のライブラリに HybridObject を追加する場合 — 次の項目のみを確認してください。
- HybridObject 名 — 新しい HybridObject の名前は何にしますか?(例:
Camera、Crypto) - ネイティブ言語 — iOS:
swiftまたはcpp? Android:kotlinまたはcpp? - 目的 — この HybridObject は何をしますか?
その後、spec-hybrid-object.md(spec を記述)、spec-nitro-json.md(自動リンクのエントリーを追加)、native-nitrogen-codegen.md(nitrogen を再実行)、および該当するネイティブ実装ファイルへ直接進んでください。セットアップ、モノレポ、サンプルアプリの手順はすべて省略してください。
新しい HybridObject がレンダリング可能なビューコンポーネントの場合は、通常の HybridObject の spec リファレンスではなく spec-hybrid-view.md を使用してください。HybridView の spec、ネイティブの view 実装、Android ビューマネージャーの登録、getHostComponent の JS 接続について説明しています。
一般的なビルド手順
# 0. Work on a separate branch; open a draft PR after the first useful commit
# 1. Scaffold
bunx nitrogen@latest init react-native-math
# 2. Run codegen (from package folder after writing spec + nitro.json)
cd packages/react-native-math && bunx nitrogen
# 3. Create example app
bunx @react-native-community/cli@latest init --skip-install MathExample
mkdir -p apps && mv MathExample apps/example
# Alternative: mv MathExample example
# 4. Install and test
cd apps/example && bun add ../../packages/react-native-math
bun add react-native-nitro-modules@<same-version-as-package>
bun example android
bun example ios
以下に、各手順の詳細なリファレンスを示します。
適用する場面
次の場合に、これらのガイドラインを参照してください。
- Nitro フレームワークを使用して新しい React Native ネイティブモジュールを作成する場合
- Nitro の最小プラットフォーム要件を確認する場合
- 実装方針を決める前に、Nitro、React Native、ネイティブツールチェーンの現在の要件を確認する場合
- Nitro ベースのライブラリの公開 API の形状を設計またはレビューする場合
- API を static、インスタンスベース、同期、非同期、コールバックベース、リソースベースのどれにするか判断する場合
- HybridObject の TypeScript spec(
*.nitro.tsファイル)を作成する場合 - Nitrogen のコード生成を実行し、生成されたインターフェースを実装する場合
- Nitro ライブラリ用のモノレポのサンプルアプリをセットアップする場合
- リポジトリのレイアウト、ルートのクリーンさ、共有設定の配置、CI の構成を選択する場合
- Nitro ライブラリのブランチ、ドラフト PR、スカッシュマージのワークフローを確立する場合
- モノレポ構成用の Android Gradle パスを設定する場合
- 自動リンクの失敗や、生成ファイルの欠落をデバッグする場合
- Nitro モジュールパッケージの npm 公開を準備する場合
release-itとbun releaseを使用したワンコマンドリリースをセットアップする場合
優先度順のガイドライン
| 優先度 | カテゴリ | 影響度 | 参照 |
|---|---|---|---|
| 0 | 一般的なパブリック API の構成 | 重大 | api-design |
| 0 | Nitro API の制約 | 重大 | この SKILL.md |
| 1 | リポジトリの構成とワークフロー | 高 | repo-structure-and-workflow.md |
| 2 | Nitrogen のスキャフォールド | 重大 | setup-monorepo-init.md |
| 3 | HybridObject の仕様 | 重大 | spec-hybrid-object.md |
| 3 | Hybrid View コンポーネント (ビューを構築する場合) | 高 | spec-hybrid-view.md |
| 4 | nitro.json の自動リンク | 重大 | spec-nitro-json.md |
| 5 | Nitrogen のコード生成 | 高 | native-nitrogen-codegen.md |
| 6 | C++ の実装 | 高 | native-implement-cpp.md |
| 7 | Kotlin の実装 | 高 | native-implement-kotlin.md |
| 8 | Swift の実装 | 高 | native-implement-swift.md |
| 9 | サンプルアプリのセットアップ (依頼された場合) | 高 | example-app-setup.md |
| 10 | Android Gradle のパス (サンプルアプリの場合) | 高 | example-android-config.md |
| 11 | Metro + インストール + テスト (サンプルアプリの場合) | 高 | example-metro-install.md |
| 12 | npm 公開の準備 | 中 | spec-package-publish.md |
| 13 | release-it による公開 | 中 | release-it-publishing.md |
| 14 | VisionCamera スタイルのライブラリ全体のパターン | 中 | vision-camera-golden-standard.md |
クイックリファレンス
最小限の HybridObject 仕様 (src/specs/Math.nitro.ts)
import type { HybridObject } from 'react-native-nitro-modules'
export interface Math extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
add(a: number, b: number): number
}
最小限のランタイム + 型のエクスポート (src/index.ts)
import { NitroModules } from 'react-native-nitro-modules'
import type { Math } from './specs/Math.nitro'
export const math = NitroModules.createHybridObject<Math>('Math')
export type { Math } from './specs/Math.nitro'
src/index.ts、index.ts、index.js、index.tsx などのパッケージエントリーポイントは、バレルのままにする必要があります。直接再エクスポートや、export const camera = NitroModules.createHybridObject<Camera>('Camera') のような Nitro ルートの一行エクスポートは含められますが、実装ロジック、関数、クラス、フック、コンポーネント、分岐、副作用、ヘルパー定義は含めないでください。実際の定義は用途ごとのファイルに移し、再エクスポートしてください。
Nitro パッケージの JS/TS ソースでは、不在と変更を明示してください。undefined の代わりに void 0 を使わないでください。また、??=、||=、&&= などの論理代入演算子も使わないでください。フォールバック動作が明確に分かるように、明示的な if ブロックまたは直接代入を優先してください。
最小限の nitro.json
{
"$schema": "https://nitro.margelo.com/nitro.schema.json",
"cxxNamespace": ["math"],
"ios": { "iosModuleName": "NitroMath" },
"android": {
"androidNamespace": ["math"],
"androidCxxLibName": "NitroMath"
},
"autolinking": {
"Math": {
"ios": {
"language": "swift",
"implementationClassName": "HybridMath"
},
"android": {
"language": "kotlin",
"implementationClassName": "HybridMath"
}
}
}
}
ルートの package.json スクリプト
{
"scripts": {
"specs": "bun --cwd packages/react-native-math run specs",
"example": "bun --cwd apps/example"
}
}
実行:bun example android、bun example ios、bun specs
参考資料
| ファイル | 説明 |
|---|---|
| repo-structure-and-workflow.md | ルートの構成、README/ドキュメント、packages/apps/config/scripts、CI、ブランチ、ドラフト PR、スカッシュマージのワークフロー |
| setup-monorepo-init.md | スキャフォールドの入力情報を収集し、nitrogen init を実行する方法 |
| spec-hybrid-object.md | *.nitro.ts の仕様を記述し、HybridObjects をエクスポートする方法 |
| spec-hybrid-view.md | Nitro View コンポーネントの構築:HybridView の仕様、ネイティブビュー、getHostComponent、hybridRef、コールバック、再利用 |
| spec-nitro-json.md | nitro.json の全フィールド、自動リンク、名前空間の設定 |
| native-nitrogen-codegen.md | Nitrogen の実行と生成ファイルの検証 |
| native-implement-cpp.md | C++ で HybridObjects を実装する方法 |
| native-implement-kotlin.md | Kotlin(Android)で HybridObjects を実装する方法 |
| native-implement-swift.md | Swift(iOS)で HybridObjects を実装する方法 |
| example-app-setup.md | RN CLI のサンプルアプリの初期化、ワークスペースの接続、バージョンの整合 |
| example-android-config.md | モノレポのパスを修正する settings.gradle と build.gradle の設定 |
| example-metro-install.md | Metro の watchFolders、ライブラリのインストール、App.tsx の使用方法、テストの実行 |
| spec-package-publish.md | package.json の author、files フィールド、npm 公開の準備 |
| release-it-publishing.md | release-it と bun release を使ったワンコマンドリリース |
| vision-camera-golden-standard.md | VisionCamera に着想を得た、パッケージ構成、API のレイヤー化、Nitro オブジェクトのモデリング、公開パターン |
問題 → スキルの対応表
| 問題 | 参照先 | 対応 |
|---|---|---|
| まず公開 API を設計する必要がある | api-design + この SKILL.md | TS/React API を設計してから、Nitro の制約を適用します |
| 最新の一般的な API が必要 | api-design | 判断する前に、公式ドキュメント、リリースノート、ソースリポジトリ、パッケージメタデータ、または llms-full.txt を確認します |
| 推奨されるリポジトリ構成が必要 | repo-structure-and-workflow.md | main、充実した README、packages/、apps/ または example/、必要に応じて Fumadocs、scripts/、config/、および .github/workflows/ を使用します |
| 静的モジュール API とインスタンス API のどちらを選ぶべきか分からない | この SKILL.md | ネイティブ状態、リソース、事前ウォームアップ、ゼロコピーのデータには HybridObjects を優先します |
| 何から始めればよいか分からない | setup-monorepo-init.md | nitrogen init でひな形を作成します |
| 仕様ファイルの構文エラー | spec-hybrid-object.md | *.nitro.ts インターフェースを修正します |
| ネイティブのビューコンポーネントが必要 | spec-hybrid-view.md | HybridView 仕様を記述し、view を実装して、Android マネージャーを登録し、getHostComponent を接続します |
ビューのコールバックプロパティが true として届く/一度も呼び出されない | spec-hybrid-view.md | すべての関数プロパティ(hybridRef を含む)を callback(...) でラップします |
| Nitro View で Android の「view manager not found」エラーが発生する | spec-hybrid-view.md | 生成された Hybrid*ViewManager を createViewManagers に追加します |
| オートリンクが機能しない | spec-nitro-json.md | nitro.json のオートリンクブロックを確認します |
| Nitrogen がファイルを生成しない | native-nitrogen-codegen.md | 仕様ファイルの拡張子を確認し、正しいディレクトリからコマンドを実行します |
| C++ の型が分からない | native-implement-cpp.md | 型リファレンスのリンク先にある正式な例に従います |
| Kotlin のコンパイルエラー | native-implement-kotlin.md | アノテーションと override 修飾子を確認します |
| Swift のコンパイルエラー | native-implement-swift.md | クラスの継承とプロパティのシグネチャを確認します |
| サンプルアプリをビルドできない(Android) | example-android-config.md | Gradle のモノレポパス設定を修正します |
| Metro がライブラリを解決できない | example-metro-install.md | metro.config.js に watchFolders を追加します |
| サンプルとパッケージのバージョンが一致しない | example-app-setup.md | ワークスペース間で react-native のバージョンを揃えます |
| npm パッケージにファイルが含まれていない | spec-package-publish.md | package.json の files フィールドを修正します |
| ワンコマンドでリリースしたい | release-it-publishing.md | bun release 経由で release-it を実行するよう設定します |
| 多機能なライブラリ構成が必要 | vision-camera-golden-standard.md | VisionCamera に着想を得たパッケージ、API、フック、ビュー、Nitro オブジェクトモデルを使用します |