use std::sync::Arc;
use rspack_collections::{IdentifierMap, IdentifierSet};
use rspack_core::{
BuildModuleGraphArtifact, Compilation, DependenciesBlock, Dependency, DependencyId,
DependencyTemplate, ExportsInfoArtifact, ExternalModule, InitFragmentExt, InitFragmentKey,
InitFragmentStage, NormalInitFragment,
};
use rspack_plugin_javascript::dependency::{
ESMExportImportedSpecifierDependency, ESMImportSideEffectDependency, ImportDependency,
};
use rustc_hash::FxHashSet;
pub fn cutout_star_re_export_externals(
compilation: &Compilation,
build_module_graph_artifact: &mut BuildModuleGraphArtifact,
exports_info_artifact: &mut ExportsInfoArtifact,
) {
let mg = build_module_graph_artifact.get_module_graph();
let mut connections_to_disable = FxHashSet::default();
let entry_modules = build_module_graph_artifact
.entry_dependencies
.iter()
.filter_map(|dep_id| mg.module_identifier_by_dependency_id(dep_id))
.copied()
.collect::<IdentifierSet>();
for module_id in &entry_modules {
let module = mg
.module_by_identifier(module_id)
.expect("should have entry module");
let mut side_effects_deps_by_module = IdentifierMap::<Vec<DependencyId>>::default();
let mut star_export_deps_by_module = IdentifierMap::default();
for dep_id in module.get_dependencies() {
let dep = mg.dependency_by_id(dep_id);
let Some(module) = mg
.module_identifier_by_dependency_id(dep_id)
.and_then(|module_id| mg.module_by_identifier(module_id))
else {
continue;
};
let Some(external_module) = module.as_external_module() else {
continue;
};
let external_type = external_module.get_external_type().as_str();
if !external_type.starts_with("module") {
continue;
}
if dep.as_any().is::<ESMImportSideEffectDependency>() {
side_effects_deps_by_module
.entry(external_module.id)
.or_default()
.push(*dep_id);
} else if let Some(esm_export_dep) = dep
.as_any()
.downcast_ref::<ESMExportImportedSpecifierDependency>()
&& esm_export_dep.name.is_none()
&& esm_export_dep.other_star_exports.is_some()
{
*star_export_deps_by_module
.entry(external_module.id)
.or_insert(0) += 1;
connections_to_disable.insert(*dep_id);
}
}
for (module_id, side_effect_deps) in side_effects_deps_by_module {
let star_count = star_export_deps_by_module.get(&module_id).unwrap_or(&0);
if side_effect_deps.len() == *star_count {
connections_to_disable.extend(side_effect_deps);
}
}
}
let mg = build_module_graph_artifact.get_module_graph_mut();
for dep_id in &connections_to_disable {
let conn = mg
.connection_by_dependency_id_mut(dep_id)
.expect("definitely has connection");
conn.force_inactive();
}
for module_id in entry_modules {
let module = mg
.module_by_identifier(&module_id)
.expect("should have entry module");
if !module.build_meta().esm {
continue;
}
let exports_info = exports_info_artifact.get_exports_info_data(&module_id);
if matches!(
exports_info.other_exports_info().provided(),
Some(rspack_core::ExportProvided::Unknown)
) {
let has_unknown_exports = module.get_dependencies().iter().any(|dep_id| {
if connections_to_disable.contains(dep_id) {
return false;
}
let dep = mg.dependency_by_id(dep_id);
let Some(exports) = dep.get_exports(
mg,
&compilation.module_graph_cache_artifact,
exports_info_artifact,
) else {
return false;
};
match &exports.exports {
rspack_core::ExportsOfExportsSpec::UnknownExports => true,
rspack_core::ExportsOfExportsSpec::NoExports => false,
rspack_core::ExportsOfExportsSpec::Names(_) => false,
}
});
if !has_unknown_exports {
let exports_info = exports_info_artifact.get_exports_info_data_mut(&module_id);
exports_info
.other_exports_info_mut()
.set_provided(Some(rspack_core::ExportProvided::NotProvided));
}
}
}
}
pub fn cutout_dyn_import_externals(build_module_graph_artifact: &mut BuildModuleGraphArtifact) {
let mg = build_module_graph_artifact.get_module_graph();
let mut connections_to_disable = Vec::new();
for (_, module) in mg.modules() {
for block_id in module.get_blocks() {
let Some(block) = mg.block_by_id(block_id) else {
continue;
};
for block_dep_id in block.get_dependencies() {
let block_dep = mg.dependency_by_id(block_dep_id);
if block_dep.as_any().is::<ImportDependency>() {
let import_dep_connection = mg.connection_by_dependency_id(block_dep_id);
if let Some(import_dep_connection) = import_dep_connection {
let import_module_id = import_dep_connection.module_identifier();
let Some(import_module) = mg.module_by_identifier(import_module_id) else {
continue;
};
if import_module.as_external_module().is_some() {
connections_to_disable.push(*block_dep_id);
}
}
}
}
}
}
let mg = build_module_graph_artifact.get_module_graph_mut();
for dep_id in connections_to_disable {
let conn = mg
.connection_by_dependency_id_mut(&dep_id)
.expect("definitely has connection");
conn.force_inactive();
}
}
pub fn render_dyn_import_external_module(
import_dep: &ImportDependency,
external_module: &ExternalModule,
source: &mut rspack_core::TemplateReplaceSource,
) {
let request = external_module.get_request();
let attributes_str = if let Some(attributes) = import_dep.get_attributes() {
format!(
", {{ with: {} }}",
serde_json::to_string(attributes).expect("invalid json to_string")
)
} else {
String::new()
};
let comments_str = {
let mut comments_string = String::new();
for (line_comment, comment) in import_dep.comments.iter() {
if *line_comment {
comments_string.push_str(&format!("//{comment}\n"));
} else {
comments_string.push_str(&format!("/*{comment}*/ "));
}
}
comments_string
};
source.replace(
import_dep.range.start,
import_dep.range.end,
format!(
"import({}{}{})",
comments_str,
rspack_util::json_stringify_str(&request.primary),
attributes_str
),
None,
);
}
#[derive(Debug)]
pub(crate) struct ImportDependencyTemplate {
pub(crate) template: Option<Arc<dyn DependencyTemplate>>,
}
impl DependencyTemplate for ImportDependencyTemplate {
fn render(
&self,
dep: &dyn rspack_core::DependencyCodeGeneration,
source: &mut rspack_core::rspack_sources::ReplaceSource,
code_generatable_context: &mut rspack_core::TemplateContext,
) {
let dep = dep
.as_any()
.downcast_ref::<ImportDependency>()
.expect("should be import dependency");
let mg = code_generatable_context.compilation.get_module_graph();
let ref_module = mg
.module_identifier_by_dependency_id(&dep.id)
.and_then(|module_id| mg.module_by_identifier(module_id));
if let Some(external) = ref_module.and_then(|m| m.as_external_module()) {
render_dyn_import_external_module(dep, external, source);
return;
}
if let Some(template) = &self.template {
template.render(dep, source, code_generatable_context);
}
}
}
#[derive(Debug)]
pub(crate) struct ExportImportedDependencyTemplate {
pub(crate) template: Option<Arc<dyn DependencyTemplate>>,
}
impl DependencyTemplate for ExportImportedDependencyTemplate {
fn render(
&self,
dep: &dyn rspack_core::DependencyCodeGeneration,
source: &mut rspack_core::rspack_sources::ReplaceSource,
code_generatable_context: &mut rspack_core::TemplateContext,
) {
let dep = dep
.as_any()
.downcast_ref::<ESMExportImportedSpecifierDependency>()
.expect("should be export imported dependency");
let mg = code_generatable_context.compilation.get_module_graph();
let module = mg.module_identifier_by_dependency_id(&dep.id);
if dep.name.is_none()
&& dep.other_star_exports.is_some()
&& let Some(module) = module
.and_then(|mid| mg.module_by_identifier(mid))
.and_then(|m| {
m.as_external_module().filter(|&m| m.get_external_type().starts_with("module"))
})
&& code_generatable_context
.compilation
.entry_modules()
.contains(&code_generatable_context.module.identifier())
{
let request = module.get_request();
let chunk_init_fragments = code_generatable_context.chunk_init_fragments();
chunk_init_fragments.push(
NormalInitFragment::new(
format!(
"export * from {};\n",
rspack_util::json_stringify_str(request.primary())
),
InitFragmentStage::StageESMImports,
0,
InitFragmentKey::Const(format!("esm_module_reexport_star_{}", dep.request)),
None,
)
.boxed(),
);
return;
}
if let Some(template) = &self.template {
template.render(dep, source, code_generatable_context);
}
}
}