Skip to main content

nodejs/stdlib/
node_module.rs

1//! Node `module` core module — `require('module')` (a.k.a. `require('node:module')`).
2//!
3//! This is DISTINCT from `src/module.rs` (the CommonJS loader that actually reads
4//! and runs files). This file is the user-facing `module` namespace: `isBuiltin`,
5//! `builtinModules`, `createRequire`, `Module.wrap`, etc. The heavy lifting reuses
6//! the loader's own machinery — `createRequire` mints a real dir-bound `require`
7//! closure through the same `__cjs_require`/`__cjs_resolve` global dispatch the
8//! loader's per-module `require` uses (see `src/module.rs`).
9//!
10//! `require('module').Module` is the `Module` class namespace; its statics
11//! (`Module.isBuiltin`, `Module.wrap`, `Module.createRequire`, `Module.builtinModules`)
12//! delegate to the same implementations as the module-level exports (Node's
13//! `Module.<x> === module.<x>` for these).
14
15use crate::host::{with_host, JsObj};
16use fusevm::Value;
17
18/// Free functions exported by `require('module')`.
19pub const METHODS: &[&str] = &[
20    "isBuiltin",
21    "createRequire",
22    "wrap",
23    "syncBuiltinESMExports",
24    "runMain",
25    "findPackageJSON",
26];
27
28/// Static method names on the `Module` class (same surface as the module-level
29/// exports — Node aliases them).
30pub const MODULE_STATIC_METHODS: &[&str] = METHODS;
31
32/// The canonical `module.builtinModules` list: the specifiers `stdlib::resolve`
33/// accepts (the modules node-js actually provides), plus the two modules added in
34/// this batch (`module`, `stream/consumers`). Sorted, no `node:` duplicates and
35/// no hidden aliases (`sys`), matching how Node presents `builtinModules`.
36const BUILTIN_MODULES: &[&str] = &[
37    "assert",
38    "assert/strict",
39    "async_hooks",
40    "buffer",
41    "child_process",
42    "cluster",
43    "console",
44    "crypto",
45    "dgram",
46    "diagnostics_channel",
47    "dns",
48    "dns/promises",
49    "domain",
50    "events",
51    "fs",
52    "fs/promises",
53    "http",
54    "http2",
55    "https",
56    "inspector",
57    "module",
58    "net",
59    "os",
60    "path",
61    "path/posix",
62    "path/win32",
63    "perf_hooks",
64    "process",
65    "punycode",
66    "querystring",
67    "readline",
68    "repl",
69    "stream",
70    "stream/consumers",
71    "string_decoder",
72    "timers",
73    "timers/promises",
74    "tls",
75    "trace_events",
76    "tty",
77    "url",
78    "util",
79    "util/types",
80    "v8",
81    "vm",
82    "wasi",
83    "worker_threads",
84    "zlib",
85];
86
87/// The `createRequire` factory: builds a real dir-bound `require` closure (the
88/// same shape as the loader's per-module `require`, with `.resolve`/`.cache`/…),
89/// resolving against `path.dirname(referencingPath)`. A `file:` URL argument is
90/// converted with `url.fileURLToPath` first.
91const CREATE_REQUIRE_SRC: &str = "(function (p) {\n\
92  if (typeof p !== 'string') { p = String(p); }\n\
93  if (p.indexOf('file://') === 0) { p = require('url').fileURLToPath(p); }\n\
94  var dir = require('path').dirname(p);\n\
95  var req = function (spec) { return __cjs_require(spec, dir); };\n\
96  req.resolve = function (spec) { return __cjs_resolve(spec, dir); };\n\
97  req.cache = {};\n\
98  req.main = undefined;\n\
99  req.extensions = {};\n\
100  return req;\n\
101})";
102
103/// Module free-function dispatch (`module.isBuiltin`, `module.createRequire`, …).
104pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
105    Some(match method {
106        "isBuiltin" => Ok(is_builtin(args)),
107        "createRequire" => create_require(args),
108        "wrap" => Ok(wrap(args)),
109        // No ESM/CJS live-binding sync in this runtime — accept and no-op.
110        "syncBuiltinESMExports" => Ok(Value::Undef),
111        // The entry script is already run by the host; a programmatic runMain is a
112        // best-effort no-op.
113        "runMain" => Ok(Value::Undef),
114        "findPackageJSON" => Ok(find_package_json(args)),
115        _ => return None,
116    })
117}
118
119/// `Module.<method>` static dispatch — same implementations as the module-level
120/// exports.
121pub fn static_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
122    call(method, args)
123}
124
125/// Non-function exports of `require('module')`: `builtinModules` (array) and the
126/// `Module` class namespace.
127pub fn constant(name: &str) -> Option<Value> {
128    match name {
129        "builtinModules" => Some(builtin_modules_array()),
130        "Module" => Some(with_host(|h| h.alloc(JsObj::Builtin("Module".into())))),
131        _ => None,
132    }
133}
134
135/// Non-function statics on the `Module` class: `Module.builtinModules` and the
136/// self-referential `Module.Module` (Node's `Module.Module === Module`).
137pub fn static_constant(name: &str) -> Option<Value> {
138    match name {
139        "builtinModules" => Some(builtin_modules_array()),
140        "Module" => Some(with_host(|h| h.alloc(JsObj::Builtin("Module".into())))),
141        _ => None,
142    }
143}
144
145// ── implementations ─────────────────────────────────────────────────────────
146
147/// `module.isBuiltin(name)` — true if `name` (with an optional `node:` prefix)
148/// names a core module. Any `node:`-prefixed specifier is treated as builtin
149/// (matches Node, which reserves the whole `node:` scheme).
150fn is_builtin(args: &[Value]) -> Value {
151    let name = super::arg_str(args, 0);
152    let base = name.strip_prefix("node:").unwrap_or(&name);
153    Value::Bool(crate::stdlib::resolve(base).is_some() || name.starts_with("node:"))
154}
155
156/// `module.createRequire(filename)` — a `require` bound to `filename`'s directory.
157fn create_require(args: &[Value]) -> Result<Value, String> {
158    let factory = run_completion(CREATE_REQUIRE_SRC)?;
159    let p = args.first().cloned().unwrap_or(Value::Undef);
160    crate::host::invoke(&factory, vec![p], None)
161}
162
163/// `module.wrap(source)` / `Module.wrap(source)` — the canonical CommonJS module
164/// wrapper string Node returns.
165fn wrap(args: &[Value]) -> Value {
166    let src = super::arg_str(args, 0);
167    with_host(|h| {
168        h.new_str(format!(
169            "(function (exports, require, module, __filename, __dirname) {{ {src}\n}});"
170        ))
171    })
172}
173
174/// `module.findPackageJSON(specifier[, base])` — best-effort: resolve `specifier`
175/// (relative to `base`'s directory when given, else cwd), then walk parent
176/// directories for the nearest `package.json`. Returns its absolute path, or
177/// `undefined` if none is found. `file:` URLs are accepted.
178fn find_package_json(args: &[Value]) -> Value {
179    use std::path::{Path, PathBuf};
180    let strip = |s: String| {
181        s.strip_prefix("file://")
182            .map(|x| x.to_string())
183            .unwrap_or(s)
184    };
185    let spec = strip(super::arg_str(args, 0));
186    let base = if args.len() > 1 {
187        Some(strip(super::arg_str(args, 1)))
188    } else {
189        None
190    };
191    let start: PathBuf = {
192        let p = Path::new(&spec);
193        if p.is_absolute() {
194            p.to_path_buf()
195        } else if let Some(b) = base.as_deref() {
196            let bp = Path::new(b);
197            let bdir = if bp.is_dir() {
198                bp
199            } else {
200                bp.parent().unwrap_or(bp)
201            };
202            bdir.join(&spec)
203        } else {
204            std::env::current_dir().unwrap_or_default().join(&spec)
205        }
206    };
207    let mut dir = if start.is_dir() {
208        Some(start.as_path())
209    } else {
210        start.parent()
211    };
212    while let Some(d) = dir {
213        let cand = d.join("package.json");
214        if cand.is_file() {
215            return with_host(|h| h.new_str(cand.to_string_lossy().to_string()));
216        }
217        dir = d.parent();
218    }
219    Value::Undef
220}
221
222/// Build the `builtinModules` array value from `BUILTIN_MODULES`.
223fn builtin_modules_array() -> Value {
224    with_host(|h| {
225        let items: Vec<Value> = BUILTIN_MODULES.iter().map(|s| h.new_str(*s)).collect();
226        h.new_array(items)
227    })
228}
229
230/// Compile a single JS expression and run it on the LIVE host, returning its
231/// completion value (mirrors `util`'s promisify factory path).
232fn run_completion(src: &str) -> Result<Value, String> {
233    let prog = crate::compile_completion(src)?;
234    let chunk = crate::load_merged(prog);
235    crate::host::run_chunk_on(chunk)
236}