#nodejs #napi #node #js #module #class

node-bindgen

使用 Rust 编写 nodejs 模块的一种简单方式

16 个稳定版本 (6 个主要版本)

6.0.2 2024 年 6 月 20 日
6.0.1 2023 年 9 月 27 日
5.1.0 2023 年 2 月 1 日
5.0.0 2021 年 7 月 15 日
0.1.2 2020 年 3 月 9 日

#29 in FFI

Download history • Rust 包仓库 394/week @ 2024-04-27 • Rust 包仓库 120/week @ 2024-05-04 • Rust 包仓库 125/week @ 2024-05-11 • Rust 包仓库 309/week @ 2024-05-18 • Rust 包仓库 68/week @ 2024-05-25 • Rust 包仓库 128/week @ 2024-06-01 • Rust 包仓库 115/week @ 2024-06-08 • Rust 包仓库 255/week @ 2024-06-15 • Rust 包仓库 167/week @ 2024-06-22 • Rust 包仓库 31/week @ 2024-06-29 • Rust 包仓库 36/week @ 2024-07-06 • Rust 包仓库 28/week @ 2024-07-13 • Rust 包仓库 39/week @ 2024-07-20 • Rust 包仓库 43/week @ 2024-07-27 • Rust 包仓库 25/week @ 2024-08-03 • Rust 包仓库 68/week @ 2024-08-10 • Rust 包仓库

每月下载量 178
用于 request-window-attention

Apache-2.0

57KB
1K SLoC

node-bindgen

使用 Rust 以惯用方式编写原生 Node.js 模块

特性

  • 简单:只需编写惯用的 Rust 代码,node-bindgen 会处理生成 Node.js FFI 包装代码。
  • 安全:根据 Rust 类型自动检查 Node.js 参数。
  • 异步:支持异步 Rust。异步代码被转换为 Node.js promises。
  • 类:可以使用 Node.js 类访问 Rust 结构体。
  • 流:使用 Rust 实现 Node.js 流。
  • N-API:使用 Node.js N-API,这意味着您不需要重新编译您的模块。

与 Node.js 版本的兼容性

此项目使用 Node N-API 的 v8。请参阅以下 兼容性矩阵

以下操作系统受支持

  • Linux
  • MacOs
  • Windows

为什么选择 node-bindgen?

编写原生 node-js 需要大量的模板代码。node-bindgen 从 rust 代码生成外部的 "C" 粘合代码,包括原生模块注册。node-bindgen 使编写 node-js 模块变得简单而有趣。

入门

CLI 安装

安装 nj-cli 命令行,它将用于生成原生库。

cargo install nj-cli

这是一个一次性步骤。

配置 Cargo.toml

将两个依赖项添加到项目的 Cargo.toml

然后添加 node-bindgen 作为常规依赖项(如下所示)

[dependencies]
node-bindgen = { version = "6.0" }

然后添加 node-bindgen 的过程宏到您的构建依赖项中(如下所示)

[build-dependencies]
node-bindgen = { version = "6.0", default-features = false, features = ["build"] }

然后将crate类型更新为cdylib以生成兼容node.js的本地模块

[lib]
crate-type = ["cdylib"]

最后,在项目顶部添加build.rs,内容如下

fn main() {
    node_bindgen::build::configure();
}

示例

这是一个添加两个数字的函数。请注意,您不需要担心JS转换。


use node_bindgen::derive::node_bindgen;

/// add two integer
#[node_bindgen]
fn sum(first: i32, second: i32) -> i32 {
    first + second
}

构建本地库

要构建node.js库,使用nj-cli进行构建

nj-cli build

这将生成位于"./dist"文件夹中的Node.js模块。

构建发布版本

nj-cli build --release

监视./src中的更改

在开发您的本地模块时,您可能希望监视文件更改并在更改发生时运行命令,例如 cargo checkcargo build

为此,我们可以使用nj-cli watch

nj-cli watch(如果不存在)安装并传递参数给cargo watch。默认情况下,nj-cli watch将运行cargo check以检查您的./src文件。

要查看nj-cli watch的所有可用方法,请运行以下命令

nj-cli watch-- --help

在Node.js中使用

然后在Node.js中,rust函数可以像正常的node.js函数一样被调用

$ node
Welcome to Node.js v18.18.0.
Type ".help" for more information.
> let addon = require('./dist');
undefined
> addon.sum(2,3)
5
>

特性

函数名或方法可以重命名为默认映射

#[node_bindgen(name="multiply")]
fn mul(first: i32,second: i32) -> i32 {
    first * second
}

Rust函数mul重命名为multiply

可选参数

如果参数被标记为可选,则可以跳过参数

#[node_bindgen]
fn sum(first: i32, second: Option<i32>) -> i32 {
    first + second.unwrap_or(0)
}

然后sum可以调用为sum(10)sum(10,20)

回调

JS回调映射为Rust闭包。

#[node_bindgen]
fn hello<F: Fn(String)>(first: f64, second: F) {

    let msg = format!("argument is: {}", first);

    second(msg);
}

从node

let addon = require('./dist');

addon.hello(2,function(msg){
  assert.equal(msg,"argument is: 2");
  console.log(msg);  // print out argument is 2
});

异步Rust也支持回调。

异步Rust支持

异步Rust函数映射为Node.js promise。


use std::time::Duration;
use flv_future_aio::time::sleep;
use node_bindgen::derive::node_bindgen;


#[node_bindgen]
async fn hello(arg: f64) -> f64 {
    println!("sleeping");
    sleep(Duration::from_secs(1)).await;
    println!("woke and adding 10.0");
    arg + 10.0
}
let addon = require('./dist');

addon.hello(5).then((val) => {
  console.log("future value is %s",val);
});

结构体序列化

结构体,包括泛型结构体,可以自动生成到-JS转换的模板。只需将node_bindgen宏应用于您的结构体

#[node_bindgen]
struct MyJson {
    some_name: String,
    a_number: i64
}

#[node_bindgen]
fn my_json() -> MyJson {
    MyJson {
        some_name: "John".to_owned(),
        a_number: 1337
    }
}
let addon = require('./dist');
assert.deepStrictEqual(addon.my_json(), {
    someName: "John",
    aNumber: 1337
});

请注意,字段必须自行实现node_bindgen::core::TryIntoJs。任何引用也必须实现Clone。字段名称将转换为驼峰命名。

枚举

枚举也将使用node_bindgen的帮助自动生成其JS表示。

#[node_bindgen]
enum ErrorType {
    WithMessage(String, usize),
    WithFields {
        val: usize
    },
    UnitErrorType
}

#[node_bindgen]
fn with_message() -> ErrorType {
    ErrorType::WithMessage("test".to_owned(), 321)
}

#[node_bindgen]
fn with_fields() -> ErrorType {
    ErrorType::WithFields {
        val: 123
    }
}

#[node_bindgen]
fn with_unit() -> ErrorType {
    ErrorType::UnitErrorType
}
assert.deepStrictEqual(addon.withMessage(), {
    withMessage: ["test", 321n]
});
assert.deepStrictEqual(addon.withFields(), {
    withFields: {
        val: 123n
    }
});
assert.deepStrictEqual(addon.withUnit(), "UnitErrorType")

元组变体将被转换为列表,结构体变体将转换为对象,单元变体将转换为与变体名称匹配的大驼峰字符串。支持泛型和引用,与结构体的注意事项相同。

JavaScript类

支持JavaScript类。


struct MyClass {
    val: f64,
}


#[node_bindgen]
impl MyClass {

    #[node_bindgen(constructor)]
    fn new(val: f64) -> Self {
        Self { val }
    }

    #[node_bindgen]
    fn plus_one(&self) -> f64 {
        self.val + 1.0
    }

    #[node_bindgen(getter)]
    fn value(&self) -> f64 {
        self.val
    }
}
let addon = require('./dist');
const assert = require('assert');

let obj = new addon.MyObject(10);
assert.equal(obj.value,10,"verify value works");
assert.equal(obj.plusOne(),11);

示例文件夹中有更多功能。

Windows + Electron支持

在Windows上使用node-bindgen与electron时,nj-build必须编译一个C++文件,win_delay_load_hook.cc,因此开发环境必须有一个有效的C/C++编译器。

如果你的机器没有有效的C/C++编译器,请安装Microsoft VSCode

将来,这个文件将用Rust重新编写,移除这个依赖。

只需确保你正在使用以下方式编译rust模块:

$ npx electron-build-env nj-cli build --release

否则,在将rust模块导入electron时,你会得到令人讨厌的错误:动态链接库(DLL)初始化例程失败

准备npm包

使用node-bindgen生成的Node模块可以直接在任何Node JS项目中使用,只需将index.node复制进去即可。但如果是直接访问模块,IDE将不会突出显示可用的函数、类等。通常,这并不方便,并且当Node模块的公共API更改时,潜在的错误风险会更高。

要创建一个具有TypeScript类型定义和所有必要JavaScript包装的完整npm包,可以使用tslink crate。

tslink crate生成描述npm模块的文件*.d.ts*.jspackage.json。这样的包可以以最小的努力集成到最终项目中。

此外,由于tslink生成了TypeScript类型定义,任何对本地Node模块(index.node)的更改都将由TypeScript编译器突出显示,从而大大降低与更改的API或公共数据类型相关的错误风险。

例如,

#[macro_use] extern crate tslink;
use tslink::tslink;
use node_bindgen::derive::node_bindgen;

struct MyScruct {
    inc: i32,
}

#[tslink(class)]
#[node_bindgen]
impl MyScruct {
    #[tslink(constructor)]
    #[node_bindgen(constructor)]
    pub fn new(inc: i32) -> Self {
        Self { inc }
    }

    #[tslink(snake_case_naming)]
    #[node_bindgen]
    fn inc_my_number(&self, a: i32) -> i32 {
        a + self.inc
    }
}

将表示为(*.d.ts)为

export declare class MyStruct {
    constructor(inc: number);
    incMyNumber(a: number): number;
}

请注意,调用#[tslink]应始终在调用#[node_bindgen]之前。

此外,请注意,node-bindgen默认将方法名称转换为snake case。您应该使用#[tslink)来考虑这一点(更多信息请参阅crate页面)。

tslink需要在项目根目录的Cargo.toml[tslink]部分)中进行配置。配置应包括到本地Node模块的有效路径。默认情况下,node-bindgen在您的root./dist文件夹中创建index.node

文件:./Cargo.toml(在项目的root中)

[project]
...
[lib]
...
[tslink]
node = "./dist/index.node"

tslinknode-bindgen的完整使用示例在这里:此处

查看更多关于 tslink 的API文档,请访问crate页面

注意。node-bindgen的开发者不对tslink crate的工作正确性负责。所有与tslink相关的可能问题和功能请求应联系tslink的开发者。

贡献

如果您想为该项目做出贡献,请阅读我们的贡献指南

许可

本项目采用Apache许可证授权。

依赖

~0–12MB
~146K SLoC