10 个版本

0.4.4 2024年6月20日
0.4.3 2023年9月27日
0.4.2 2022年3月8日
0.4.1 2021年8月31日
0.1.2 2020年2月7日

#class 中排名 6

Download history 91/week @ 2024-04-25 111/week @ 2024-05-02 63/week @ 2024-05-09 165/week @ 2024-05-16 197/week @ 2024-05-23 118/week @ 2024-05-30 133/week @ 2024-06-06 42/week @ 2024-06-13 314/week @ 2024-06-20 161/week @ 2024-06-27 102/week @ 2024-07-04 46/week @ 2024-07-11 117/week @ 2024-07-18 197/week @ 2024-07-25 220/week @ 2024-08-01 112/week @ 2024-08-08

每月下载 651

Apache-2.0

14KB
350

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.js

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")

元组变体会被转换为列表,结构体变体会被转换为对象,而单元变体会被转换为与变体名称匹配的 PascalCase 字符串。泛型和引用被支持,与结构体的要求相同。

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

否则,当在 electron 中导入 rust 模块时,您将得到可怕的 A dynamic link library (DLL) initialization routine failed 错误

准备 npm 软件包

使用 node-bindgen 生成的节点模块可以直接在任何 Node.js 项目中使用,只需将 index.node 复制到其中。但是,如果直接访问模块,IDE 将不会突出显示可用的函数、类等。通常,这并不方便,并且一旦节点模块的公共 API 发生更改,就会增加潜在错误的风险。

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

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

此外,由于 tslink 生成 TypeScript 类型定义,任何对本地节点模块(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 默认将方法名称转换为蛇形命名。您应该使用 #[tslink(snake_case_naming)] 来考虑这一点(更多内容请参见 crate 页面)。

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

文件:./Cargo.toml(项目根目录中)

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

使用 tslinknode-bindgen 的完整示例在此

有关 tslink 的更多 API 文档,请参见 crate 页面

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

贡献

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

许可证

本项目采用Apache许可证。

依赖项

约2.4–3.5MB
约60K SLoC