use std::{cell::RefCell, ptr::NonNull};
use napi::{
Either, Env,
bindgen_prelude::{Array, Object, 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, with_compilation};
#[napi]
pub struct Dependency {
pub(crate) compilation_id: Option<CompilationId>,
pub(crate) dependency_id: DependencyId,
pub(crate) dependency: NonNull<dyn rspack_core::Dependency>,
}
impl Dependency {
fn with_ref<R>(
&mut self,
f: impl FnOnce(&dyn rspack_core::Dependency, Option<&Compilation>) -> napi::Result<R>,
) -> napi::Result<R> {
if let Some(compilation_id) = self.compilation_id {
with_compilation(compilation_id, |compilation| {
let module_graph = compilation.get_module_graph();
if let Some(dependency) = internal::try_dependency_by_id(module_graph, &self.dependency_id)
{
f(dependency, 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 {
f(unsafe { self.dependency.as_ref() }, None)
}
}
}
#[napi]
impl Dependency {
#[napi(
getter,
js_name = "_parentModule",
ts_return_type = "Module | undefined"
)]
pub fn parent_module(&mut self) -> napi::Result<Option<ModuleObject>> {
self.with_ref(|dependency, compilation| {
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<String> {
self.with_ref(|dependency, _| Ok(dependency.dependency_type().as_str().to_string()))
}
#[napi(getter)]
pub fn category(&mut self) -> napi::Result<String> {
self.with_ref(|dependency, _| Ok(dependency.category().as_str().to_string()))
}
#[napi(getter)]
pub fn request(&mut self) -> napi::Result<napi::Either<String, ()>> {
self.with_ref(|dependency, _| {
Ok(match dependency.as_module_dependency() {
Some(dep) => napi::Either::A(dep.request().to_string()),
None => napi::Either::B(()),
})
})
}
#[napi(getter, ts_return_type = "Record<string, string> | undefined")]
pub fn attributes<'a>(&mut self, env: &'a Env) -> napi::Result<Either<Object<'a>, ()>> {
self.with_ref(|dependency, _| {
Ok(match dependency.get_attributes() {
Some(attributes) => {
let mut object = Object::new(env)?;
for (key, value) in attributes.iter() {
object.set(key, value)?;
}
Either::A(object)
}
None => Either::B(()),
})
})
}
#[napi(getter)]
pub fn critical(&mut self) -> napi::Result<bool> {
self.with_ref(|dependency, _| {
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<()> {
self.with_ref(|dependency, _| {
if let Some(dep) = dependency.as_context_dependency()
&& !val
{
dep.set_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>, ()>> {
self.with_ref(|dependency, compilation| {
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(()),
})
})
}
#[napi(getter)]
pub fn loc(&mut self) -> napi::Result<Option<crate::location::DependencyLocation>> {
self.with_ref(|dependency, _| Ok(dependency.loc().map(|loc| loc.into())))
}
}
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,
registered_compilation_id: Option<CompilationId>,
}
impl DependencyWrapper {
pub fn new<'a>(
dependency: &'a dyn rspack_core::Dependency,
compilation_id: CompilationId,
compilation: Option<&Compilation>,
) -> Self {
let dependency_id = *dependency.id();
let dependency_ptr = unsafe {
std::mem::transmute::<
*const (dyn rspack_core::Dependency + 'a),
*mut (dyn rspack_core::Dependency + 'static),
>(dependency)
};
let dependency = unsafe { NonNull::new_unchecked(dependency_ptr) };
Self {
dependency_id,
dependency,
compilation_id,
registered_compilation_id: compilation.map(Compilation::id),
}
}
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_id = val.registered_compilation_id;
instance.dependency = val.dependency;
ToNapiValue::to_napi_value(env, r)
}
std::collections::hash_map::Entry::Vacant(vacant_entry) => {
let js_dependency = Dependency {
compilation_id: val.registered_compilation_id,
dependency_id: val.dependency_id,
dependency: val.dependency,
};
let r = vacant_entry.insert(OneShotInstanceRef::new(env, js_dependency)?);
ToNapiValue::to_napi_value(env, r)
}
}
})
}
}
}