No description
Find a file
LongYinan baf0db2f19
feat(napi): new Function/FunctionRef API (#1913)
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(...)
  });
}
```
2024-01-26 10:58:30 +08:00
.cargo fix(napi): compile error for wasm32-unknown-unknown target 2023-11-20 17:10:58 +08:00
.github chore(deps): update actions/cache action to v4 (#1908) 2024-01-18 10:35:59 +08:00
.husky chore: upgrade deps 2021-03-11 13:42:28 +08:00
.yarn fix(wasm-runtime): wasm compatible with core-js (#1909) 2024-01-20 15:24:16 +08:00
bench feat(cli): brand new cli tool with both cli and programmatical usage (#1492) 2023-04-06 11:04:53 +08:00
cli chore(release): publish 2024-01-17 00:59:12 +08:00
crates Update crates/napi/src/bindgen_runtime/js_values/function.rs 2024-01-26 02:27:46 +00:00
examples feat(napi): new Function/FunctionRef API 2024-01-26 02:27:46 +00:00
images docs: add next.js into README 2021-07-30 13:14:54 +08:00
memory-testing test: add test file name into error message (#1821) 2023-11-20 15:56:20 +08:00
triples chore(release): publish 2023-12-30 00:44:20 +08:00
wasm-runtime chore(release): publish 2024-01-17 00:59:12 +08:00
.cirrus.yml feat: add wasm runtime package (#1904) 2024-01-16 23:28:40 +08:00
.dockerignore feat(napi): support musl linux 2020-06-11 16:20:37 +08:00
.editorconfig init 2018-04-28 16:26:38 +08:00
.eslintignore feat(cli): support generate browser compatible codes (#1891) 2024-01-08 21:02:46 +08:00
.eslintrc.yml feat: add wasm runtime package (#1904) 2024-01-16 23:28:40 +08:00
.gitattributes chore: upgrade to yarn3 2022-01-24 17:25:40 +08:00
.gitignore chore: add .idea folder to ignore (#1801) 2023-11-13 10:07:50 +08:00
.npmignore feat: support linux aarch64 2020-10-15 09:12:43 +08:00
.prettierignore chore: upgrade emnapi dependencies (#1817) 2023-11-19 15:13:06 +08:00
.yarnrc.yml chore(deps): update yarn to v4.0.2 (#1813) 2023-11-14 19:59:37 +08:00
alpine-zig.Dockerfile build: upgrade toolchain in Docker (#1810) 2023-11-14 09:58:58 +08:00
alpine.Dockerfile build: update Node.js version in alpine.Dockerfile 2023-03-31 14:19:05 +08:00
Cargo.toml refactor(cli): --profile flag (#1604) 2023-05-27 12:00:43 +08:00
CODE_OF_CONDUCT.md doc: add email to CODE_OF_CONDUCT 2020-05-20 18:49:36 +08:00
debian-aarch64.Dockerfile ci: add back pnpm to debian Docker images (#1841) 2023-12-06 17:19:10 +08:00
debian-zig.Dockerfile build: upgrade toolchain in Docker (#1810) 2023-11-14 09:58:58 +08:00
debian.Dockerfile ci: add back pnpm to debian Docker images (#1841) 2023-12-06 17:19:10 +08:00
lerna.json ci: add release npm cli 2023-07-18 11:06:45 +08:00
LICENSE docs: update license 2021-01-25 14:12:44 +08:00
package.json chore(deps): update dependency husky to v9 (#1914) 2024-01-25 16:06:58 +08:00
README.md chore: drop node 17, add node 18 2022-04-27 13:17:39 +08:00
renovate.json chore: remove node10 test 2022-01-04 11:14:55 +08:00
rustfmt.toml feat(cli): refactor cli build 2021-11-19 18:22:40 +08:00
tsconfig.json fix: prevent crashing when napi_register_module_v1 is called twice (#1554) 2023-04-08 23:08:48 +08:00
tsconfig.root-lint.json feat: add wasm runtime package (#1904) 2024-01-16 23:28:40 +08:00
yarn.lock chore(deps): update dependency husky to v9 (#1914) 2024-01-25 16:06:58 +08:00

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

chat Stake to support us

Platform Support

Test & Release FreeBSD Address Sanitizer Memory Leak Detect

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

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