baf0db2f19
This is the experimental feature that provides a all new design `Function` API in NAPI-RS. The main motivation to design a new `Function` instead of improving the old `JsFunction` is there are some fundamental problems in the old `JsFunction` API. 1. The old `JsFunction` doesn't contains a lifetime, which means you can send it to a outlive scope and call it later, which would cause a `napi_invalid_arg` error in the underlying `napi_call_function` API. This design issue also happens in the `JsObject`/`JsBuffer` and all other non-primitive types APIs. 2. It's not possible to generate correct TypeScript type definitions for the old `JsFunction` API. 3. The arguments of the old `JsFunction` API must be the same type, which is makes it really unfriendly to use. Expect that, we also have a high level and modern Function exists in the `NAPI-RS` which is the Generic type style `Fn(Args) -> Return`. This API is pretty nice to use, and more importantly, it's sound. But there are some limitations to use it, like create a reference to it to outlive the scope of the JavaScript function under the hood. And you can't use it create a `ThreadsafeFunction`. So there is the new design `Function` API, there are some core features: 1. It's a generic typed API, which means you can get more accurate Rust type information and generate correct TypeScript type definitions. 2. It's sound, which means you can't send it to a outlive scope and call it later, if you want do that, you must create a reference to it or create a `ThreadsafeFunction`. 3. It's friendly to use, you can use different types of arguments and return types, and it can covert the Rust tuple type to JavaScript arguments automatically. Here is some examples to show how to use it: ```rust use napi::bindgen_prelude::*; use napi_derive::napi; #[napi] pub fn callback_javascript_callback(add_one: Function<u32, u32>) -> Result<u32> { add_one.call(100) } ``` ⬇️⬇️⬇️ ```typescript export function callbackJavascriptCallback(add_one: (arg0: number) => number): number; ``` ⬇️⬇️⬇️ ```javascript callbackJavascriptCallback((arg0) => arg0 + 1); // 101 ``` If you define a tuple as the `Function` arguments, it will be converted to JavaScript arguments automatically. ```rust use napi::bindgen_prelude::*; use napi_derive::napi; #[napi] pub fn callback_javascript_callback(add: Function<(u32, u32), u32>) -> Result<u32> { add.call((100, 200)) } ``` ⬇️⬇️⬇️ ```typescript export function callbackJavascriptCallback(add: (arg0: number, arg1: number) => number): number; ``` ⬇️⬇️⬇️ ```javascript callbackJavascriptCallback((arg0, arg1) => arg0 + arg1); // 300 ``` If you are trying to send it into a outlive scope, you will get a compile error. For example, if you are trying to send a callback to `git2-rs` `RemoteCallbacks::credentials` API: ```rust use napi::bindgen_prelude::*; use napi_derive::napi; #[napi] pub fn build_credential(on_credential: Function<(String, Option<String>, CredentialType), ClassInstance<Cred>>) -> Result<()> { let mut callbacks = git2::RemoteCallbacks::new(); callbacks.credentials(move |url, username_from_url, allowed_types| { on_credential.call((url.to_string(), username_from_url.map(|s| s.to_string()), allowed_types.into())) .map(...) .map_error(...) }); } ``` You will get a compile error: ```text error[E0597]: `on_credential` does not live long enough ``` To fix this issue, you can create a reference to it: ```rust use napi::bindgen_prelude::*; use napi_derive::napi; #[napi] pub fn build_credential(env: Env. on_credential: Function<(String, Option<String>, CredentialType), ClassInstance<Cred>>) -> Result<()> { let mut callbacks = git2::RemoteCallbacks::new(); let on_credential_ref = on_credential.create_ref()?; callbacks.credentials(move |url, username_from_url, allowed_types| { let on_credential = on_credential_ref.borrow_back(&env)?; on_credential.call((url.to_string(), username_from_url.map(|s| s.to_string()), allowed_types.into())) .map(...) .map_error(...) }); } ``` |
||
---|---|---|
.cargo | ||
.github | ||
.husky | ||
.yarn | ||
bench | ||
cli | ||
crates | ||
examples | ||
images | ||
memory-testing | ||
triples | ||
wasm-runtime | ||
.cirrus.yml | ||
.dockerignore | ||
.editorconfig | ||
.eslintignore | ||
.eslintrc.yml | ||
.gitattributes | ||
.gitignore | ||
.npmignore | ||
.prettierignore | ||
.yarnrc.yml | ||
alpine-zig.Dockerfile | ||
alpine.Dockerfile | ||
Cargo.toml | ||
CODE_OF_CONDUCT.md | ||
debian-aarch64.Dockerfile | ||
debian-zig.Dockerfile | ||
debian.Dockerfile | ||
lerna.json | ||
LICENSE | ||
package.json | ||
README.md | ||
renovate.json | ||
rustfmt.toml | ||
tsconfig.json | ||
tsconfig.root-lint.json | ||
yarn.lock |
napi-rs
This project was initialized from xray
A framework for building compiled Node.js
add-ons in Rust
via Node-API. Website: https://napi.rs
Platform Support
MSRV
Rust 1.65.0
node12 | node14 | node16 | node18 | node20 | |
---|---|---|---|---|---|
Windows x64 | ✓ | ✓ | ✓ | ✓ | ✓ |
Windows x86 | ✓ | ✓ | ✓ | ✓ | ✓ |
Windows arm64 | ✓ | ✓ | ✓ | ✓ | ✓ |
macOS x64 | ✓ | ✓ | ✓ | ✓ | ✓ |
macOS aarch64 | ✓ | ✓ | ✓ | ✓ | ✓ |
Linux x64 gnu | ✓ | ✓ | ✓ | ✓ | ✓ |
Linux x64 musl | ✓ | ✓ | ✓ | ✓ | ✓ |
Linux aarch64 gnu | ✓ | ✓ | ✓ | ✓ | ✓ |
Linux aarch64 musl | ✓ | ✓ | ✓ | ✓ | ✓ |
Linux arm gnueabihf | ✓ | ✓ | ✓ | ✓ | ✓ |
Linux riscv64 gnu | N/A | N/A | ✓ | ✓ | ✓ |
Linux aarch64 android | ✓ | ✓ | ✓ | ✓ | ✓ |
Linux armv7 android | ✓ | ✓ | ✓ | ✓ | ✓ |
FreeBSD x64 | ✓ | ✓ | ✓ | ✓ | ✓ |
This library depends on Node-API and requires Node@10.0.0
or later.
We already have some packages written by napi-rs
: node-rs
One nice feature is that this crate allows you to build add-ons purely with the Rust/JavaScript
toolchain and without involving node-gyp
.
Taste
You can start from package-template to play with
napi-rs
Define JavaScript functions
/// import the preludes
use napi::bindgen_prelude::*;
use napi_derive::napi;
/// module registration is done by the runtime, no need to explicitly do it now.
#[napi]
fn fibonacci(n: u32) -> u32 {
match n {
1 | 2 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
/// use `Fn`, `FnMut` or `FnOnce` traits to defined JavaScript callbacks
/// the return type of callbacks can only be `Result`.
#[napi]
fn get_cwd<T: Fn(String) -> Result<()>>(callback: T) {
callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap();
}
/// or, define the callback signature in where clause
#[napi]
fn test_callback<T>(callback: T)
where T: Fn(String) -> Result<()>
{}
/// async fn, require `async` feature enabled.
/// [dependencies]
/// napi = {version="2", features=["async"]}
#[napi]
async fn read_file_async(path: String) -> Result<Buffer> {
tokio::fs::read(path)
.map(|r| match r {
Ok(content) => Ok(content.into()),
Err(e) => Err(Error::new(
Status::GenericFailure,
format!("failed to read file, {}", e),
)),
})
.await
}
more examples at examples
Building
This repository is a Cargo
crate. Any napi-based add-on should contain Cargo.toml
to make it a Cargo crate.
In your Cargo.toml
you need to set the crate-type
to "cdylib"
so that cargo builds a C-style shared library that can be dynamically loaded by the Node executable. You'll also need to add this crate as a dependency.
[package]
name = "awesome"
[lib]
crate-type = ["cdylib"]
[dependencies]
napi = "2"
napi-derive = "2"
[build-dependencies]
napi-build = "1"
And create build.rs
in your own project:
// build.rs
extern crate napi_build;
fn main() {
napi_build::setup();
}
So far, the napi
build script has only been tested on macOS
Linux
Windows x64 MSVC
and FreeBSD
.
Install the @napi-rs/cli
to help you build your Rust
codes and copy Dynamic lib
file to .node
file in case you can require
it in your program.
{
"package": "awesome-package",
"devDependencies": {
"@napi-rs/cli": "^1.0.0"
},
"napi": {
"name": "jarvis" // <----------- Config the name of native addon, or the napi command will use the name of `Cargo.toml` for the binary file name.
},
"scripts": {
"build": "napi build --release",
"build:debug": "napi build"
}
}
Then you can require your native binding:
require('./jarvis.node')
The module_name
would be your package
name in your Cargo.toml
.
xxx => ./xxx.node
xxx-yyy => ./xxx_yyy.node
You can also copy Dynamic lib
file to an appointed location:
napi build [--release] ./dll
napi build [--release] ./artifacts
There are documents which contains more details about the @napi-rs/cli
usage.
Testing
Because libraries that depend on this crate must be loaded into a Node executable in order to resolve symbols, all tests are written in JavaScript in the test_module
subdirectory.
To run tests:
yarn build:test
yarn test
Related projects
Features table
Rust Type | Node Type | NAPI Version | Minimal Node version | Enable by napi feature |
---|---|---|---|---|
u32 | Number | 1 | v8.0.0 | |
i32/i64 | Number | 1 | v8.0.0 | |
f64 | Number | 1 | v8.0.0 | |
bool | Boolean | 1 | v8.0.0 | |
String/&'a str | String | 1 | v8.0.0 | |
Latin1String | String | 1 | v8.0.0 | latin1 |
UTF16String | String | 1 | v8.0.0 | |
Object | Object | 1 | v8.0.0 | |
serde_json::Map | Object | 1 | v8.0.0 | serde-json |
serde_json::Value | any | 1 | v8.0.0 | serde-json |
Array | Array | 1 | v8.0.0 | |
Vec | Array | 1 | v8.0.0 | |
Buffer | Buffer | 1 | v8.0.0 | |
External | External | 1 | v8.0.0 | |
Null | null | 1 | v8.0.0 | |
Undefined/() | undefined | 1 | v8.0.0 | |
Result<()> | Error | 1 | v8.0.0 | |
T: Fn(...) -> Result | Function | 1 | v8.0.0 | |
Async/Future | Promise | 4 | v10.6.0 | async |
AsyncTask | Promise | 1 | v8.5.0 | |
JsGlobal | global | 1 | v8.0.0 | |
JsSymbol | Symbol | 1 | v8.0.0 | |
Int8Array/Uint8Array ... | TypedArray | 1 | v8.0.0 | |
JsFunction | threadsafe function | 4 | v10.6.0 | napi4 |
BigInt | BigInt | 6 | v10.7.0 | napi6 |