use std::{cell::RefCell, ptr::NonNull};
use napi::{
Either, Env,
bindgen_prelude::{Array, ToNapiValue},
};
use napi_derive::napi;
use rspack_core::{Compilation, CompilationId, DependencyId, internal};
use rspack_napi::OneShotInstanceRef;
use rspack_plugin_javascript::dependency::{
CommonJsExportRequireDependency, ESMExportImportedSpecifierDependency,
ESMImportSpecifierDependency,
};
use rustc_hash::FxHashMap as HashMap;
use crate::module::ModuleObject;
#[napi]
pub struct Dependency {
pub(crate) compilation: Option<NonNull<Compilation>>,
pub(crate) dependency_id: DependencyId,
pub(crate) dependency: NonNull<dyn rspack_core::Dependency>,
}
impl Dependency {
fn as_ref(&mut self) -> napi::Result<(&dyn rspack_core::Dependency, Option<&Compilation>)> {
if let Some(compilation) = self.compilation {
let compilation = unsafe { compilation.as_ref() };
let module_graph = compilation.get_module_graph();
if let Some(dependency) = internal::try_dependency_by_id(module_graph, &self.dependency_id) {
self.dependency = {
#[allow(clippy::unwrap_used)]
NonNull::new(
(&**dependency) as *const dyn rspack_core::Dependency
as *mut dyn rspack_core::Dependency,
)
.unwrap()
};
Ok((unsafe { self.dependency.as_ref() }, Some(compilation)))
} else {
Err(napi::Error::from_reason(format!(
"Unable to access dependency with id = {:?} now. The dependency have been removed on the Rust side.",
self.dependency_id
)))
}
} else {
Ok((unsafe { self.dependency.as_ref() }, None))
}
}
fn as_mut(&mut self) -> napi::Result<&mut dyn rspack_core::Dependency> {
Ok(unsafe { self.dependency.as_mut() })
}
}
#[napi]
impl Dependency {
#[napi(
getter,
js_name = "_parentModule",
ts_return_type = "Module | undefined"
)]
pub fn parent_module(&mut self) -> napi::Result<Option<ModuleObject>> {
let (dependency, compilation) = self.as_ref()?;
let Some(compilation) = compilation else {
return Ok(None);
};
let module_graph = compilation.get_module_graph();
let parent_module = module_graph
.get_parent_module(dependency.id())
.and_then(|m| compilation.module_by_identifier(m))
.map(|m| ModuleObject::with_ref(m.as_ref(), compilation.compiler_id()));
Ok(parent_module)
}
#[napi(getter)]
pub fn get_type(&mut self) -> napi::Result<&str> {
let (dependency, _) = self.as_ref()?;
Ok(dependency.dependency_type().as_str())
}
#[napi(getter)]
pub fn category(&mut self) -> napi::Result<&str> {
let (dependency, _) = self.as_ref()?;
Ok(dependency.category().as_str())
}
#[napi(getter)]
pub fn request(&mut self) -> napi::Result<napi::Either<&str, ()>> {
let (dependency, _) = self.as_ref()?;
Ok(match dependency.as_module_dependency() {
Some(dep) => napi::Either::A(dep.request()),
None => napi::Either::B(()),
})
}
#[napi(getter)]
pub fn critical(&mut self) -> napi::Result<bool> {
let (dependency, _) = self.as_ref()?;
Ok(match dependency.as_context_dependency() {
Some(dep) => dep.critical().is_some(),
None => false,
})
}
#[napi(setter)]
pub fn set_critical(&mut self, val: bool) -> napi::Result<()> {
let dependency = self.as_mut()?;
if let Some(dep) = dependency.as_context_dependency_mut() {
let critical = dep.critical_mut();
if !val {
*critical = None;
}
}
Ok(())
}
#[napi(getter, ts_return_type = "Array<string> | undefined")]
pub fn ids<'a>(&mut self, env: &'a Env) -> napi::Result<Either<Array<'a>, ()>> {
let (dependency, compilation) = self.as_ref()?;
Ok(match compilation {
Some(compilation) => {
let module_graph = compilation.get_module_graph();
if let Some(dependency) = dependency.downcast_ref::<CommonJsExportRequireDependency>() {
let ids = dependency.get_ids(module_graph);
let mut arr = env.create_array(ids.len() as u32)?;
for (i, v) in ids.iter().enumerate() {
arr.set(i as u32, v.as_str())?;
}
Either::A(arr)
} else if let Some(dependency) =
dependency.downcast_ref::<ESMExportImportedSpecifierDependency>()
{
let ids = dependency.get_ids(module_graph);
let mut arr = env.create_array(ids.len() as u32)?;
for (i, v) in ids.iter().enumerate() {
arr.set(i as u32, v.as_str())?;
}
Either::A(arr)
} else if let Some(dependency) = dependency.downcast_ref::<ESMImportSpecifierDependency>() {
let ids = dependency.get_ids(module_graph);
let mut arr = env.create_array(ids.len() as u32)?;
for (i, v) in ids.iter().enumerate() {
arr.set(i as u32, v.as_str())?;
}
Either::A(arr)
} else {
Either::B(())
}
}
None => Either::B(()),
})
}
}
type DependencyInstanceRefs = HashMap<DependencyId, OneShotInstanceRef<Dependency>>;
type DependencyInstanceRefsByCompilationId =
RefCell<HashMap<CompilationId, DependencyInstanceRefs>>;
thread_local! {
static DEPENDENCY_INSTANCE_REFS: DependencyInstanceRefsByCompilationId = Default::default();
}
pub struct DependencyWrapper {
dependency_id: DependencyId,
dependency: NonNull<dyn rspack_core::Dependency>,
compilation_id: CompilationId,
compilation: Option<NonNull<Compilation>>,
}
impl DependencyWrapper {
pub fn new(
dependency: &dyn rspack_core::Dependency,
compilation_id: CompilationId,
compilation: Option<&Compilation>,
) -> Self {
let dependency_id = *dependency.id();
#[allow(clippy::unwrap_used)]
Self {
dependency_id,
dependency: NonNull::new(
dependency as *const dyn rspack_core::Dependency as *mut dyn rspack_core::Dependency,
)
.unwrap(),
compilation_id,
compilation: compilation
.map(|c| NonNull::new(c as *const Compilation as *mut Compilation).unwrap()),
}
}
pub fn cleanup_last_compilation(compilation_id: CompilationId) {
DEPENDENCY_INSTANCE_REFS.with(|refs| {
let mut refs_by_compilation_id = refs.borrow_mut();
refs_by_compilation_id.remove(&compilation_id)
});
}
}
impl ToNapiValue for DependencyWrapper {
unsafe fn to_napi_value(
env: napi::sys::napi_env,
val: Self,
) -> napi::Result<napi::sys::napi_value> {
unsafe {
DEPENDENCY_INSTANCE_REFS.with(|refs| {
let mut refs_by_compilation_id = refs.borrow_mut();
let entry = refs_by_compilation_id.entry(val.compilation_id);
let refs = match entry {
std::collections::hash_map::Entry::Occupied(entry) => entry.into_mut(),
std::collections::hash_map::Entry::Vacant(entry) => {
let refs = HashMap::default();
entry.insert(refs)
}
};
match refs.entry(val.dependency_id) {
std::collections::hash_map::Entry::Occupied(mut occupied_entry) => {
let r = occupied_entry.get_mut();
let instance = &mut **r;
instance.compilation = val.compilation;
instance.dependency = val.dependency;
ToNapiValue::to_napi_value(env, r)
}
std::collections::hash_map::Entry::Vacant(vacant_entry) => {
let js_dependency = Dependency {
compilation: val.compilation,
dependency_id: val.dependency_id,
dependency: val.dependency,
};
let r = vacant_entry.insert(OneShotInstanceRef::new(env, js_dependency)?);
ToNapiValue::to_napi_value(env, r)
}
}
})
}
}
}