.NET Native AOT and Node.js interop管理者向けチェックリスト|導入・設定・周知の確認項目

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 / RIDwin-x64、win-arm64、linux-x64、linux-arm64、osx-x64、osx-arm64 など、実際に配る単位を固定する配布対象ごとの build matrix を1枚で説明できる
Build runnertarget 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)

項目推奨設定・確認見落としやすい点
PublishAotCLI ではなく project file に置く日常 build の analyzer 連動を逃しにくい
Native AOT prerequisitesWindows は 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/CDOS / 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 依存を棚卸しする置き換え対象が一覧化されている
2API 面の小さい addon を 1件だけ pilot 候補に選ぶ成功条件と切り戻し条件が決まっている
3PublishAot、必要な analyzer、publish job を追加するtarget RID ごとに artifact が出る
4target OS 上で publish し、.node として配置するCI で per-RID artifact が取得できる
5Node 側で require() と代表関数の smoke test を回すbuild success と load success の両方が確認できる
6一部チーム / 一部 package だけで pilot 展開するcrash と support 問い合わせが安定している
7onboarding / CI docs から不要依存を削る旧前提と新前提の混在がなくなる
8debug 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つです。

  1. 対象 OS / RID と build runner を棚卸しし、matrix を固定する。
  2. API 面が小さい addon を1件選び、PublishAot と per-RID publish を追加する。
  3. 周知文に「削除する依存」「残る前提」「切り戻し条件」を入れて pilot 展開する。

これを先にやっておくと、.NET Native AOT and Node.js interop の導入は「面白そうな技術検証」ではなく、「事故を減らしながら進める運用改善」に変わります。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次