with one click
adding-builtin-module
// Scaffolds and registers a new Node.js-compatible built-in module in the skeleton. Use when asked to add a new node:X module, implement a new built-in, or create a new native+JS module pair.
// Scaffolds and registers a new Node.js-compatible built-in module in the skeleton. Use when asked to add a new node:X module, implement a new built-in, or create a new native+JS module pair.
Guides development workflow for the embedded skeleton crate. Use when modifying files under crates/wasm-rquickjs/skeleton/, working on JavaScript runtime APIs, or troubleshooting skeleton build issues.
Cleans up and refactors a Node.js-compatible built-in module for improved code quality. Use when asked to clean up, simplify, refactor, or improve the coding style of an existing built-in module (e.g., node:buffer, node:path, node:crypto).
Runs build, clippy, formatting, and lint checks across all targets before a PR. Use when asked to finalize, prepare, or clean up before submitting a pull request.
Tests a 3rd-party npm library against the wasm-rquickjs runtime. Use when asked to test an npm package, check library compatibility, or pick the next untested library from the tracker.
Fixes failing Node.js compatibility tests in the node_compat suite. Use when asked to make a node_compat test pass, unskip a test, fix a test-crypto/test-path/test-* failure, or implement missing Node.js API behavior to satisfy a vendored test.
Regenerates DTS goldenfiles after d.ts generation logic changes. Use when dts tests fail due to expected output changes, when asked to update goldenfiles, or after modifying the d.ts generator.
| name | adding-builtin-module |
| description | Scaffolds and registers a new Node.js-compatible built-in module in the skeleton. Use when asked to add a new node:X module, implement a new built-in, or create a new native+JS module pair. |
Step-by-step guide for adding a new Node.js-compatible built-in module to the skeleton.
Built-in modules use a hybrid native+JS pattern:
#[rquickjs::module], registered as __wasm_rquickjs_builtin/<name>_nativeFile: crates/wasm-rquickjs/skeleton/src/builtin/<name>.rs
use rquickjs::module::ModuleDef;
use rquickjs::prelude::*;
pub const <NAME>_JS: &str = include_str!("<name>.js");
pub const REEXPORT_JS: &str = r#"export { default } from 'node:<name>'; export * from 'node:<name>';"#;
pub const WIRE_JS: &str = ""; // Only if globals need wiring
#[rquickjs::module]
mod native_module {
// Export functions here
#[rquickjs::function]
pub fn example_function() -> String {
"hello".to_string()
}
}
pub use native_module::js_native_module;
File: crates/wasm-rquickjs/skeleton/src/builtin/<name>.js
import { example_function } from '__wasm_rquickjs_builtin/<name>_native';
// NOTE: Always use original Rust snake_case names in imports!
// Implement Node.js-compatible API surface
const result = example_function();
export { result };
export default { result };
Edit crates/wasm-rquickjs/skeleton/Cargo.toml_:
default-features = false for crates that may pull in C/native librariesrust_backend feature) for wasm32-wasip2 compatibilitymod.rsFile: crates/wasm-rquickjs/skeleton/src/builtin/mod.rs
Four places need updating:
a) Add the module declaration (top of file):
mod <name>;
b) Add to add_module_resolvers — register the internal native path and public names:
.with_module("__wasm_rquickjs_builtin/<name>_native")
.with_module("node:<name>")
.with_module("<name>")
c) Add to module_loader (ModuleLoader) — map native path to Rust module:
.with_module("__wasm_rquickjs_builtin/<name>_native", <name>::js_native_module)
d) Add to module_loader (BuiltinLoader) — map public names to JS constants:
.with_module("node:<name>", <name>::<NAME>_JS)
.with_module("<name>", <name>::REEXPORT_JS)
e) (Optional) Add to wire_builtins — if the module needs to attach globals:
writeln!(result, "{}", <name>::WIRE_JS).unwrap();
Add the new module to the supported APIs list in README.md.
#[rquickjs::module(rename = ...)] does NOT affect JS importsThe rename attribute (e.g., rename = "camelCase") only affects Rust-side naming. JavaScript import statements must always use the original Rust snake_case names. Importing the camelCase version causes "Could not find export" errors.
u32 return value truncationrquickjs may deliver u32 as signed i32 to JavaScript (e.g., 0xFFFFFFFF → -1). In the JS wrapper, apply >>> 0:
const result = native_fn() >>> 0;
Use Object.defineProperty with writable: false, configurable: false on default export objects to emulate Node.js behavior where module constants are immutable.
Always use default-features = false for crates that may pull in C libraries. Use pure-Rust backends to ensure wasm32-wasip2 target compatibility.
The skeleton is always compiled to wasm32-wasip1. Never write conditional code that checks for unix/windows/macOS or any other host platform (e.g., #[cfg(unix)], #[cfg(windows)], #[cfg(target_os = "...")], process.platform === "win32", path.sep === "\\", etc.). Such checks are meaningless in the WASM target and add dead code complexity.
Never use a loopback transport for node:http. Every node:http client request MUST go through wasi:http (the native Rust NodeHttpClientRequest). Do NOT add any fallback that bypasses wasi:http by creating direct node:net socket connections for loopback/localhost addresses.
examples/runtime/ (JS file + WIT interface pair)tests/runtime.rs that use the examplecargo test --test runtime <module> -- --nocaptureIf also adding node_compat coverage, use the fixing-node-compat-test skill for that workflow.