🍝 edoN 🍜
Embed Node.js within Rust
Embed the fully featured Nodejs runtime into your Rust application!
Features:
Use Cases
- Use JavaScript plugins with full support for Nodejs in your Rust application
- Roll your own multi-threaded lambda runtime
- Roll your own multi-threaded SSR implementation
- Avoid the need to embed your application within an n-api extension
Usage
Simple Example
Evaluate JavaScript as a string
pub fn main() -> std::io::Result<()> {
let nodejs = edon::Nodejs::load("/path/to/libnode.so")?;
let ctx = nodejs.spawn_worker_thread()?;
ctx.eval(r#"
const message: string = "Hello World TypeScript"
console.log(message)
"#)?;
Ok(())
}
Native Extensions
Register a Napi extension and use the napi-rs API to work with the values
pub fn main() -> anyhow::Result<()> {
let nodejs = edon::Nodejs::load_auto()?;
nodejs.napi_module_register(
"example_native_module",
|env, mut exports| {
let key = env.create_string("meaning")?;
let value = env.create_uint32(42)?;
exports.set_property(key, value)?;
Ok(exports)
})?;
let ctx0 = nodejs.spawn_worker_thread()?;
ctx0.eval(r#"
const native = process._linkedBinding('example_native_module')
console.log(native)
"#)?;
Ok(())
}
Execute Native code in the Nodejs Context
Run native code against a specific Nodejs context. This is essentially eval but using
Node's n-api.
pub fn main() -> anyhow::Result<()> {
let nodejs = edon::Nodejs::load_auto()?;
let ctx0 = nodejs.spawn_worker_thread()?;
ctx0.exec(|env| {
let mut global_this = env.get_global()?;
let key = env.create_string("meaning")?;
let value = env.create_uint32(42)?;
global_this.set_property(key, value)?;
Ok(())
})?;
ctx0.eval("console.log(globalThis.meaning)")?;
Ok(())
}
Use Async Rust to interact with JsValues
Use a Rust async runtime that works cooperatively with Nodejs's libuv event loop to work with
JavaScript values in a non-blocking/asynchronous fashion.
pub fn main() -> anyhow::Result<()> {
let nodejs = edon::Nodejs::load_auto()?;
nodejs.napi_module_register("my_async_extension", |env, mut exports| {
exports.set_named_property("add_one", env.create_function_from_closure("add_one", |ctx| {
let arg0 = ctx.get::<JsRc<JsNumber>>(0)?;
ctx.env.spawn_local_promise({
let env = ctx.env.clone();
async move {
async_io::Timer::after(Duration::from_secs(1)).await;
let value = arg0.get_int32()?;
let js_value = env.create_int32(value + 1)?;
Ok(js_value)
}
})
})?)?;
Ok(exports)
})?;
let ctx0 = nodejs.spawn_worker_thread()?;
ctx0.eval(
r#"
const native = process._linkedBinding('my_async_extension');
(async () => {
const result = await native.add_one(41)
console.log(result) // "42"
})();
"#,
)?;
Ok(())
}
Libnode Shared Library
This requires the libnode shared library. Currently Node.js don't provide prebuilt binaries so you have to compile libnode yourself.
Offering prebuilt binaries with a C FFI is currently under development, however in the meantime you can download libnode from here:
https://github.com/alshdavid/libnode-prebuilt
mkdir -p /opt/libnode
curl -L \
--url https://github.com/alshdavid/libnode-prebuilt/releases/download/v22.15.0/libnode-linux-amd64.tar.xz \
| tar -xJvf - -C /opt/libnode
export EDON_LIBNODE_PATH="/opt/libnode/libnode.so"
Distributing libnode with your application
I haven't had success in compiling libnode to a static library so currently you must include the libnode dynamic library alongside your binary.
/your-app
your-app
libnode.so
You can instruct edon to automatically find libnode by using
pub fn main() -> anyhow::Result<()> {
let nodejs = edon::Nodejs::load_auto()?;
}
Or manually specify the path
pub fn main() -> anyhow::Result<()> {
let nodejs = edon::Nodejs::load("/path/to/libnode.so")?;
}
Credits
Project is inspired by and contains code from: