iTranslated by AI
Adding native ESM modules to LLRT
LLRT only support a fraction of the Node.js APIs, so you need to manually add support to the runtime to run existing Node.js modules.
While there are methods like injecting shims at runtime or rewriting code during build, LLRT is designed according to the rquickjs API, so the barrier to extending it yourself with Rust is quite low.
I was able to set up the build environment on my aarch64-apple-darwin machine without issues by following Building from source.
Note that LLRT uses facebook/zstd for internal binary representation, and the zig compiler might be required for its build.
Also, using a newer nightly rustc seems to prevent issues. In my environment, rustc 1.76.0 (07dca489a 2024-02-04) was running fine.
Once make is finished, the environment is ready if you can execute the following command:
❯ ./target/debug/llrt --version
LLRT (darwin arm64) 0.1.6-beta
The overall flow is as follows:
- Implement a custom
ModuleDeffor rquickjs in a module undersrc/. - Add the above to
src/main.rs. - Define the mapping with JS (ESM) in
src/vm.rs.
Add src/hello/mod.rs and write it as follows:
use rquickjs::{module::{Declarations, Exports, ModuleDef}, prelude::{Func}, Ctx, Result};
use crate::module::export_default;
pub struct HelloModule;
impl HelloModule {
/*
Function body code to be called
*/
pub fn greet(_ctx: Ctx, name: String) -> String {
format!("Hello, {}!", name)
}
}
impl ModuleDef for HelloModule {
fn declare(declare: &mut Declarations) -> Result<()> {
declare.declare("greet")?;
declare.declare("default")?;
Ok(())
}
fn evaluate<'js>(ctx: &Ctx<'js>, exports: &mut Exports<'js>) -> Result<()> {
/*
export_default: A helper function provided by LLRT. Set it in the default import space.
*/
export_default(ctx, exports, |default| {
default.set("greet", Func::from(Self::greet))?;
Ok(())
})?;
Ok(())
}
}
Define it as a submodule in main.rs.
src/main.rs
@@ -34,6 +34,8 @@ mod uuid;
mod vm;
mod xml;
+mod hello;
+
use minimal_tracer::MinimalTracer;
use rquickjs::{AsyncContext, Module};
use std::{
vm.rs is an important file that implements the LLRT JavaScript VM.
Associate the ESM "hello" with HelloModule.
src/vm.rs
@@ -59,6 +59,7 @@ use crate::{
},
uuid::UuidModule,
xml::XmlModule,
+ hello::HelloModule,
};
macro_rules! create_modules {
@@ -99,7 +100,8 @@ create_modules!(
"buffer" => BufferModule,
"child_process" => ChildProcessModule,
"util" => UtilModule,
"uuid" => UuidModule,
+ "hello" => HelloModule
);
Prepare the JS code to test this.
import {greet} from "hello";
console.log(greet("LLRT"));
This is all you need for a Hello World, but you'll likely want to debug while editing the Rust code.
Invoke it as follows:
❯ cargo run -- test.mjs
Compiling llrt v0.1.6-beta (/src/llrt)
Finished dev [unoptimized + debuginfo] target(s) in 4.33s
Running `target/debug/llrt test.mjs`
Hello, LLRT!
Since Hello, LLRT! is displayed, you can see that the hello module is working.
Discussion