use rspack_cacheable::{
cacheable, cacheable_dyn,
with::{AsPreset, AsVec},
};
use rspack_core::{
AsContextDependency, AsModuleDependency, Dependency, DependencyCategory,
DependencyCodeGeneration, DependencyId, DependencyRange, DependencyTemplate,
DependencyTemplateType, DependencyType, ExportNameOrSpec, ExportSpec, ExportsInfoArtifact,
ExportsInfoGetter, ExportsOfExportsSpec, ExportsSpec, GetUsedNameParam, InitFragmentExt,
InitFragmentKey, InitFragmentStage, ModuleGraph, ModuleGraphCacheArtifact, NormalInitFragment,
PrefetchExportsInfoMode, TemplateContext, TemplateReplaceSource, UsedName, property_access,
};
use swc_core::atoms::Atom;
use crate::dependency::commonjs::OBJECT_PROTOTYPE_METHODS;
#[cacheable]
#[derive(Debug, Clone, Copy)]
pub enum ExportsBase {
Exports,
ModuleExports,
This,
DefinePropertyExports,
DefinePropertyModuleExports,
DefinePropertyThis,
}
impl ExportsBase {
pub const fn is_exports(&self) -> bool {
matches!(self, Self::Exports | Self::DefinePropertyExports)
}
pub const fn is_module_exports(&self) -> bool {
matches!(
self,
Self::ModuleExports | Self::DefinePropertyModuleExports
)
}
pub const fn is_this(&self) -> bool {
matches!(self, Self::This | Self::DefinePropertyThis)
}
pub const fn is_expression(&self) -> bool {
matches!(self, Self::Exports | Self::ModuleExports | Self::This)
}
pub const fn is_define_property(&self) -> bool {
matches!(
self,
Self::DefinePropertyExports | Self::DefinePropertyModuleExports | Self::DefinePropertyThis
)
}
}
#[cacheable]
#[derive(Debug, Clone)]
pub struct CommonJsExportsDependency {
id: DependencyId,
range: DependencyRange,
value_range: Option<DependencyRange>,
base: ExportsBase,
#[cacheable(with=AsVec<AsPreset>)]
names: Vec<Atom>,
}
impl CommonJsExportsDependency {
pub fn new(
range: DependencyRange,
value_range: Option<DependencyRange>,
base: ExportsBase,
names: Vec<Atom>,
) -> Self {
Self {
id: DependencyId::new(),
range,
value_range,
base,
names,
}
}
}
#[cacheable_dyn]
impl Dependency for CommonJsExportsDependency {
fn id(&self) -> &DependencyId {
&self.id
}
fn range(&self) -> Option<DependencyRange> {
Some(self.range)
}
fn category(&self) -> &DependencyCategory {
&DependencyCategory::CommonJS
}
fn dependency_type(&self) -> &DependencyType {
&DependencyType::CjsExports
}
fn get_exports(
&self,
_mg: &ModuleGraph,
_mg_cache: &ModuleGraphCacheArtifact,
_exports_info_artifact: &ExportsInfoArtifact,
) -> Option<ExportsSpec> {
let name = self.names[0].clone();
let vec = vec![ExportNameOrSpec::ExportSpec(ExportSpec {
can_mangle: Some(!OBJECT_PROTOTYPE_METHODS.contains(&name.as_str())),
name,
..Default::default()
})];
Some(ExportsSpec {
exports: ExportsOfExportsSpec::Names(vec),
..Default::default()
})
}
fn could_affect_referencing_module(&self) -> rspack_core::AffectType {
rspack_core::AffectType::False
}
}
impl AsModuleDependency for CommonJsExportsDependency {}
#[cacheable_dyn]
impl DependencyCodeGeneration for CommonJsExportsDependency {
fn dependency_template(&self) -> Option<DependencyTemplateType> {
Some(CommonJsExportsDependencyTemplate::template_type())
}
}
impl AsContextDependency for CommonJsExportsDependency {}
#[cacheable]
#[derive(Debug, Clone, Default)]
pub struct CommonJsExportsDependencyTemplate;
impl CommonJsExportsDependencyTemplate {
pub fn template_type() -> DependencyTemplateType {
DependencyTemplateType::Dependency(DependencyType::CjsExports)
}
}
impl DependencyTemplate for CommonJsExportsDependencyTemplate {
fn render(
&self,
dep: &dyn DependencyCodeGeneration,
source: &mut TemplateReplaceSource,
code_generatable_context: &mut TemplateContext,
) {
let dep = dep
.as_any()
.downcast_ref::<CommonJsExportsDependency>()
.expect(
"CommonJsExportsDependencyTemplate should only be used for CommonJsExportsDependency",
);
let TemplateContext {
compilation,
module,
runtime,
init_fragments,
runtime_template,
..
} = code_generatable_context;
let module_graph = compilation.get_module_graph();
let module = module_graph
.module_by_identifier(&module.identifier())
.expect("should have mgm");
let used = if dep.names.is_empty() {
let exports_info_used = compilation
.exports_info_artifact
.get_prefetched_exports_info_used(&module.identifier(), *runtime);
ExportsInfoGetter::get_used_name(
GetUsedNameParam::WithoutNames(&exports_info_used),
*runtime,
&dep.names,
)
} else {
let exports_info = compilation
.exports_info_artifact
.get_prefetched_exports_info(
&module.identifier(),
PrefetchExportsInfoMode::Nested(&dep.names),
);
ExportsInfoGetter::get_used_name(
GetUsedNameParam::WithNames(&exports_info),
*runtime,
&dep.names,
)
};
let exports_argument = module.get_exports_argument();
let module_argument = module.get_module_argument();
let base = if dep.base.is_exports() {
runtime_template.render_exports_argument(exports_argument)
} else if dep.base.is_module_exports() {
format!(
"{}.exports",
runtime_template.render_module_argument(module_argument)
)
} else if dep.base.is_this() {
runtime_template.render_this_exports()
} else {
panic!("Unexpected base type");
};
if dep.base.is_expression() {
if let Some(UsedName::Normal(used)) = used {
source.replace(
dep.range.start,
dep.range.end,
format!("{}{}", base, property_access(used, 0)),
None,
);
} else {
let is_inlined = matches!(used, Some(UsedName::Inlined(_)));
let placeholder_var = format!(
"__webpack_{}_export__",
if is_inlined { "inlined" } else { "unused" }
);
source.replace(
dep.range.start,
dep.range.end,
placeholder_var.clone(),
None,
);
init_fragments.push(
NormalInitFragment::new(
format!("var {placeholder_var};\n"),
InitFragmentStage::StageConstants,
0,
InitFragmentKey::CommonJsExports(placeholder_var),
None,
)
.boxed(),
);
}
} else if dep.base.is_define_property() {
if let Some(value_range) = &dep.value_range {
if let Some(UsedName::Normal(used)) = used {
if !used.is_empty() {
source.replace(
dep.range.start,
value_range.start,
format!(
"Object.defineProperty({}{}, {}, (",
base,
property_access(used[0..used.len() - 1].iter(), 0),
serde_json::to_string(&used.last())
.expect("Unexpected render define property base")
),
None,
);
source.replace_static(value_range.end, dep.range.end, "))", None);
} else {
panic!("Unexpected base type");
}
} else {
init_fragments.push(
NormalInitFragment::new(
"var __webpack_unused_export__;\n".to_string(),
InitFragmentStage::StageConstants,
0,
InitFragmentKey::CommonJsExports("__webpack_unused_export__".to_owned()),
None,
)
.boxed(),
);
source.replace_static(
dep.range.start,
value_range.start,
"__webpack_unused_export__ = (",
None,
);
source.replace_static(value_range.end, dep.range.end, ")", None);
}
} else {
panic!("Define property need value range");
}
} else {
panic!("Unexpected base type");
}
}
}