use rspack_cacheable::{cacheable, cacheable_dyn, with::AsPreset};
use rspack_collections::{IdentifierMap, IdentifierSet};
use rspack_core::{
AsContextDependency, AsModuleDependency, Dependency, DependencyCategory,
DependencyCodeGeneration, DependencyId, DependencyLocation, DependencyRange, DependencyTemplate,
DependencyTemplateType, DependencyType, ESMExportInitFragment, EvaluatedInlinableValue,
ExportNameOrSpec, ExportSpec, ExportsInfoArtifact, ExportsInfoGetter, ExportsOfExportsSpec,
ExportsSpec, GetUsedNameParam, LazyUntil, ModuleGraph, ModuleGraphCacheArtifact,
PrefetchExportsInfoMode, SideEffectsStateArtifact, TSEnumValue, TemplateContext,
TemplateReplaceSource, UsedName,
};
use swc_core::ecma::atoms::Atom;
use crate::is_export_inlined;
#[cacheable]
#[derive(Debug, Clone)]
pub struct ESMExportSpecifierDependency {
id: DependencyId,
range: DependencyRange,
loc: Option<DependencyLocation>,
#[cacheable(with=AsPreset)]
name: Atom,
#[cacheable(with=AsPreset)]
value: Atom, inline: Option<EvaluatedInlinableValue>,
enum_value: Option<TSEnumValue>,
}
impl ESMExportSpecifierDependency {
pub fn new(
name: Atom,
value: Atom,
inline: Option<EvaluatedInlinableValue>,
enum_value: Option<TSEnumValue>,
range: DependencyRange,
loc: Option<DependencyLocation>,
) -> Self {
Self {
name,
value,
inline,
enum_value,
range,
loc,
id: DependencyId::new(),
}
}
}
#[cacheable_dyn]
impl Dependency for ESMExportSpecifierDependency {
fn id(&self) -> &DependencyId {
&self.id
}
fn loc(&self) -> Option<DependencyLocation> {
self.loc.clone()
}
fn category(&self) -> &DependencyCategory {
&DependencyCategory::Esm
}
fn dependency_type(&self) -> &DependencyType {
&DependencyType::EsmExportSpecifier
}
fn get_exports(
&self,
_mg: &ModuleGraph,
_mg_cache: &ModuleGraphCacheArtifact,
_exports_info_artifact: &ExportsInfoArtifact,
) -> Option<ExportsSpec> {
Some(ExportsSpec {
exports: ExportsOfExportsSpec::Names(vec![ExportNameOrSpec::ExportSpec(ExportSpec {
name: self.name.clone(),
inlinable: self.inline.clone(),
exports: self.enum_value.as_ref().map(|enum_value| {
enum_value
.iter()
.map(|(enum_name, enum_member)| {
ExportNameOrSpec::ExportSpec(ExportSpec {
name: enum_name.clone(),
inlinable: enum_member.clone(),
can_mangle: Some(false),
..Default::default()
})
})
.collect()
}),
..Default::default()
})]),
priority: Some(1),
can_mangle: None,
terminal_binding: Some(true),
from: None,
dependencies: None,
hide_export: None,
exclude_exports: None,
})
}
fn get_module_evaluation_side_effects_state(
&self,
_module_graph: &rspack_core::ModuleGraph,
_module_graph_cache: &ModuleGraphCacheArtifact,
_side_effects_state_artifact: &SideEffectsStateArtifact,
_module_chain: &mut IdentifierSet,
_connection_state_cache: &mut IdentifierMap<rspack_core::ConnectionState>,
) -> rspack_core::ConnectionState {
rspack_core::ConnectionState::Active(false)
}
fn could_affect_referencing_module(&self) -> rspack_core::AffectType {
rspack_core::AffectType::False
}
fn lazy(&self) -> Option<LazyUntil> {
Some(LazyUntil::Local(self.name.clone()))
}
}
impl AsModuleDependency for ESMExportSpecifierDependency {}
#[cacheable_dyn]
impl DependencyCodeGeneration for ESMExportSpecifierDependency {
fn dependency_template(&self) -> Option<DependencyTemplateType> {
Some(ESMExportSpecifierDependencyTemplate::template_type())
}
}
impl AsContextDependency for ESMExportSpecifierDependency {}
#[cacheable]
#[derive(Debug, Clone, Default)]
pub struct ESMExportSpecifierDependencyTemplate;
impl ESMExportSpecifierDependencyTemplate {
pub fn template_type() -> DependencyTemplateType {
DependencyTemplateType::Dependency(DependencyType::EsmExportSpecifier)
}
}
impl DependencyTemplate for ESMExportSpecifierDependencyTemplate {
fn render(
&self,
dep: &dyn DependencyCodeGeneration,
_source: &mut TemplateReplaceSource,
code_generatable_context: &mut TemplateContext,
) {
let dep = dep
.as_any()
.downcast_ref::<ESMExportSpecifierDependency>()
.expect(
"ESMExportSpecifierDependencyTemplate should only be used for ESMExportSpecifierDependency",
);
let TemplateContext {
init_fragments,
compilation,
module,
runtime,
concatenation_scope,
..
} = code_generatable_context;
if let Some(scope) = concatenation_scope {
scope.register_export(dep.name.clone(), dep.value.to_string());
return;
}
let module_graph = compilation.get_module_graph();
let module = module_graph
.module_by_identifier(&module.identifier())
.expect("should have module graph module");
if let Some(enum_value) = &dep.enum_value {
let all_enum_member_inlined = enum_value.iter().all(|(enum_key, enum_member)| {
if enum_member.is_none() {
return false;
}
let export_name = &[dep.name.clone(), enum_key.clone()];
is_export_inlined(
&compilation.exports_info_artifact,
&module.identifier(),
export_name,
*runtime,
)
});
if all_enum_member_inlined {
return;
}
}
let exports_info = compilation
.exports_info_artifact
.get_prefetched_exports_info(&module.identifier(), PrefetchExportsInfoMode::Default);
let Some(used_name) = ExportsInfoGetter::get_used_name(
GetUsedNameParam::WithNames(&exports_info),
*runtime,
std::slice::from_ref(&dep.name),
) else {
return;
};
let used_name = match used_name {
UsedName::Normal(vec) => {
vec[0].clone()
}
UsedName::Inlined(_) => return,
};
init_fragments.push(Box::new(ESMExportInitFragment::new(
module.get_exports_argument(),
vec![(used_name, dep.value.clone())],
)));
}
}