23 releases
0.2.4 | Jan 2, 2024 |
---|---|
0.2.3 | Mar 8, 2022 |
0.1.6 | Feb 23, 2022 |
0.1.3 | Jan 29, 2022 |
#253 in Development tools
104 downloads per month
Used in nodex-plugin-helloworld
12MB
4.5K
SLoC
Nodex - Nodejs eXtension 🥳
Yet another crate to create native nodejs addons :)
This crate aims to make creating native nodejs addons very easy and comfortable.
click here: uuhan/nodex@dev to see the most recent developments.
Platform Support
- linux
- macos
- windows (>=0.2.1)
Changelog
Usage
[lib]
crate-type = ["cdylib"]
[dependencies.nodex-api]
version = "0.2.3"
features = ["v8"]
The default napi version is set to v1, you can use other version with your need.
We have v1,v2,v3,...v8 versions.
Currently, nodex just reexports nodex-api:
[lib]
crate-type = ["cdylib"]
[dependencies.nodex]
version = "0.2.3"
features = ["v8"]
Napi Level
v1
- NapiValueT::wrap::<T, Finalizer>() - Wraps a native instance, call finalizer when value is garbage-collected.
- NapiValueT::remove_wrap::<T>() - Remove the wrapped native instance. The finalizer will not be called if the wrapped instance is removed.
- NapiValueT::unwrap::<T>() - Access the wrapped instance.
- NapiValueT::gc::<Finalizer>() - Hook fired when value is gabage-collected.
v3
- NapiEnv::add_cleanup_hook() - Do the cleanup when nodejs environment exits.
v4
- NapiThreadsafeFunction::<Data, const N: usize> - Thread safe function.
v5
- NapiValueT::finalizer() - Adds a napi_finalize callback which will be called when the JavaScript object is ready for gc.
v6
- NapiEnv::set_instance_data::<Data, Finalizer> - Set data to current agent.
- NapiENv::get_instance_data::<Data> - Get Option<&mut Data> from current agent.
v8
- NapiEnv::add_async_cleanup_hook() - Do the cleanup when nodejs environment exits, asynchronous.
Examples
Init Module
simply define your module by:
use nodex::prelude::*;
nodex::napi_module!(init);
fn init(env: NapiEnv, exports: JsObject) -> NapiResult<()> {
Ok(())
}
Version Guard
make sure the node api version is large or equal than your compiled addon's.
use nodex::prelude::*;
fn env(env: NapiEnv) -> NapiResult<()> {
nodex::napi_guard!(env.napi_version()?);
Ok(())
}
Nodejs Version & Napi Version
get the runtime version:
use nodex::prelude::*;
fn env(env: NapiEnv) -> NapiResult<()> {
let node_version = env.node_version()?;
let napi_version = env.napi_version()?;
Ok(())
}
Define Js Variable
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 handle scope
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 cleanup hook
sync
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(())
}
aync
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(())
}
Set Property Descriptor
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(())
}
Create An Async Work
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(())
}
gabage-collected hook
for napi less than 5, implement by napi_wrap, otherwise by 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(())
}
Wrap native instance
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(())
}
Thread safe function
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 for some heavy work
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
Run script
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(())
}
More
Run:
bash demo.sh
How to participate in
Code of conduct
cat >> .git/hooks/pre-push << EOF
#!/bin/sh
cargo fmt || exit
cargo clippy -- -D warnings || exit
EOF
chmod +x .git/hooks/pre-push
TODO
- ergonomical api design.
- export the codebase from crates world, make it easy to call rust function from js world.
- sweet syntax, like: #nodex::function fn foo()
- import the huge codebase from npm world, make it easy to call js function from rust side.
- sweet syntax, like: let lodash = nodex::import!(lodash);
- nodejs async runtime to drive rust async code
- async runtime for async rust
- macros like: #nodex::rt async fn main(), so you can use nodejs to run any rust async-code.
- node --require=main.node
- rust code introspection with nodejs repl
- cargo-nodex cargo subcommand to make ease of create nodejs addons, e.g. auto generate ts typings.
- cargo nodex build
- cargo nodex typings
- cargo nodex package
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.