use std::sync::Arc;
use rquickjs::{Ctx, Object};
use super::registry::ModuleRegistry;
pub trait RequireHook: Send + Sync {
fn namespace<'js>(&self, ctx: &Ctx<'js>, specifier: &str) -> rquickjs::Result<Option<Object<'js>>>;
fn is_builtin(&self, _specifier: &str) -> bool {
false
}
}
pub fn install<'js>(
ctx: &Ctx<'js>,
registry: Arc<ModuleRegistry>,
hooks: Vec<Arc<dyn RequireHook>>,
) -> rquickjs::Result<()> {
let hooks = Arc::new(hooks);
let names = registry.names();
let (reg, hk) = (Arc::clone(®istry), Arc::clone(&hooks));
let require = rquickjs::Function::new(
ctx.clone(),
move |ctx: Ctx<'js>, specifier: String| -> rquickjs::Result<Object<'js>> {
for hook in hk.iter() {
if let Some(ns) = hook.namespace(&ctx, &specifier)? {
return Ok(ns);
}
}
match reg.namespace(&ctx, &specifier)? {
Some(ns) => Ok(ns),
None => Err(rquickjs::Exception::throw_type(
&ctx,
&format!(
"require('{specifier}') is not available: only the runtime's native modules ({}) can be require()d",
names.join(", ")
),
)),
}
},
)?;
let (reg, hk) = (registry, hooks);
let resolve = rquickjs::Function::new(ctx.clone(), move |ctx: Ctx<'js>, specifier: String| {
if reg.serves(&specifier) || hk.iter().any(|h| h.is_builtin(&specifier)) {
return Ok(specifier);
}
let base = crate::source_map::caller_source_file(&ctx)
.and_then(|file| file.parent().map(std::path::Path::to_path_buf))
.or_else(|| std::env::current_dir().ok())
.unwrap_or_else(|| std::path::PathBuf::from("."));
ferrijs_std::permissions::check_read(&ctx, &base)?;
ferrijs_std::node::require_resolve::resolve(&base, &specifier)
.map(|path| path.to_string_lossy().into_owned())
.map_err(|message| rquickjs::Exception::throw_message(&ctx, &message))
})?;
require.set("resolve", resolve)?;
ctx.globals().set("require", require)
}