2026年4月20日に Microsoft が公開した Writing Node.js addons with .NET Native AOT の実務的な答えは明快です。.NET Native AOT and Node.js interop は、C++ 製 addon を前提にしてきた運用を見直す有力候補になりました。特に、公開する関数が少ない Node.js addon なら、C# と Native AOT で置き換えつつ、Node-API の安定 ABI を活かして運用を整理しやすいからです。 (Microsoft for Developers)
ただし、管理者が最初に確認すべきなのは「作れるか」ではありません。確認すべきは、どの OS / Runtime Identifier(RID)を誰がどこで build するか、AOT 警告をいつ潰すか、Python / node-gyp を減らす代わりに何が残るか、の3点です。この記事では、導入前確認、設定差分、周知項目、展開順序を、そのまま運用に流し込める checklist 形式で整理します。 (Microsoft Learn)
.NET Native AOT and Node.js interop の最新更新で変わったこと
今回の更新で押さえるべき本質は、Node.js addon が napi_register_module_v1 を export する shared library であり、C# でも [UnmanagedCallersOnly] と Native AOT を使ってその入口を作れる、と Microsoft が具体例つきで示したことです。サンプルでは dotnet publish で OS 別の native library を出力し、最終的に .node 拡張子へ rename して TypeScript から require() しています。 (Microsoft for Developers)
もう一つ重要なのは、これは「完全に managed-only になる」話ではないことです。Microsoft の事例では特定の Python 依存を減らせましたが、Windows の Native AOT では Visual Studio 2022 の C++ workload、Linux では clang と zlib 系の開発パッケージ、macOS では Xcode Command Line Tools が前提です。Native AOT は target runtime ごとに publish する方式なので、運用の主戦場はむしろ build matrix の設計に移ります。 (Microsoft for Developers)
まず決めるべき導入パターン
方式選定で迷ったら、最初に見るべきは「公開する API 面が小さいか、大きいか」です。今回の Microsoft の実装は、必要な関数が少数だったため、薄い N-API wrapper を自前で持つ方が依存を増やさず制御しやすいと判断しています。一方で node-api-dotnet は、TypeScript 型定義、async/promises、streams などを含む高水準な interop を提供し、Native AOT も扱えますが、公開リポジトリ上では Public Preview とされています。 (Microsoft for Developers)
| 方式 | 向いているケース | 管理者が重視する判断 |
|---|---|---|
| 直接 N-API + .NET Native AOT | 公開する関数が少ない、OS 固有 API を叩く、小さく始めたい | 依存は減らしやすいが、unsafe と P/Invoke を含むためレビュー基準を先に決める |
| node-api-dotnet + Native AOT | .NET の型公開、callback、async、TypeScript 型定義まで欲しい | 開発体験は高いが、Preview と AOT 制約を前提に pilot する |
| 既存 C++ addon 継続 | すでに安定、ネイティブ資産が大きい、移行効果が小さい | 移行を急がず、補助的 module から AOT 化する |
node-api-dotnet を使う場合も、AOT 環境では動的ロードや reflection、generic export などに制約があります。便利な抽象化があるからといって、AOT 側の検証が軽くなるわけではありません。 (Microsoft GitHub)
導入前チェックリスト
導入前の差し戻しは、機能不足より build 体制の未整理で起こります。Native AOT は cross-OS compile をサポートせず、Linux では build した環境の世代が実行環境の下限にも影響します。コード変更より先に、配布対象と build 実行場所を確定させるのが安全です。 (Microsoft Learn)
| 確認項目 | 何を見るか | 完了の目安 |
|---|---|---|
| 対象 OS / RID | win-x64、win-arm64、linux-x64、linux-arm64、osx-x64、osx-arm64 など、実際に配る単位を固定する | 配布対象ごとの build matrix を1枚で説明できる |
| Build runner | target OS と一致する runner / VM / container を用意する | すべての target OS に publish job がある |
| Linux 基準環境 | 最古のサポート distro / base image を決める | build image がタグ固定されている |
| Node 境界 | Node-API の範囲だけで完結するか、外部 native library を持ち込むかを決める | external ABI review の有無が明文化されている |
| 初回移行候補 | 少数 API、同期 utility、platform-specific 処理を優先する | 1 addon だけで pilot を始められる |
Node-API の ABI 安定性は大きな利点ですが、その保証は Node-API 自体の境界に限られます。外部 native library や Node の C++ / V8 / libuv API を混ぜると、互換性検証の負担は一気に増えます。最初の移行候補は「小さい」「単機能」「境界が狭い」を徹底した方が失敗しません。 (Node.js)
設定チェックリスト
設定差分の中心は project file です。公式ブログの最小構成は net10.0、PublishAot=true、AllowUnsafeBlocks=true です。さらに Microsoft Learn では、PublishAot を project file に置くことで publish 時の AOT 化だけでなく build/editing 時の dynamic code-usage analysis も有効になると説明しています。再利用ライブラリを整備するなら IsAotCompatible と、必要に応じて VerifyReferenceAotCompatibility まで含めて判断すると、後から dependency 問題が噴きにくくなります。 (Microsoft for Developers)
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<PublishAot>true</PublishAot>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
この最小構成は公式ブログのサンプルに基づいており、AllowUnsafeBlocks は function pointer と fixed buffer を扱うために必要です。 (Microsoft for Developers)
<PropertyGroup>
<IsAotCompatible>true</IsAotCompatible>
<VerifyReferenceAotCompatibility>true</VerifyReferenceAotCompatibility>
</PropertyGroup>
IsAotCompatible は trim / single-file / AOT analyzers を有効にする入口として機能します。ただし VerifyReferenceAotCompatibility は .NET 10 時代の metadata を前提にした確認なので、古い package が多い環境では warning が多めに出ることも見込んでください。 (Microsoft Learn)
| 項目 | 推奨設定・確認 | 見落としやすい点 |
|---|---|---|
PublishAot | CLI ではなく project file に置く | 日常 build の analyzer 連動を逃しにくい |
| Native AOT prerequisites | Windows は VS 2022 C++ workload、Ubuntu は clang と zlib1g-dev、macOS は Xcode CLT を確認する | Python が減っても native toolchain は残る |
| Publish 単位 | dotnet publish -r <RID> -c Release を target ごとに実行する | build 成功と load 成功は別 |
| Export 方法 | top-level published assembly に [UnmanagedCallersOnly(EntryPoint=...)] を置く | project reference / NuGet 内の method は自動 export されない |
| パッケージング | 出力 library を .node 名で per-RID folder に配置する | Node 側の require() パスも変わる |
| ライブラリ検証 | 再利用ライブラリは IsAotCompatible、必要なら VerifyReferenceAotCompatibility を有効にする | 旧 package 群では warning noise が出やすい |
| Debug 情報 | .pdb / .dbg / .dSYM の保管方針を決める | crash dump 解析で必要になる |
| ライフサイクル | hot unload を前提にしない | FreeLibrary / dlclose は未対応 |
配布面の利点は明確で、AOT で publish した native library は self-contained で、target machine に .NET runtime を別途入れなくてよい一方、shared library のみが正式サポートで、unload は非対応です。つまり「配りやすくはなるが、差し替えや reload 戦略は別途設計が必要」という理解が正解です。 (Microsoft Learn)
コードレビュー基準も1枚決めておくと運用が安定します。Microsoft の native interop best practices は、.NET 7+ では LibraryImport を優先し、native C signature と一致する宣言、delegate より function pointer + UnmanagedCallersOnly、必要に応じて ArrayPool<T> を使う方針を勧めています。今回のブログサンプルも、その方向に沿っています。 (Microsoft Learn)
周知チェックリスト
周知で失敗しやすいのは、削れる依存関係だけを強調して、残る前提を書かないことです。Microsoft の事例では特定 Python version 依存が消え、CI も簡素化しましたが、公式 docs 上は Native AOT build に OS ごとの native toolchain が要ります。案内文は「減るもの」と「残るもの」を同時に書くのが基本です。 (Microsoft for Developers)
| 周知項目 | 必ず伝える内容 | 誤解しやすい点 |
|---|---|---|
| 開発端末の前提 | 移行済み addon では Python / node-gyp を減らせる可能性がある一方、.NET SDK と native toolchain は必要 | 「全員の Python が即不要」と断定しない |
| CI/CD | OS / RID ごとに publish が増え、runner も target OS 対応が必要 | 1本の job で全 platform を賄えるとは限らない |
| 配布物 | .node を per-RID 配布し、パス規約を固定する | 1ファイル universal 配布だと誤解しやすい |
| 品質保証 | publish 時 warning 0 と Node require() smoke test を必須化する | compile success だけで出荷しない |
| 既知の制約 | unhandled exception で host process crash、unload 非対応、AOT 制限あり | Node 側で全部 recover できるわけではない |
| 切り戻し | pilot 完了までは旧 addon 経路を残す | 移行初日に旧経路を消さない |
そのまま使える周知文例
件名: Node.js addon のビルド方式変更(.NET Native AOT 対応)
対象:
- 対象リポジトリ:
- 影響を受ける OS / RID:
- 影響を受ける開発者 / 運用者:
変更点:
- 変更後の addon は .NET Native AOT で publish した native library を .node として配布します。
- 移行済み module では node-gyp / 特定 Python 依存を減らせる可能性があります。
- ただし、build には .NET SDK と OS ごとの native toolchain が必要です。
検証ルール:
- 各 RID で dotnet publish を実行
- Node 側で require() と代表関数の smoke test を実施
- AOT / trimming warning が残る場合は本番展開しない
切り戻し条件:
- target RID の load 失敗
- warning 未解消
- crash dump 解析に必要な symbol 未整備
展開順序チェックリスト
展開は big-bang より parallel rollout が安全です。PublishAot は build/editing 時の analysis を有効にし、publish 時は project と dependency 全体を検査します。release 直前にまとめて切り替えるより、早い段階で per-RID publish job を足して warning を拾う方が事故は減ります。 (Microsoft Learn)
| 順番 | 実施内容 | 先に進む条件 |
|---|---|---|
| 1 | 現行 addon、node-gyp、Python 依存を棚卸しする | 置き換え対象が一覧化されている |
| 2 | API 面の小さい addon を 1件だけ pilot 候補に選ぶ | 成功条件と切り戻し条件が決まっている |
| 3 | PublishAot、必要な analyzer、publish job を追加する | target RID ごとに artifact が出る |
| 4 | target OS 上で publish し、.node として配置する | CI で per-RID artifact が取得できる |
| 5 | Node 側で require() と代表関数の smoke test を回す | build success と load success の両方が確認できる |
| 6 | 一部チーム / 一部 package だけで pilot 展開する | crash と support 問い合わせが安定している |
| 7 | onboarding / CI docs から不要依存を削る | 旧前提と新前提の混在がなくなる |
| 8 | debug symbol 保管、運用 runbook、障害時手順を更新する | 本番移行後の調査導線ができている |
失敗しやすいポイント
「Python が外れた = ネイティブ準備も不要」と考える
これは誤りです。Microsoft の事例では特定 Python version 依存を減らせましたが、同じ記事で Node.js、C++ tooling、.NET SDK を前提にしており、公式 docs でも OS ごとの Native AOT prerequisites が明記されています。社内周知では「削除する依存」と「残る toolchain」を必ず分けて書いてください。 (Microsoft for Developers)
「1つの成果物を全 OS へ配れる」と考える
Native AOT は特定 runtime environment 向けに publish する方式で、cross-OS compile はサポートされません。さらに Linux は build した環境の世代が実行可能な下限にも関わるため、base image を曖昧にしたまま進めると本番で詰まります。 (Microsoft Learn)
「publish は release 直前でまとめてやればよい」と考える
これも危険です。PublishAot は publish だけの設定ではなく、build/editing 時の analysis にも効きます。しかも publish 時には project と dependency 全体が検査されるので、release freeze に入ってから初めて AOT warning を見る進め方は遅すぎます。 (Microsoft Learn)
「参照プロジェクトの method も自動で export される」と考える
Native AOT の native export は、UnmanagedCallersOnly と non-empty な EntryPoint を持つ method が対象ですが、考慮されるのは publish される assembly 内の method です。project reference や NuGet package 内に入口を置いても、そのままでは export されません。入口は top-level addon assembly に置く方が安全です。 (Microsoft Learn)
「例外は Node.js 側で拾えばよい」と考える
ブログのサンプルが明示している通り、[UnmanagedCallersOnly] method で unhandled exception が起きると host process が crash します。exported method の境界では、例外を catch して JavaScript 側の Error に変換する設計を最低条件にしてください。 (Microsoft for Developers)
「DLL を差し替えれば unload / hot swap できる」と考える
Native AOT の shared library は unload が正式サポートされていません。運用で差し替えを想定するなら、library の hot swap ではなく、versioned deployment か process restart ベースで設計した方が現実的です。 (Microsoft Learn)
次にやること
最初の1週間でやるべきことは3つです。
- 対象 OS / RID と build runner を棚卸しし、matrix を固定する。
- API 面が小さい addon を1件選び、
PublishAotと per-RID publish を追加する。 - 周知文に「削除する依存」「残る前提」「切り戻し条件」を入れて pilot 展開する。
これを先にやっておくと、.NET Native AOT and Node.js interop の導入は「面白そうな技術検証」ではなく、「事故を減らしながら進める運用改善」に変わります。

コメント