#nodejs #add-on #native #extension #js #napi #ease

nodex

nodex — 轻松创建Nodejs原生插件

23个版本

0.2.4 2024年1月2日
0.2.3 2022年3月8日
0.1.6 2022年2月23日
0.1.3 2022年1月29日

#233 in 开发工具

Download history 2/week @ 2024-05-21

104 每月下载量
nodex-plugin-helloworld 中使用

MIT 协议

12MB
4.5K SLoC

Nodex - Nodejs 扩展 🥳

又一个用于创建原生Nodejs插件的crate :)

此crate旨在使创建原生Nodejs插件变得非常简单和舒适。

点击此处:uuhan/nodex@dev 查看最新发展。

平台支持

  • linux
  • macos
  • windows (>=0.2.1)

变更日志

版本

用法

[lib]
crate-type = ["cdylib"]

[dependencies.nodex-api]
version = "0.2.3"
features = ["v8"]

默认napi版本设置为v1,您可以根据需要使用其他版本。

我们有v1,v2,v3,...v8版本。

目前,nodex只是重新导出nodex-api

[lib]
crate-type = ["cdylib"]

[dependencies.nodex]
version = "0.2.3"
features = ["v8"]

Napi 级别

v1

  • NapiValueT::wrap::<T, Finalizer>() - 包装原生实例,当值被垃圾回收时调用finalizer。
  • NapiValueT::remove_wrap::<T>() - 移除包装的原生实例。如果包装的实例被移除,则不会调用finalizer。
  • NapiValueT::unwrap::<T>() - 访问包装实例。
  • NapiValueT::gc::<Finalizer>() - 当值被垃圾回收时触发的钩子。

v3

  • NapiEnv::add_cleanup_hook() - 当nodejs环境退出时执行清理。

v4

  • NapiThreadsafeFunction::<Data, const N: usize> - 线程安全函数。

v5

  • NapiValueT::finalizer() - 添加napi_finalize回调,当JavaScript对象准备好进行gc时将被调用。

v6

  • NapiEnv::set_instance_data::<Data, Finalizer> - 将数据设置到当前代理。
  • NapiENv::get_instance_data::<Data> - 从当前代理获取Option<&mut Data>。

v8

  • NapiEnv::add_async_cleanup_hook() - 当nodejs环境退出时异步执行清理。

示例

初始化模块

通过以下方式简单定义您的模块

use nodex::prelude::*;
nodex::napi_module!(init);
fn init(env: NapiEnv, exports: JsObject) -> NapiResult<()> {
    Ok(())
}

版本保护

确保node api版本大于或等于您的编译插件版本。

use nodex::prelude::*;
fn env(env: NapiEnv) -> NapiResult<()> {
    nodex::napi_guard!(env.napi_version()?);
    Ok(())
}

Nodejs 版本 & Napi 版本

获取运行时版本

use nodex::prelude::*;
fn env(env: NapiEnv) -> NapiResult<()> {
    let node_version = env.node_version()?;
    let napi_version = env.napi_version()?;
    Ok(())
}

定义Js变量

use nodex::prelude::*;
fn env(env: NapiEnv) -> NapiResult<()> {
    // String & Symbol
    let label: JsSymbol = env.symbol()?;
    let name: JsString = env.string("")?;

    // Object
    let mut obj: JsObject = env.object()?;
    obj.set_property(name, env.null()?)?;

    // Function
    let func: JsFunction = env.func(move |this, (a1, a2, a3): (JsValue, JsValue, JsValue)| {
        let env = this.env();
        a1.as_function()?.call(this, ())?;
        a1.as_function()?.call(this, env.string("I am from rust world.")?)
    })?;

    let func: JsFunction = env.func(move |this, a1: JsFunction| {
        let env = this.env();
        a1.call(this, env.string("I am from rust world.")?)
    })?;

    let class: JsClass = env.class("myclass", |mut this, a1: JsNumber| {
        this.set_named_property("a1", a1)?;
        Ok(this)
    }, &[])?;

    // Error
    let error: JsError = JsError::error(env, "error", Some("code"))?;

    Ok(())
}

Napi 处理作用域

use nodex::prelude::*;
fn env(env: NapiEnv) -> NapiResult<()> {
    // napi handle scope
    let _scope: NapiHandleScope = env.handle_scope()?;
    let _escapable_scope: NapiEscapableHandleScope = env.escapable_handle_scope()?;
    Ok(())
}

Napi 清理钩子

同步

use nodex::prelude::*;
fn env(env: NapiEnv) -> NapiResult<()> {
    env.add_cleanup_hook(|| {
        println!("clean hook fired");
        Ok(())
    })?;

    let hook_to_remove = env.add_cleanup_hook(|| {
        println!("clean hook fired");
        Ok(())
    })?;

    hook_to_remove.remove()?;
    Ok(())
}

异步

use nodex::prelude::*;
fn env(env: NapiEnv) -> NapiResult<()> {
    match env.add_async_cleanup_hook(|hook| {
        // DO SOME CLEANUP
        // NB: should call remove after done
        hook.remove()
    })? {
        Some(hook) => {
            // NB: also the hook can be removed before it is fired.
            hook.remove()?;
        }
        None => {}
    }

    Ok(())
}

设置属性描述符

use nodex::prelude::*;
fn env(env: NapiEnv) -> NapiResult<()> {
    let mut obj: JsObject = env.object()?;
    obj.define_properties(&[DescriptorValueBuilder::new()
        .with_utf8name("myvalue")
        .with_value(env.string("myvalue")?)
        .build()?])?;

    obj.define_properties(&[DescriptorMethodBuilder::new()
        .with_utf8name("mymethod")
        .with_method(move |this, ()| this.env().double(200.))
        .build()?])?;

    obj.define_properties(&[DescriptorAccessorBuilder::new()
        .with_utf8name("myaccessor")
        .with_getter(|this| this.env().double(100.))
        .with_setter(|_this: JsObject, n: JsNumber| {
            println!("setter: {}", n.get_value_int32()?);
            Ok(())
        })
        .build()?])?;

    Ok(())
}

创建异步工作

use nodex::prelude::*;
fn env(env: NapiEnv) -> NapiResult<()> {
    // without shared state
    env.async_work(
        "my-test-async-task",
        (),
        move |_| {
            // you can do the hard work in the thread-pool context.
            // NB: js work is not allowed here.
            println!("execute async task");
        },
        move |_, status, _| {
            // you can do some js work in this context
            println!("[{}] complete async task", status);
            Ok(())
        },
    )?
    .queue()?;

    Ok(())
}

垃圾回收钩子

对于napi小于5的情况,通过napi_wrap实现,否则通过napi_add_finalizer实现。

use nodex::prelude::*;
fn env(env: NapiEnv) -> NapiResult<()> {
    let mut obj = env.object()?;
    obj.gc(move |_| {
        println!("obj garbage-collected");
        Ok(())
    });

    Ok(())
}

包装原生实例

use nodex::prelude::*;
fn env(env: NapiEnv) -> NapiResult<()> {
    let mut obj = env.object()?;
    obj.wrap([1usize; 2], move |_, wrapped| {
        Ok(())
    })?;
    obj.unwrap::<[usize; 2]>()?; // access the wrapped instance
    obj.remove_wrap::<[usize; 2]>()?; // the finalizer will not be called
    Ok(())
}

线程安全函数

require: napi >= 4

use nodex::prelude::*;
fn env(env: NapiEnv) -> NapiResult<()> {
    let tsfn = NapiThreadsafeFunction::<_, 0>::new(
        env,
        "tsfn-task",
        env.func(|this, a1: JsString| {
            println!("callback result: {}", a1.get()?);
            this.env().undefined()
        })?,
        // finalizer
        move |_| Ok(()),
        // js-callback
        move |f, data: String| {
            f.call(env.object()?, env.string(&data)?)?;
            Ok(())
        },
    )?;

    std::thread::spawn(move || {
        tsfn.non_blocking("hello, world - 1".into()).unwrap();
        tsfn.non_blocking("hello, world - 2".into()).unwrap();
        tsfn.release().unwrap();
    });
    Ok(())
}

对于一些重型工作使用Promise

use nodex::prelude::*;
fn test(env: NapiEnv) -> NapiResult<()> {
let promise: JsPromise<JsString, JsError> = env.promise(
    move |result| {
        for i in 1..=3 {
            std::thread::sleep(std::time::Duration::from_secs(1));
            println!("[{}] Doing...", i);
        }

        *result = true;
    },
    move |promise, _, result| {
        let env = promise.env();
        if result {
            promise.resolve(env.string("the promise is resolved.")?)?;
        } else {
            promise.reject(env.error("the promise is rejected.")?)?;
        }
        Ok(())
    },
)?;
Ok(())
}
// the `promise.value()` can return to js world as a Promise

运行脚本

use nodex::prelude::*;
fn script(env: NapiEnv) -> NapiResult<()> {
    let func: Function<JsUndefined> = env.run_script(
        r#"
            function hello() {
                console.log(this);
            }

            hello
        "#,
    )?;

    func.call(env.global()?.object(), ())?;
    Ok(())
}

更多

示例/演示

运行

bash demo.sh

如何参与

行为准则

cat >> .git/hooks/pre-push << EOF
#!/bin/sh

cargo fmt || exit
cargo clippy -- -D warnings || exit
EOF

chmod +x .git/hooks/pre-push

待办事项

  • 人体工程学API设计。
  • 从crates世界导出代码库,使其易于从js世界调用Rust函数。
  • 从npm世界导入庞大的代码库,使其易于从Rust端调用js函数。
    • 甜美的语法,例如:let lodash = nodex::import!(lodash);
  • Node.js异步运行时驱动Rust异步代码
    • 异步Rust运行时
    • 宏如:#nodex::rt async fn main(),这样您可以使用Node.js运行任何Rust异步代码。
      • node --require=main.node
      • 使用Node.js repl进行Rust代码反射
  • cargo-nodex cargo子命令,用于简化创建Node.js插件,例如自动生成ts类型定义。
    • cargo nodex build
    • cargo nodex typings
    • cargo nodex package

许可协议

根据您的选择,许可协议为以下之一

依赖关系