Skip to content

Workflow: Module Load Lifecycle

Fresh 🌱

What actually happens from the moment Node loads your native module to the moment a Rust-backed function returns a value.

The flow

sequenceDiagram
    participant JS as Node.js
    participant NODE as index.node
    participant MAIN as "#[neon::main] main()"
    participant FN as Your Rust fn

    JS->>NODE: require('.')
    NODE->>MAIN: load module, run init (once)
    MAIN->>MAIN: cx.export_function("get", get_fn)
    MAIN->>MAIN: neon::registered().export(cx)
    MAIN-->>JS: module.exports populated
    JS->>FN: addon.get(args)
    FN->>FN: read args via cx, build JS values
    FN-->>JS: Ok(value)  or  thrown exception

Stage by stage

  1. require('.') - Node loads the compiled index.node binary, exactly like loading any other module, but the file is native code.
  2. Module initialization runs once - Neon calls the function marked #[neon::main]. This is the only code that runs at load time.
  3. Exports are registered - inside main, you call cx.export_function(...) for explicit exports and/or neon::registered().export(&mut cx)? to publish everything tagged with #[neon::export]. You can also cx.export_value(...) for constants and prebuilt objects.
  4. module.exports is populated - once main returns Ok(()), the JavaScript side sees a fully formed exports object.
  5. A call arrives - when JS invokes an exported function, Neon creates a fresh FunctionContext and runs your Rust function.
  6. The function returns - your function reads arguments through cx, builds JS values through cx, and returns a JsResult: either Ok(value) (returned to JS) or Err(...) (thrown as a JS exception).

Key facts

FactWhy it matters
#[neon::main] runs once, at loadDo one-time setup here (register exports, build constant objects). Do not put per-call work here.
Each call gets its own FunctionContextContexts are not shared or reused across calls; never store one.
The init function returns NeonResultExport registration can throw, so propagate with ?.

Mapping back to code

This whole diagram is driven by two things you write: the #[neon::main] function (step 2-4) and your individual #[neon::export] / FunctionContext functions (step 5-6). See the Export Functions SOP.