6个版本

0.3.0 2023年9月27日
0.2.3 2021年7月15日
0.2.2 2021年3月15日
0.2.1 2020年10月15日
0.1.0 2020年2月4日

#219 in 构建工具

Download history 155/week @ 2024-03-11 348/week @ 2024-03-18 183/week @ 2024-03-25 279/week @ 2024-04-01 236/week @ 2024-04-08 143/week @ 2024-04-15 98/week @ 2024-04-22 385/week @ 2024-04-29 143/week @ 2024-05-06 95/week @ 2024-05-13 291/week @ 2024-05-20 67/week @ 2024-05-27 123/week @ 2024-06-03 121/week @ 2024-06-10 119/week @ 2024-06-17 108/week @ 2024-06-24

每月478次下载
用于 2 个crate(通过 node-bindgen

Apache-2.0

9KB
194

node-bindgen

使用惯用Rust编写原生Node.js模块的简单方法

特性

  • 简单:只需编写惯用Rust代码,node-bindgen将负责生成Node.js FFI包装器代码。
  • 安全:根据Rust类型自动检查Node.js参数。
  • 异步:支持异步Rust。异步代码将转换为Node.js承诺。
  • 类:可以使用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。字段名将被转换为camelCase。

枚举

枚举也将使用 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 模块时,您将遇到令人厌恶的 动态链接库(DLL)初始化例程失败

准备 npm 软件包

使用 node-bindgen 生成的 Node 模块可以直接在任何 Node JS 项目中使用,只需将 index.node 复制进去即可。但是,如果直接访问模块,IDE 将不会突出显示可用的函数、类等。通常,这不太方便,并且当公共 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 默认将方法名称转换为蛇形命名。您应使用 #[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–9MB
~85K SLoC