.NET Native AOT and Node.js interop の2026-04-20更新で本当に見るべき点は、「Node.js から C# を呼べるようになった」という話そのものではありません。Microsoft が公式に示したのは、C++ 製 Node.js アドオンや別プロセスの .NET helper による運用負荷を、C# と Native AOT で整理し直せる実装パターンです。C# Dev Kit チームは実際に C++ アドオンを C# と Native AOT に置き換え、特定の Python 依存を外し、CI も簡素化できたと説明しています。 (Microsoft for Developers)
結論だけ先に言うと、このパターンが刺さるのは「TypeScript/Node.js が前面にあり、OS 依存の小さな処理だけをネイティブ化したい」現場です。逆に、動的読み込み、Reflection.Emit、Assembly.LoadFile、Windows の built-in COM、ホットアンロードが前提のコードをそのまま持ち込むと詰まりやすいです。この記事では、.NET Native AOT and Node.js interop の rollout が、現場のワークフローをどう変えるのかを具体的な利用シナリオで整理します。 (Microsoft Learn)
.NET Native AOT and Node.js interop の2026-04-20更新で押さえるべきこと
2026年4月20日に公開された .NET Blog の記事では、TypeScript/Node.js が前面にある C# Dev Kit で、Windows Registry の読み取りのようなプラットフォーム依存処理に使っていた C++ 製 Node.js アドオンを、C# と Native AOT で書き直す手順が紹介されました。もともとの実装は node-gyp でビルドしており、そこで特定の Python 依存がオンボーディングと CI の摩擦になっていた、というのが背景です。 (Microsoft for Developers)
仕組みは意外に素直です。Node.js のネイティブアドオンは、特定のエントリポイントを公開する共有ライブラリで、Node はそれを読み込んで JavaScript のモジュールのように扱います。Node-API は Node.js 本体が提供する ABI 安定な C API で、Node docs でも推奨されています。Native AOT は任意のネイティブエントリポイントを持つ共有ライブラリを生成できるため、この境界にきれいにはまります。要するに「Node.js が .NET を特別扱いする」のではなく、「.NET 側が Node.js から読めるネイティブ共有ライブラリになる」構図です。 (Microsoft for Developers)
公式サンプルは net10.0 と PublishAot を使い、UnmanagedCallersOnly で napi_register_module_v1 を公開し、LibraryImport で Node-API 関数を呼び出しています。dotnet publish の出力は OS ごとの共有ライブラリなので、実運用では .node にリネームして require() で読む前提になります。なお、Node docs では .node の import サポートは early development 扱いなので、保守性重視ならまずは require() ラッパーで始める方が安全です。 (Microsoft for Developers)
.NET Native AOT and Node.js interop の rollout で変わるワークフロー
| 観点 | rollout 前に起きがち | rollout 後の考え方 |
|---|---|---|
| 開発環境 | ネイティブ依存が各開発者の端末に散らばりやすい | publish 環境に寄せて管理する |
| CI/CD | install 時ビルドや環境差異で不安定になりやすい | OS/arch ごとの .node を成果物化する |
| ランタイム | pipe や child process が増えやすい | 小さい処理は in-process 化の候補になる |
| 配布 | 配布先の .NET runtime 前提を置きやすい | self-contained なネイティブライブラリで配る |
この表のポイントは、「依存が消える」のではなく「責務の置き場所が変わる」ことです。C# Dev Kit の事例では特定の Python 依存が外れましたが、Native AOT の publish には Windows なら Visual Studio の C++ workload、Linux なら clang などの前提が必要です。一方で、生成された Native AOT ライブラリは自己完結型で、配布先に .NET runtime を入れなくてよく、Node 側では .node として扱えます。AOT 生成物はターゲット環境ごとで、framework-dependent 配布よりサイズが増えることもあるため、現場では「全員の端末で毎回ビルドする」より「CI で OS/arch 別 artifact を作る」設計に寄せるのが現実的です。 (Microsoft for Developers)
VS Code 拡張や Electron 管理ツールで、OS 固有機能を呼びたい
このパターンが最も分かりやすく効くのは、TypeScript/Node.js の UI や拡張ホストから、OS 固有の小さな機能だけを呼びたいケースです。公式ブログの例も、Node.js から直接は扱えない Windows Registry を、.NET の Microsoft.Win32.Registry 経由で読み出して文字列を返す、という非常に実務的なものです。 (Microsoft for Developers)
現場のワークフローとしては、PowerShell や dotnet の外部プロセスを起動して 1 回だけ値を取ってくる、という設計を減らしやすくなります。TypeScript 側は .node を読み込み、小さな関数を同期的または最小限の境界で呼ぶだけにして、重い業務ロジックは .NET 側に閉じ込める、という分け方がしやすいからです。しかもブログの実装では、Windows 固有のレジストリ例を示しつつ、同じ Native AOT + N-API の考え方自体は Windows、Linux、macOS で使えると説明されています。 (Microsoft for Developers)
ただし、ここで期待すべきは「C++ より圧倒的に速い」ことではありません。Microsoft の事例でも、レジストリ参照や文字列マーシャリングのような処理では C++ 実装と性能は同等レベルとされ、主な利点は contributor experience と CI の簡素化でした。しかも UnmanagedCallersOnly のメソッドで未処理例外を出すと、Node.js のホストプロセスごと落ちます。採用理由はベンチマークの夢ではなく、運用整理と実装言語の統一に置くべきです。 (Microsoft for Developers)
社内 CLI で別プロセスの .NET helper を減らしたい
Power users や admins 向けの社内 CLI では、Node.js 側の UI やコマンド解析は速いものの、実処理は dotnet の別プロセスに投げて pipe や標準入出力でやり取りしている、という構成がよくあります。C# Dev Kit チーム自身も、現時点で substantial な .NET workload を別プロセスで動かしており、Native AOT で共有ライブラリ化できれば、Node.js プロセス内に寄せてシリアライズや process-management のオーバーヘッドを避けられる可能性があると述べています。 (Microsoft for Developers)
ただし、ここは全部を in-process 化すればよいわけではありません。小さく頻繁に呼ぶ処理、たとえば環境検出、設定値取得、軽い検証のようなものは addon 向きです。逆に、障害分離が重要な処理、動的読み込みやコード生成に依存する処理、COM 前提の Windows 管理コード、同一プロセス内での unload/hot swap を求める設計は、引き続き別プロセスのままにした方が安全です。Native AOT にはその種の制約が明記されています。 (Microsoft Learn)
既存の C++ Node.js アドオンを置き換えたい
既存の C++ アドオンが、実際には「文字列を受け取る」「OS API を呼ぶ」「結果を返す」程度の薄い shim なら、C# への置き換えはかなり現実的です。公式ブログでも、数個の関数しか要らないケースでは、薄い N-API ラッパーの方が制御しやすく、余計な依存を増やさずに済むと説明しています。反対に、.NET のクラス全体を公開したい、JavaScript から .NET へのコールバックを多用したい、といった richer scenario なら、より高レベルな interop レイヤーを検討した方がよい、という立て付けです。 (Microsoft for Developers)
実装面では、interop 宣言を DllImport から何となく移植するより、LibraryImport を前提に考えた方が安全です。Microsoft Learn のネイティブ interop ベストプラクティスでも、.NET 7 以降では LibraryImport の利用が推奨されており、ブログ側でも LibraryImport による source-generated marshalling が trimming-compatible だと説明されています。 (Microsoft Learn)
もう一つ大事なのが、既存 .NET ライブラリの AOT 監査です。Native AOT は reflection による型探索、ランタイムでのライブラリ読み込み、実行時コード生成と相性が悪いため、移植前に IsAotCompatible や AOT analyzer を使って依存関係を洗うべきです。再利用ライブラリを持つなら、少なくとも net8.0 以降を含む multi-target を用意し、必要なら VerifyReferenceAotCompatibility も有効化しておくと判断が早くなります。なお、IsAotCompatible の assembly metadata 自体は .NET 10 で導入された点も押さえておくべきです。 (Microsoft Learn)
導入判断の基準
向いている案件と、慎重に見るべき案件
向いているのは、次のような案件です。
- Node.js 側から見せる API が小さく、数個から数十個の関数で足りる案件。公式ブログでも、その程度なら薄い N-API ラッパーの方が制御しやすいとされています。 (Microsoft for Developers)
- 配布先に .NET runtime を事前導入したくない案件。Native AOT の共有ライブラリは自己完結型です。 (Microsoft Learn)
- Node のバージョン変化に振り回されたくない案件。Node-API は ABI 安定な C API で、Node docs でも推奨されています。 (Node.js)
慎重に見るべきなのは、次のような案件です。
- reflection ベースの型探索、
Assembly.LoadFile、Reflection.Emit、ランタイム読み込み、実行時コード生成が前提のコード。Native AOT ではそのまま持ち込みにくいです。 (Microsoft Learn) - Windows の built-in COM 前提のコード。Native AOT の制約に入っています。 (Microsoft Learn)
- 同一プロセス内での unload や hot swap が必要な設計。Native AOT ライブラリのアンロードはサポートされていません。 (Microsoft Learn)
- addon 側の失敗でホストを落とせない案件。未処理例外や呼び出し規則の不一致は、致命的なクラッシュにつながります。 (Microsoft for Developers)
失敗しやすいポイント
見落としがちな運用面
.nodeの命名を甘く見ると詰まります。Node はネイティブアドオンを.nodeとして扱いますが、同じベース名のaddon.jsがあるとrequire('addon')は JavaScript ファイルを優先します。ラッパーとバイナリの命名は最初に整理しておくべきです。 (Node.js)UnmanagedCallersOnlyのメソッドでは、例外を漏らさない設計が必須です。JavaScript 側に返すエラーと、プロセスを落とす障害を分けて考える必要があります。 (Microsoft for Developers)- Windows x86 をまだ捨てられない環境では、呼び出し規則の不一致が特に危険です。Microsoft Learn は、calling convention の不一致がデータ破損や fatal crash を招くと明記しています。 (Microsoft Learn)
- クラッシュ調査の準備を忘れがちです。Native AOT はデバッグ情報を既定で別ファイルに出すため、
.pdb、.dbg、.dSYMを CI の成果物として保管しておかないと、本番障害の解析で困ります。 (Microsoft Learn) - ESM しか使っていない repo でも、
.nodeの import は慎重に見た方が安全です。Node docs 上では addon modules のimportはまだ early development 扱いです。安定運用を優先するなら、まずはrequire()ベースの薄いラッパーに寄せた方が無難です。 (Node.js)
小さく始める導入手順
- まずは一番痛い OS 依存処理を 1 つだけ選びます。レジストリ参照、環境検出、パス解決のように、I/O はあるが境界が小さい機能が向いています。いきなり大規模な helper process 全置き換えを狙わない方が成功率は高いです。 (Microsoft for Developers)
- .NET 側のライブラリを先に AOT 監査します。
IsAotCompatible、AOT analyzer、必要ならVerifyReferenceAotCompatibilityを有効にして、reflection や動的読み込みの問題を build 時に出し切ります。 (Microsoft Learn) - Node-API の境界は小さく保ちます。
UnmanagedCallersOnlyで公開する関数を絞り、P/Invoke 宣言はLibraryImportを第一候補にし、CI で OS/arch 別に publish して.nodeとして梱包します。 (Microsoft for Developers) - 最初のリリースから、例外ハンドリング、デバッグシンボル保管、ターゲット OS ごとの smoke test を入れます。ここまで回して初めて、「さらに in-process 化を広げるか」「やはり helper process を残すか」を判断できます。 (Microsoft for Developers)
まとめ
今回の .NET Native AOT and Node.js interop の rollout が現場にもたらす最大の変化は、Node.js の前面 UI と .NET のネイティブ連携を、C++ アドオンや別プロセスだけに頼らず組み直せるようになったことです。Microsoft の事例でも主眼はベンチマークの勝利ではなく、オンボーディングと CI の整理でした。次にやるべきことは、大規模移行ではなく、いま一番痛い OS 依存処理を 1 つ選び、AOT 互換性を確認し、OS/arch 別の .node を CI で作る小さな検証から始めることです。そこまで回せれば、このパターンが自分たちのワークフローに本当に合うかを、かなり高い精度で判断できます。 (Microsoft for Developers)

コメント