Skip to main content

nodejs/
module.rs

1//! CommonJS module loader.
2//!
3//! Node's `require()` semantics, layered on the existing engine — no bespoke VM
4//! primitive. A `.js` file is wrapped in the canonical Node module wrapper
5//! `(function (exports, require, module, __dirname, __filename) { … })`, compiled
6//! through the ordinary `compile` → `load_merged` path to obtain the wrapper
7//! FUNCTION value, then `host::invoke`d with a fresh `module = { exports: {} }`.
8//! Whatever the body assigns to `module.exports` (or hangs off `exports`) is the
9//! module's value; it is cached by resolved absolute path so a second `require`
10//! of the same file returns the identical object and circular requires observe
11//! the partially-filled `exports`.
12//!
13//! Core modules (`fs`, `path`, `http`, …) short-circuit to their native
14//! `JsObj::Builtin` namespace (see `stdlib::resolve`) and are never read from
15//! disk. Everything else — relative paths, JSON files, and bare `node_modules`
16//! packages with their `package.json` `"exports"`/`"main"` and `index.js`
17//! fallbacks — resolves on the real filesystem and runs the genuine, unmodified
18//! source.
19//!
20//! Per-module `require` is a real JS closure that bakes in the defining module's
21//! directory, so a `require(...)` deferred inside a function called much later
22//! still resolves against the module that defined it (a single global
23//! "current dir" would resolve against the wrong module). The closure is minted
24//! by a one-time compiled factory (`FACTORY`) invoked with the directory string;
25//! it dispatches back into this loader through the `__cjs_require` /
26//! `__cjs_resolve` global native builtins.
27
28use std::cell::RefCell;
29use std::collections::HashMap;
30use std::path::{Path, PathBuf};
31
32use crate::host::{self, with_host, JsObj};
33use fusevm::Value;
34
35thread_local! {
36    /// Require cache: resolved absolute path → the `module` object (its `.exports`
37    /// is re-read on every hit, matching Node — `module.exports = X` reassignment
38    /// is observed by later requires).
39    static CACHE: RefCell<HashMap<PathBuf, Value>> = RefCell::new(HashMap::new());
40    /// Resolved `(specifier, from_dir)` to the CANONICAL absolute path it named,
41    /// so a repeated `require` of an already-loaded module costs one hash lookup
42    /// instead of walking `node_modules` and calling `canonicalize` again.
43    ///
44    /// Node keeps the same table (`Module._pathCache`) with the same
45    /// consequence: a file that appears after a specifier has already resolved
46    /// is not picked up by a later `require` of that specifier.
47    static PATH_CACHE: RefCell<HashMap<(String, PathBuf), PathBuf>> =
48        RefCell::new(HashMap::new());
49    /// Base directory the ENTRY script's top-level `require` resolves against
50    /// (the dir of `node app.js`, or cwd for `node -e`).
51    static ENTRY_DIR: RefCell<PathBuf> = RefCell::new(std::env::current_dir().unwrap_or_default());
52    /// The compiled per-module `require`-closure factory (see module docs),
53    /// minted once per host and reused for every module.
54    static FACTORY: RefCell<Option<Value>> = const { RefCell::new(None) };
55    /// The compiled synthetic-CallSite-array factory (for `Error.captureStackTrace`
56    /// under a custom `Error.prepareStackTrace`), minted once per host.
57    static CALLSITE_FACTORY: RefCell<Option<Value>> = const { RefCell::new(None) };
58}
59
60/// Clear all per-host loader state. Called from `host::reset_host` so a fresh
61/// eval (which rebuilds the heap) never reuses a stale heap handle.
62pub fn reset() {
63    CACHE.with(|c| c.borrow_mut().clear());
64    PATH_CACHE.with(|c| c.borrow_mut().clear());
65    FACTORY.with(|f| *f.borrow_mut() = None);
66    CALLSITE_FACTORY.with(|f| *f.borrow_mut() = None);
67    ENTRY_DIR.with(|d| *d.borrow_mut() = std::env::current_dir().unwrap_or_default());
68}
69
70/// The resolved filenames of every currently loaded module, in load order —
71/// the keys `require.cache` exposes.
72pub fn cache_keys() -> Vec<String> {
73    CACHE.with(|c| {
74        c.borrow()
75            .keys()
76            .map(|p| p.to_string_lossy().into_owned())
77            .collect()
78    })
79}
80
81/// The module object cached under the resolved filename `key`, if any.
82pub fn cache_get(key: &str) -> Option<Value> {
83    CACHE.with(|c| c.borrow().get(Path::new(key)).cloned())
84}
85
86/// Drop `key` from the module cache, so the next `require` of that file runs it
87/// again. This is what `delete require.cache[id]` must do to mean anything.
88pub fn cache_delete(key: &str) -> bool {
89    CACHE.with(|c| c.borrow_mut().remove(Path::new(key)).is_some())
90}
91
92/// Set the base directory the ENTRY script's `require` resolves against.
93pub fn set_entry_dir(dir: PathBuf) {
94    ENTRY_DIR.with(|d| *d.borrow_mut() = dir);
95}
96
97/// The ENTRY script's base directory.
98pub fn entry_dir() -> PathBuf {
99    ENTRY_DIR.with(|d| d.borrow().clone())
100}
101
102/// Install the CJS wrapper variables the ENTRY script sees.
103///
104/// A `require`d module already receives `exports`/`require`/`module`/`__dirname`
105/// /`__filename` as wrapper parameters (see `compile_wrapper`); the entry
106/// script used to receive none of them, so `typeof module` was `"undefined"`
107/// there and every UMD header took its browser branch. Node gives the entry
108/// script the same five names, with values that DEPEND ON THE ENTRY POINT:
109///
110/// | | `node f.js` | `node -e` | `node -` / piped |
111/// | --- | --- | --- | --- |
112/// | `__filename` | resolved abs path | `[eval]` | `[stdin]` |
113/// | `__dirname` | its directory | `.` | `.` |
114/// | `module.id` | `.` | `[eval]` | `[stdin]` |
115/// | `module.path` | its directory | `.` | `.` |
116///
117/// `module.filename` is `path.resolve(__filename)` in every case, so under `-e`
118/// it is `<cwd>/[eval]` — a path that does not exist, which is Node's own
119/// value. Measured on node v26.7.0.
120///
121/// `origin` is the `__filename` value: an absolute script path, or `[eval]` /
122/// `[stdin]` for the two source-on-the-command-line entry points.
123pub fn install_entry_globals(origin: &str) {
124    let from_file = origin != "[eval]" && origin != "[stdin]";
125    let (dirname, id) = if from_file {
126        let dir = Path::new(origin)
127            .parent()
128            .map(|p| p.to_string_lossy().into_owned())
129            .unwrap_or_else(|| ".".into());
130        (dir, ".".to_string())
131    } else {
132        (".".to_string(), origin.to_string())
133    };
134    let filename = crate::stdlib::path::resolve_one(origin);
135    let module = new_module(&id, &dirname, &filename);
136    let exports = module_exports(&module);
137    with_host(|h| {
138        let origin_str = h.new_str(origin.to_string());
139        let dirname = h.new_str(dirname);
140        h.set_global("__filename", origin_str);
141        h.set_global("__dirname", dirname);
142        h.set_global("module", module.clone());
143        // `require.main === module` in the entry script is the canonical
144        // "am I the program" test, and it read `undefined === <module>`
145        // because nothing ever set `main`. The ENTRY module is the value for
146        // every `require` in the process, not just this one, so it is recorded
147        // for the per-module closures too (`make_require` installs it).
148        // …but only when the program IS a module. `node -e` and a script on
149        // stdin run as a Script, not a CommonJS module, and node reports
150        // `require.main` as `undefined` for both.
151        if from_file {
152            h.set_builtin_static("require", "main", module.clone());
153        }
154        h.set_global("exports", exports.clone());
155        // Top-level `this`: the module's `exports` from a file (CommonJS
156        // module), `globalThis` from `-e` and from stdin (a Script). See
157        // `JsHost::set_top_this` for the measurements.
158        let top = if from_file {
159            exports
160        } else {
161            h.global_object()
162        };
163        h.set_top_this(top);
164    });
165}
166
167// ── resolution ───────────────────────────────────────────────────────────────
168
169/// Append `.ext` to a path (Node appends the extension, it does not replace an
170/// existing one — `foo.min` → `foo.min.js`, not `foo.js`).
171fn add_ext(p: &Path, ext: &str) -> PathBuf {
172    let mut s = p.as_os_str().to_owned();
173    s.push(".");
174    s.push(ext);
175    PathBuf::from(s)
176}
177
178/// `require`-as-a-file: `p`, then `p.js`, then `p.json`. `.node` native addons
179/// are skipped (unsupported), matching the resolution order minus that step.
180fn load_as_file(p: &Path) -> Option<PathBuf> {
181    if p.is_file() {
182        return Some(p.to_path_buf());
183    }
184    for ext in ["js", "json"] {
185        let cand = add_ext(p, ext);
186        if cand.is_file() {
187            return Some(cand);
188        }
189    }
190    None
191}
192
193/// `require`-as-a-directory: honor `package.json` `"exports"`/`"main"`, else
194/// `index.js` / `index.json`.
195fn load_as_dir(p: &Path) -> Option<PathBuf> {
196    let pkg = p.join("package.json");
197    if pkg.is_file() {
198        if let Some(main) = pkg_entry(&pkg) {
199            let mp = p.join(&main);
200            if let Some(f) = load_as_file(&mp).or_else(|| load_index(&mp)) {
201                return Some(f);
202            }
203        }
204    }
205    load_index(p)
206}
207
208/// `index.js` / `index.json` inside directory `p`.
209fn load_index(p: &Path) -> Option<PathBuf> {
210    for name in ["index.js", "index.json"] {
211        let cand = p.join(name);
212        if cand.is_file() {
213            return Some(cand);
214        }
215    }
216    None
217}
218
219/// The relative entry path a `package.json` declares: the `"."` (or main-string)
220/// `"exports"` target if present, else `"main"`. Only the common `"exports"`
221/// shapes are handled — a bare string, or an object whose `"."` maps to a string
222/// or to `{ "require"/"default"/"node": "…" }`. Anything more exotic falls back
223/// to `"main"`, then to the directory's `index.js`.
224fn pkg_entry(pkg: &Path) -> Option<String> {
225    let text = std::fs::read_to_string(pkg).ok()?;
226    let json: serde_json::Value = serde_json::from_str(&text).ok()?;
227    if let Some(e) = exports_main(json.get("exports")) {
228        return Some(strip_dot_slash(&e));
229    }
230    json.get("main")
231        .and_then(|m| m.as_str())
232        .map(strip_dot_slash)
233}
234
235/// Resolve the `"exports"` field down to a single relative path for the `"."`
236/// (package root) entry, across the shapes CommonJS packages commonly ship.
237fn exports_main(exports: Option<&serde_json::Value>) -> Option<String> {
238    let exports = exports?;
239    // `"exports": "./index.js"` — a bare string is the `"."` target.
240    if let Some(s) = exports.as_str() {
241        return Some(s.to_string());
242    }
243    let obj = exports.as_object()?;
244    // Either a subpath map keyed by `"."`, or a bare conditions map at the root.
245    let target = obj.get(".").unwrap_or(exports);
246    condition_target(target)
247}
248
249/// Reduce an `"exports"` target — a string, or a conditions object — to a path,
250/// preferring the CommonJS-relevant conditions (`require`/`node`/`default`).
251fn condition_target(target: &serde_json::Value) -> Option<String> {
252    if let Some(s) = target.as_str() {
253        return Some(s.to_string());
254    }
255    let obj = target.as_object()?;
256    for cond in ["require", "node", "default"] {
257        if let Some(v) = obj.get(cond) {
258            if let Some(s) = condition_target(v) {
259                return Some(s);
260            }
261        }
262    }
263    None
264}
265
266/// Drop a leading `./` from a package-relative path.
267fn strip_dot_slash(s: &str) -> String {
268    s.strip_prefix("./").unwrap_or(s).to_string()
269}
270
271/// Resolve `spec` (already known to be a bare specifier) by walking parent
272/// directories from `from_dir`, checking `<dir>/node_modules/<spec>` at each
273/// level with the file-then-directory rules.
274fn resolve_bare(spec: &str, from_dir: &Path) -> Option<PathBuf> {
275    let mut dir = Some(from_dir);
276    while let Some(d) = dir {
277        // Skip a `node_modules/node_modules` descent.
278        if d.file_name().is_some_and(|n| n == "node_modules") {
279            dir = d.parent();
280            continue;
281        }
282        let candidate = d.join("node_modules").join(spec);
283        if let Some(f) = load_as_file(&candidate).or_else(|| load_as_dir(&candidate)) {
284            return Some(f);
285        }
286        dir = d.parent();
287    }
288    None
289}
290
291/// Resolve `spec` relative to `from_dir` to an absolute file path, or `None` if
292/// no file matches (core modules are handled earlier, by the caller).
293pub fn resolve(spec: &str, from_dir: &Path) -> Option<PathBuf> {
294    let is_relative =
295        spec.starts_with("./") || spec.starts_with("../") || spec == "." || spec == "..";
296    let is_absolute = spec.starts_with('/');
297    if is_relative || is_absolute {
298        // NORMALIZED, not merely joined: `Path::join` keeps the `.` in
299        // `<dir>/./d.js`, and that string is what `require.resolve` returns and
300        // what keys the module cache — so `./d.js` and `d.js` from the same
301        // directory would be two cache entries of one file.
302        let joined = if is_absolute {
303            spec.to_string()
304        } else {
305            from_dir.join(spec).to_string_lossy().into_owned()
306        };
307        let base = PathBuf::from(crate::stdlib::path::resolve_one(&joined));
308        return load_as_file(&base).or_else(|| load_as_dir(&base));
309    }
310    resolve_bare(spec, from_dir)
311}
312
313// ── loading / execution ──────────────────────────────────────────────────────
314
315/// `require(spec)` from `from_dir`: the single entry point shared by the
316/// top-level `require` builtin and the per-module `__cjs_require`. Returns the
317/// module's exports value.
318pub fn require(spec: &str, from_dir: &Path) -> Result<Value, String> {
319    // Core module: the native namespace value, never a file (mirrors the legacy
320    // `require` path — `require('events')` yields the EventEmitter ctor, etc.).
321    if let Some(v) = crate::stdlib::data_module(spec) {
322        return Ok(v);
323    }
324    if let Some(ns) = crate::stdlib::resolve(spec) {
325        return Ok(with_host(|h| h.alloc(JsObj::Builtin(ns.to_string()))));
326    }
327    // Resolution is filesystem work — a `node_modules` walk, a set of extension
328    // probes, then `canonicalize` — and a program that requires the same
329    // specifier in a loop paid all of it on every call even though the module
330    // itself was already loaded and cached. The answer is memoized per
331    // `(specifier, from_dir)`.
332    let key = (spec.to_string(), from_dir.to_path_buf());
333    if let Some(hit) = PATH_CACHE.with(|c| c.borrow().get(&key).cloned()) {
334        return load_file(&hit);
335    }
336    let path = resolve(spec, from_dir).ok_or_else(|| {
337        crate::host::plain_coded_error(
338            "Error",
339            "MODULE_NOT_FOUND",
340            &format!("Cannot find module '{spec}'"),
341        )
342    })?;
343    // A canonical absolute key so the same file required via different relative
344    // specifiers shares one cache entry.
345    let path = std::fs::canonicalize(&path).unwrap_or(path);
346    PATH_CACHE.with(|c| c.borrow_mut().insert(key, path.clone()));
347    load_file(&path)
348}
349
350/// Load the resolved absolute file `path` (`.json` parses to its value; `.js`
351/// runs through the module wrapper) and return its exports, caching by path.
352fn load_file(path: &Path) -> Result<Value, String> {
353    if let Some(cached) = CACHE.with(|c| c.borrow().get(path).cloned()) {
354        // Re-read `.exports` — a cached module may have reassigned it.
355        return Ok(module_exports(&cached));
356    }
357    if path.extension().is_some_and(|e| e == "json") {
358        let text = std::fs::read_to_string(path)
359            .map_err(|e| format!("cannot read {}: {e}", path.display()))?;
360        let src = with_host(|h| h.new_str(text));
361        let val = crate::builtins::call_builtin_function("JSON.parse", vec![src])?;
362        // A JSON module's value IS the parsed data; cache a synthetic wrapper so
363        // repeated requires share it.
364        let abs = path.to_string_lossy().into_owned();
365        let dir = path.parent().unwrap_or(Path::new("")).to_string_lossy();
366        let module = new_module(&abs, &dir, &abs);
367        with_host(|h| {
368            if let Some(JsObj::Object(p)) = h.get_mut(&module) {
369                p.insert("exports".to_string(), val.clone());
370                p.insert("loaded".to_string(), Value::Bool(true));
371            }
372        });
373        CACHE.with(|c| c.borrow_mut().insert(path.to_path_buf(), module));
374        return Ok(val);
375    }
376
377    let source = std::fs::read_to_string(path)
378        .map_err(|e| format!("cannot read {}: {e}", path.display()))?;
379    let dir = path.parent().map(Path::to_path_buf).unwrap_or_default();
380
381    // Compile the Node module wrapper to obtain the wrapper FUNCTION value.
382    // A compile error is annotated with the offending file (Node does likewise).
383    let wrapper = compile_wrapper(&source)
384        .map_err(|e| format!("{e}\n    while loading {}", path.display()))?;
385
386    // The `module` object, plus the aliases the wrapper receives. A required
387    // module's `id` IS its absolute filename (only the entry module's is `.`).
388    let abs = path.to_string_lossy().into_owned();
389    let module = new_module(&abs, &dir.to_string_lossy(), &abs);
390    let exports = module_exports(&module);
391    let require_fn = make_require(&dir)?;
392    let (dirname, filename) = with_host(|h| {
393        (
394            h.new_str(dir.to_string_lossy().to_string()),
395            h.new_str(path.to_string_lossy().to_string()),
396        )
397    });
398
399    // Cache BEFORE running so a circular `require` back to this module observes
400    // the partial `exports`.
401    CACHE.with(|c| c.borrow_mut().insert(path.to_path_buf(), module.clone()));
402
403    host::invoke(
404        &wrapper,
405        vec![exports, require_fn, module.clone(), dirname, filename],
406        None,
407    )?;
408    mark_loaded(&module);
409
410    Ok(module_exports(&module))
411}
412
413/// A fresh `module` object: `{ id, path, exports, filename, loaded, children,
414/// paths }`, in that key order.
415///
416/// The order is observable (`Object.keys(module)`) and this is node's. Only
417/// `exports` used to be present, so a module reading `module.id` or
418/// `module.filename` — both of which a bundler-emitted or `__dirname`-avoiding
419/// package does — got `undefined`.
420///
421/// `paths` is the `node_modules` chain from `dir` up to the root, the same walk
422/// `require` performs to resolve a bare specifier. `loaded` starts `false`; it
423/// is set once the body returns.
424fn new_module(id: &str, dir: &str, filename: &str) -> Value {
425    let mut node_modules: Vec<String> = Vec::new();
426    let mut cur = Some(Path::new(dir));
427    while let Some(d) = cur.filter(|d| !d.as_os_str().is_empty()) {
428        node_modules.push(d.join("node_modules").to_string_lossy().into_owned());
429        cur = d.parent();
430    }
431    with_host(|h| {
432        let exports = h.new_object(indexmap::IndexMap::new());
433        let mut props = indexmap::IndexMap::new();
434        props.insert("id".to_string(), h.new_str(id.to_string()));
435        props.insert("path".to_string(), h.new_str(dir.to_string()));
436        props.insert("exports".to_string(), exports);
437        props.insert("filename".to_string(), h.new_str(filename.to_string()));
438        props.insert("loaded".to_string(), Value::Bool(false));
439        // `module.parent` is long deprecated but PRESENT: node reports `null`
440        // for a file the loader reached directly, and code still tests
441        // `if (!module.parent)` to detect "run as the entry point". The key was
442        // missing entirely, so `'parent' in module` was false.
443        let null = h.null();
444        props.insert("parent".to_string(), null);
445        let children = h.new_array(Vec::new());
446        props.insert("children".to_string(), children);
447        let paths: Vec<Value> = node_modules.into_iter().map(|p| h.new_str(p)).collect();
448        let paths = h.new_array(paths);
449        props.insert("paths".to_string(), paths);
450        let obj = h.new_object(props);
451        // `parent` is present but NOT enumerable: `Object.keys(module)` does not
452        // list it, while `'parent' in module` is true. Adding it as an ordinary
453        // property changed the key order the es_parity module tests pin.
454        h.hide_prop(&obj, "parent");
455        obj
456    })
457}
458
459/// The directories `require.resolve` would search for `spec`, in order.
460///
461/// A RELATIVE specifier resolves against one directory — the requiring one — so
462/// node reports just that. A bare package name walks the `node_modules` chain
463/// up to the root.
464pub fn resolve_paths(spec: &str, from_dir: &std::path::Path) -> Vec<String> {
465    if spec.starts_with('.') || spec.starts_with('/') {
466        return vec![from_dir.to_string_lossy().into_owned()];
467    }
468    let mut out = Vec::new();
469    let mut cur = Some(from_dir);
470    while let Some(d) = cur {
471        out.push(d.join("node_modules").to_string_lossy().into_owned());
472        cur = d.parent();
473    }
474    out
475}
476
477/// Flip `module.loaded` once the body has run, as Node's loader does.
478fn mark_loaded(module: &Value) {
479    with_host(|h| {
480        if let Some(JsObj::Object(p)) = h.get_mut(module) {
481            p.insert("loaded".to_string(), Value::Bool(true));
482        }
483    });
484}
485
486/// Read `module.exports` (falls back to `undefined` for a malformed module).
487fn module_exports(module: &Value) -> Value {
488    with_host(|h| match h.get(module) {
489        Some(JsObj::Object(p)) => p.get("exports").cloned().unwrap_or(Value::Undef),
490        _ => Value::Undef,
491    })
492}
493
494/// Compile `<source>` wrapped in the Node module wrapper and return the wrapper
495/// FUNCTION value.
496fn compile_wrapper(source: &str) -> Result<Value, String> {
497    // A module may open with a hashbang line, which is only a comment at the
498    // start of the TEXT; inside the wrapper it would not be, so it becomes a
499    // `//` comment of the same length.
500    let source = match source.strip_prefix("#!") {
501        Some(rest) => format!("//{rest}"),
502        None => source.to_string(),
503    };
504    // A trailing newline before `})` guards a source ending in a `//` comment.
505    eval_binding(&format!(
506        "(function (exports, require, module, __dirname, __filename) {{\n{source}\n}})"
507    ))
508}
509
510/// Compile+run a single JS expression on the LIVE host — no reset, no
511/// event-loop drain — and return its value.
512///
513/// This delegates to `crate::eval_in_global_scope`, the frontend's one
514/// runtime-source evaluator, and two things changed with it. The wrapper used to
515/// be compiled as `var __cjs_wN = (function …);` and read back out of the scope
516/// with `read_name`, because a bare expression statement pops its value; a
517/// completion-value compile returns the expression directly, so the capture
518/// variable and its uniquifying counter are gone. And the run used to happen on
519/// the CALLER's frame, which let a module body see the locals of whatever
520/// function called `require`: measured against node v26.7.0,
521/// `function outer(){ let secret = 1; return require('./m.js'); }` with `m.js` =
522/// `module.exports = typeof secret` is `"undefined"` there and was `"number"`
523/// here.
524fn eval_binding(src: &str) -> Result<Value, String> {
525    crate::eval_in_global_scope(src)
526}
527
528/// Build a per-module `require` closure bound to `dir` (see module docs).
529fn make_require(dir: &Path) -> Result<Value, String> {
530    let factory = factory()?;
531    let dir_str = with_host(|h| h.new_str(dir.to_string_lossy().to_string()));
532    let req = host::invoke(&factory, vec![dir_str], None)?;
533    // Every `require` in the process reports the same `main` — the ENTRY
534    // module — which is what `require.main === module` tests against.
535    if let Some(main) = with_host(|h| h.builtin_static("require", "main")) {
536        // `req` is a FUNCTION value, so its properties live in the fn-prop side
537        // table, not in an object property map.
538        with_host(|h| h.set_fn_prop(&req, "main", main));
539    }
540    Ok(req)
541}
542
543/// The one-time compiled `require`-closure factory. `require.resolve` /
544/// `require.cache` are provided since some packages read them.
545fn factory() -> Result<Value, String> {
546    if let Some(f) = FACTORY.with(|f| f.borrow().clone()) {
547        return Ok(f);
548    }
549    let src = "(function (__cjs_dir) {\n\
550        var req = function (spec) { return __cjs_require(spec, __cjs_dir); };\n\
551        req.resolve = function (spec) { return __cjs_resolve(spec, __cjs_dir); };\n\
552        req.cache = __cjs_cache;\n\
553        req.main = undefined;\n\
554        req.extensions = {};\n\
555        return req;\n\
556    });";
557    let f = eval_binding(src)?;
558    FACTORY.with(|c| *c.borrow_mut() = Some(f.clone()));
559    Ok(f)
560}
561
562/// An array of `depth` synthetic V8 CallSite objects for `Error.captureStackTrace`.
563/// Stack-introspection packages (e.g. `depd`) set `Error.prepareStackTrace` to a
564/// function that receives this array; the getters return neutral placeholders (no
565/// real frame info is available), which is enough for those packages to build
566/// their deprecation sites without throwing.
567pub fn callsite_stack(depth: usize) -> Result<Value, String> {
568    let factory = if let Some(f) = CALLSITE_FACTORY.with(|f| f.borrow().clone()) {
569        f
570    } else {
571        let src = "(function (n) {\n\
572            var a = [];\n\
573            for (var i = 0; i < n; i++) {\n\
574                a.push({\n\
575                    getFileName: function () { return null; },\n\
576                    getLineNumber: function () { return 0; },\n\
577                    getColumnNumber: function () { return 0; },\n\
578                    getFunctionName: function () { return null; },\n\
579                    getMethodName: function () { return null; },\n\
580                    getTypeName: function () { return null; },\n\
581                    getThis: function () { return undefined; },\n\
582                    isNative: function () { return false; },\n\
583                    isEval: function () { return false; },\n\
584                    toString: function () { return '<anonymous>'; }\n\
585                });\n\
586            }\n\
587            return a;\n\
588        });";
589        let f = eval_binding(src)?;
590        CALLSITE_FACTORY.with(|c| *c.borrow_mut() = Some(f.clone()));
591        f
592    };
593    host::invoke(&factory, vec![Value::Float(depth as f64)], None)
594}