Next.jsのServerComponentからRustのasync fnを使用する
ウェブアプリケーションを実装する際、個人的にも組織としても backend をRustで、frontend をReact/TypeScriptで実装しその間はGraphQLまたはgrpcとなることが多いのだけれども、Next.jsのServerComponentからnapi-rsを使用してRustの関数を直接呼んでやるとかなり楽できるのではないかと思いそういうことができそうか試してみた。
成果物
とりあえずnpm run devで動作は確認できているがbuildする場合はprebuiltされたbinaryへのパスは調整する必要があるかもしれない。
セットアップ
まずはすべてデフォルト設定で OK なのでRSCだけ有効にしてNext.jsのプロジェクトを立ち上げる。その後以下に従いnapi-rsのセットアップを行う。
napiの設定はpackage.jsonに書くようで、今回はひとまず手元の M1 mackbook で動作させることをゴールとしているのでtriplesにaarch64-apple-darwinを指定している。
{
"napi": {
"name": "napi",
"triples": {
"defaults": false,
"additional": [
"aarch64-apple-darwin"
]
}
}
}
Rust 側の関数
今回はサンプルとしてsrc/lib.rsに以下のようなasync fnを用意した。
#[macro_use]
extern crate napi_derive;
#[napi]
pub async fn async_sum(a: i32, b: i32) -> i32 {
tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
a + b
}
index.js の修正
この状態でnpm run buildするとプロジェクトのルートにindex.jsとnapi.darwin-arm64.nodeが吐かれる。が、そのままでは動かないので修正していく。詳細は省略するが以下のようにswitchでplatformとarchを判別するコードが吐かれる。
switch (platform) {
case 'android':
switch (arch) {
case 'arm64':
...
require("xxxxx.node")
...
break
case 'arm':
...
require("xxxxx.node")
...
break
default:
throw new Error(`Unsupported architecture on Android ${arch}`)
}
break
case 'win32':
...
今回はaarch64-apple-darwinだけ考慮するのと、requireを使用してしまうとwebpack側のrequireが使用されてしまうので以下のように__non_webpack_require__を利用してbinaryを読み込むようindex.jsを修正する。
また、このindex.jsは.next/server/appに配置されることを前提として*.nodeのパスを指定する必要がありそう。今回はnpm run devで動けばよし。としているのでbuildする場合はこの辺のパスに注意すること。
const node = __non_webpack_require__("../../../napi.darwin-arm64.node"); // use relative path from .next/server/app
export const { asyncSum } = node;
index.js の利用
上記で変更したindex.jsはsrc/app/page.tsxから以下のように使用する。
import { asyncSum } from "../../";
export default async function Home() {
const result = await asyncSum(1, 2);
return <main>Result: {result}</main>;
}
以下のように表示されたら成功だ。
Result: 3
まとめ
たとえばデスクトップPCだけを想定したアプリや管理画面などはこの方法で実装すると楽ではないかと考え試してみた。実際にproductionで動かすのであればまだ考慮することはありそうだが、ひとまずasync関数をNodeから使用できることが確認できた。今後の選択肢に加えてもう少し検証してみたい。
以上。
Discussion