node-bindgen
Easy way to write Node.js module using Rust
README
node-bindgen
Easy way to write native Node.js module using idiomatic Rust
Features
- __Easy:__ Just write idiomatic Rust code, node-bindgen take care of generating Node.js FFI wrapper codes.
- __Safe:__ Node.js arguments are checked automatically based on Rust types.
- __Async:__ Support Async Rust. Async codes are translated into Node.js promises.
- __Class:__ Rust struct can be accessed using Node.js classes.
- __Stream:__ Implement Node.js stream using Rust
- __N-API:__ Use Node.js N-API, which means you don't have to recompile your module.
Compatibility with Node.js version
This project uses the v7 of Node N-API. Please see following compatibility matrix.
Following OS are supported:
Linux
MacOs
Windows
Why node-bindgen?
Writing native node-js requires lots of boilerplate code. Node-bindgen generates external "C" glue code from rust code, including native module registration. node-bindgen make it writing node-js module easy and fun.
Getting started
CLI Installation
Install nj-cli command line, which will be used to generate the native library.
- ```
- cargo install nj-cli
- ```
This is a one time step.
Configuring Cargo.toml
Add two dependencies to your projects' Cargo.toml.
Add node-bindgen as a regular dependency (as below):
- ```
- [dependencies]
- node-bindgen = { version = "5.1" }
- ```
Then add node-bindgen's procedure macro to your build-dependencies as below:
- ```
- [build-dependencies]
- node-bindgen = { version = "5.1", features = ["build"] }
- ```
Then update crate type to cdylib to generate node.js compatible native module:
- ```
- [lib]
- crate-type = ["cdylib"]
- ```
Finally, add build.rs at the top of the project with following content:
- ```
- fn main() {
- node_bindgen::build::configure();
- }
- ```
Example
Here is a function that adds two numbers. Note that you don't need to worry about JS conversion.
- ```rust
- use node_bindgen::derive::node_bindgen;
- /// add two integer
- #[node_bindgen]
- fn sum(first: i32, second: i32) -> i32 {
- first + second
- }
- ```
Building native library
To build node.js library, using nj-cli to build:
- ```
- nj-cli build
- ```
This will generate Node.js module in "./dist" folder.
To build a release version:
- ```
- nj-cli build --release
- ```
Watching ./src for Changes
While developing your native module, you may want to watch for file changes and run a command when a change occurs, for example cargo check or cargo build.
For this, we can use nj-cli watch.
To see all available methods for nj-cli watch, run the following command:
nj-cli watch -- --help
Using in Node.js
Then in the Node.js, rust function can be invoked as normal node.js function:
- ```js
- $ node
- Welcome to Node.js v14.0.0.
- Type ".help" for more information.
- > let addon = require('./dist');
- undefined
- > addon.sum(2,3)
- 5
- >
- ```
Features
Function name or method can be renamed instead of default mapping
- ```rust
- #[node_bindgen(name="multiply")]
- fn mul(first: i32,second: i32) -> i32 {
- first * second
- }
- ```
Rust function mul is re-mapped as multiply
Optional argument
Argument can be skipped if it is marked as optional
- ```rust
- #[node_bindgen]
- fn sum(first: i32, second: Option<i32>) -> i32 {
- first + second.unwrap_or(0)
- }
- ```
Then sum can be invoked as
- ```sum(10)``` or ```sum(10,20)```
- ## Callback
- JS callback are mapped as Rust closure.
- ```rust
#[node_bindgen]
fn hello let msg = format!("argument is: {}", first);
second(msg);
}
- ```
- from 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
});
- ```
- Callback are supported in Async rust as well.
- ## Support for Async Rust
- Async rust function is mapped to Node.js promise.
- ```rust
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
}
- ```
- ```js
let addon = require('./dist');
addon.hello(5).then((val) => {
console.log("future value is %s",val);
});
- ```
- ## Struct serialization
- Structs, including generic structs, can have have the to-JS conversion boilerplate autogenerated.
- Just apply the `node_bindgen` macro to your struct:
- ```rust
#[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
}
}
- ```
- ```js
let addon = require('./dist');
assert.deepStrictEqual(addon.my_json(), {
someName: "John",
aNumber: 1337
});
- ```
- Note that the fields must implement
- `node_bindgen::core::TryIntoJs` themselves.
- Any references must also implement `Clone`.
- Field names will be converted to camelCase.
- ## Enums
- Enums will also have their JS representation autogenerated with the help of `node_bindgen`:
- ```rust
#[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
}
- ```
- ```js
assert.deepStrictEqual(addon.withMessage(), {
withMessage: ["test", 321n]
});
assert.deepStrictEqual(addon.withFields(), {
withFields: {
val: 123n
}
});
assert.deepStrictEqual(addon.withUnit(), "UnitErrorType")
- ```
- Tuple variants will be converted into lists, struct variants converted to objects, and unit variants converted into strings matching the variant's name in PascalCase.
- Generics and references are supported, with the same caveats as for structs.
- ## JavaScript class
- JavaScript class is supported.
- ```rust
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
}
}
- ```
- ```js
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);
- ```
- There are more features in the examples folder.
- ## Windows + Electron Support
- When using node-bindgen with electron on Windows, `nj-build` must
- compile a C++ file, `win_delay_load_hook.cc`, and therefore it is required that the development
- environment has a valid C/C++ compiler.
- > If your machine does not have a valid C/C++ compiler, install [Microsoft VSCode](https://code.visualstudio.com/docs/cpp/config-mingw).
- In the future, this file will be re-written in Rust, removing this dependency.
- Just make sure that you are compiling the rust module using
- ```
$ npx electron-build-env nj-cli build --release
- ```
- otherwise you will get dreaded `A dynamic link library (DLL) initialization routine failed` when importing the rust module in electron
- ## Contributing
- If you'd like to contribute to the project, please read our [Contributing guide](CONTRIBUTING.md).
- ## License
- This project is licensed under the [Apache license](LICENSE-APACHE).