それが簡単にできちゃう、napi-rsなら。
napi-rs/napi-rs: A framework for building compiled Node.js add-ons in Rust via Node-API https://github.com/napi-rs/napi-rs
そしてこれを使って、oxlint
はJSで書かれたESLintプラグインを実行してる。
Node-API
Node-API | Node.js v25.0.0 Documentation https://nodejs.org/api/n-api.html
Node.jsのコードから、C/C++やらで書いたコードを呼べるやつ。
で、Rustでもそれ用のコードをいい感じに書けるようになるのが、napi
, napi_derive
crateと、それをよしなにビルドしてくれる@napi-rs/cli
というわけ。
JSからRust
これは本当に簡単で、こういう関数を用意するだけ。
use napi_derive::napi;
#[napi]
pub fn hello(name: String) -> String {
format!("🦀 < `Hello, {name}!`")
}
そしてこれをビルドすると、
- 指定したプラットフォーム向けにコンパイルされた
.node
- それを読み込んで関数を
export
する.js
と.d.ts
というファイルたちができるので、それを使う。
import { hello } from "./index.js";
console.log(hello("World"));
ちなみに、WASM向けのビルドにも対応してるので、ブラウザでも動かせるコードも出力できる。
WebAssembly – NAPI-RS https://napi.rs/docs/concepts/webassembly
すばらしい。
RustからJS
こっちが本題。
RustからJSを呼ぼうとすると、最初に思いつくのは、
- RustでメインのCLIを用意
- 子プロセスとしてNode.jsをspawn
- プロセス間でメッセージング
ってなりがち。
しかし、ついさっき書いたように、今やRustで書いたCLIは、JSで動かせることがわかってる。
なら、JSで書いた関数を、そのままコールバック的に渡せば・・・?っていう話。
実行イメージはこう。
import { helloWithCallback } from "./index.js";
await helloWithCallback((arg) =>
arg
.replace("🦀", "👻")
.replace("{{template}}", "THIS IS JS~")
.toUpperCase(),
);
そしてそれを実現するコードがこれ。
use napi::bindgen_prelude::{Function, Result};
use napi_derive::napi;
#[napi]
pub fn hello_with_callback(callback: Function<String, String>) -> Result<String> {
let result = callback.call("🦀 < `Hello, {{template}}!`".to_string())?;
println!("{result}");
Ok(result)
}
特筆すべき点としては、
- 末端で実行されるのはNode.jsだが、本体はRustで書ける
- そのRustのコードでは、先と同様
#[napi]
をつける - 引数で
Function
型のコールバックを受け取って、それをよきときに呼ぶ- RustからJSに引数も渡せる
というあたり。
これが最もシンプルな同期で呼ぶパターンで、ここに追加の要件としてで、
- 引数を複数渡して呼びたい
- Rust側で定義した別スレッドで呼びたい
Promise
を返すJSを呼びたい- ノンブロッキングで呼びたい
などなどの要望が出てくるので、それに応じて使うAPIを変えていくことになる・・・らしい。
Functions and Callbacks in NAPI-RS – NAPI-RS https://napi.rs/blog/function-and-callbacks
いや〜、すごいっす。
leaysgur/js-from-rust-example: Call JS function callback from Rust! https://github.com/leaysgur/js-from-rust-example
公式のサンプル集も見つけた。
napi-rs/callback-example: Example for the blog post https://napi.rs/blog/function-and-callbacks https://github.com/napi-rs/callback-example