# 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.

- Skill: `golemcloud/adding-builtin-module` (Agent Skill)
- Install (CLI): `npx skillmds@latest add golemcloud/adding-builtin-module`
- Raw SKILL.md: https://api.skillmd.com/api/skills/golemcloud/adding-builtin-module/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: golemcloud (https://skillmd.com/u/golemcloud)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/golemcloud/adding-builtin-module

---


# Adding a New Built-in Module

Step-by-step guide for adding a new Node.js-compatible built-in module to the skeleton.

## Architecture

Built-in modules use a **hybrid native+JS pattern**:
- **Rust native bridge**: Low-level operations via `#[rquickjs::module]`, registered as `__wasm_rquickjs_builtin/<name>_native`
- **JS wrapper**: High-level Node.js-compatible API surface, imports from the native bridge

## Checklist

### 1. Create the Rust native module

File: `crates/wasm-rquickjs/skeleton/src/builtin/<name>.rs`

```rust
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;
```

### 2. Create the JS wrapper

File: `crates/wasm-rquickjs/skeleton/src/builtin/<name>.js`

```javascript
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 };
```

### 3. Add dependencies (if needed)

Edit `crates/wasm-rquickjs/skeleton/Cargo.toml_`:
- Always use `default-features = false` for crates that may pull in C/native libraries
- Use pure-Rust backends (e.g., `rust_backend` feature) for `wasm32-wasip2` compatibility

### 4. Register in `mod.rs`

File: `crates/wasm-rquickjs/skeleton/src/builtin/mod.rs`

Four places need updating:

**a) Add the module declaration** (top of file):
```rust
mod <name>;
```

**b) Add to `add_module_resolvers`** — register the internal native path and public names:
```rust
.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:
```rust
.with_module("__wasm_rquickjs_builtin/<name>_native", <name>::js_native_module)
```

**d) Add to `module_loader` (BuiltinLoader)** — map public names to JS constants:
```rust
.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:
```rust
writeln!(result, "{}", <name>::WIRE_JS).unwrap();
```

### 5. Update README.md

Add the new module to the supported APIs list in `README.md`.

## Critical Gotchas

### `#[rquickjs::module(rename = ...)]` does NOT affect JS imports
The `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 truncation
`rquickjs` may deliver `u32` as signed i32 to JavaScript (e.g., `0xFFFFFFFF` → `-1`). In the JS wrapper, apply `>>> 0`:
```javascript
const result = native_fn() >>> 0;
```

### CJS default export immutability
Use `Object.defineProperty` with `writable: false, configurable: false` on default export objects to emulate Node.js behavior where module constants are immutable.

### WASM-compatible dependencies
Always use `default-features = false` for crates that may pull in C libraries. Use pure-Rust backends to ensure `wasm32-wasip2` target compatibility.

### No platform-conditional code
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.

### node:http transport rule
**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.

## Testing

1. Define an example in `examples/runtime/` (JS file + WIT interface pair)
2. Add tests in `tests/runtime.rs` that use the example
3. Run: `cargo test --test runtime <module> -- --nocapture`

If also adding node_compat coverage, use the `fixing-node-compat-test` skill for that workflow.

