use std::{fmt::Debug, sync::Arc};
use crate::css::css_view::CssView;
use crate::types::module_render_output::ModuleRenderOutput;
use crate::{
AssetView, DebugStmtInfoForTreeShaking, EcmaModuleAstUsage, ExportsKind, ImportRecordIdx,
ImportRecordMeta, LegalComments, ModuleId, ModuleIdx, ModuleInfo, NormalizedBundlerOptions,
RawImportRecord, ResolvedId, StmtInfo,
};
use crate::{EcmaView, IndexModules, Interop, Module, ModuleType};
use std::ops::{Deref, DerefMut};
use itertools::Itertools;
use oxc::span::CompactStr;
use oxc_index::IndexVec;
use rolldown_ecmascript::{EcmaAst, EcmaCompiler, PrintOptions};
use rolldown_sourcemap::collapse_sourcemaps;
use rustc_hash::FxHashSet;
use string_wizard::SourceMapOptions;
#[derive(Debug, Clone)]
pub struct NormalModule {
pub exec_order: u32,
pub idx: ModuleIdx,
pub is_user_defined_entry: bool,
pub id: ModuleId,
pub stable_id: String,
pub debug_id: String,
pub repr_name: String,
pub module_type: ModuleType,
pub ecma_view: EcmaView,
pub css_view: Option<CssView>,
pub asset_view: Option<AssetView>,
pub originative_resolved_id: ResolvedId,
}
impl NormalModule {
pub fn star_export_module_ids(&self) -> impl Iterator<Item = ModuleIdx> + '_ {
if self.has_star_export() {
itertools::Either::Left(
self
.ecma_view
.import_records
.iter()
.filter(|&rec| rec.meta.contains(ImportRecordMeta::IsExportStar))
.map(|rec| rec.resolved_module),
)
} else {
itertools::Either::Right(std::iter::empty())
}
}
pub fn has_star_export(&self) -> bool {
self.ecma_view.meta.has_star_export()
}
pub fn to_debug_normal_module_for_tree_shaking(&self) -> DebugNormalModuleForTreeShaking {
DebugNormalModuleForTreeShaking {
id: self.repr_name.to_string(),
is_included: self.ecma_view.meta.is_included(),
stmt_infos: self
.ecma_view
.stmt_infos
.iter()
.map(StmtInfo::to_debug_stmt_info_for_tree_shaking)
.collect(),
}
}
pub fn to_module_info(
&self,
raw_import_records: Option<&IndexVec<ImportRecordIdx, RawImportRecord>>,
) -> ModuleInfo {
ModuleInfo {
code: Some(self.ecma_view.source.clone()),
id: self.id.clone(),
is_entry: self.is_user_defined_entry,
importers: {
let mut value = self.ecma_view.importers.clone();
value.sort_unstable();
value
},
dynamic_importers: {
let mut value = self.ecma_view.dynamic_importers.clone();
value.sort_unstable();
value
},
imported_ids: self.ecma_view.imported_ids.clone(),
dynamically_imported_ids: self.ecma_view.dynamically_imported_ids.clone(),
exports: {
let mut exports = self.ecma_view.named_exports.keys().cloned().collect_vec();
if let Some(e) = raw_import_records {
exports.extend(
e.iter()
.filter(|&rec| rec.meta.contains(ImportRecordMeta::IsExportStar))
.map(|_| CompactStr::new("*")),
);
} else {
exports.extend(
self
.ecma_view
.import_records
.iter()
.filter(|&rec| rec.meta.contains(ImportRecordMeta::IsExportStar))
.map(|_| CompactStr::new("*")),
);
}
exports
},
}
}
pub fn is_virtual(&self) -> bool {
self.id.starts_with('\0') || self.id.starts_with("rolldown:")
}
pub fn get_exported_names<'modules>(
&'modules self,
export_star_set: &mut FxHashSet<ModuleIdx>,
modules: &'modules IndexModules,
include_default: bool,
ret: &mut FxHashSet<&'modules CompactStr>,
) {
if export_star_set.contains(&self.idx) {
return;
}
export_star_set.insert(self.idx);
self
.star_export_module_ids()
.filter_map(|id| modules[id].as_normal())
.for_each(|module| module.get_exported_names(export_star_set, modules, false, ret));
if include_default {
ret.extend(self.ecma_view.named_exports.keys());
} else {
ret.extend(self.ecma_view.named_exports.keys().filter(|name| name.as_str() != "default"));
}
}
pub fn star_exports_from_external_modules<'me>(
&'me self,
modules: &'me IndexModules,
) -> impl Iterator<Item = ImportRecordIdx> + 'me {
self.ecma_view.import_records.iter_enumerated().filter_map(move |(rec_id, rec)| {
if !rec.meta.contains(ImportRecordMeta::IsExportStar) {
return None;
}
match modules[rec.resolved_module] {
Module::External(_) => Some(rec_id),
Module::Normal(_) => None,
}
})
}
pub fn interop(&self, importee: &NormalModule) -> Option<Interop> {
if matches!(importee.ecma_view.exports_kind, ExportsKind::CommonJs) {
if self.ecma_view.def_format.is_esm() { Some(Interop::Node) } else { Some(Interop::Babel) }
} else {
None
}
}
#[inline]
pub fn should_consider_node_esm_spec_for_static_import(&self) -> bool {
self.ecma_view.def_format.is_esm()
}
#[inline]
pub fn should_consider_node_esm_spec_for_dynamic_import(&self) -> bool {
self.ecma_view.def_format.is_esm() || self.ecma_view.def_format.is_commonjs()
}
pub fn render(
&self,
options: &NormalizedBundlerOptions,
args: &ModuleRenderArgs,
initial_indent: u32,
) -> Option<ModuleRenderOutput> {
match args {
ModuleRenderArgs::Ecma { ast } => {
let enable_sourcemap = options.sourcemap.is_some() && !self.is_virtual();
let print_legal_comments = matches!(options.legal_comments, LegalComments::Inline);
let render_output = EcmaCompiler::print_with(
ast,
PrintOptions {
sourcemap: enable_sourcemap,
filename: self.id.to_string(),
print_legal_comments,
initial_indent,
},
);
if !self.ecma_view.mutations.is_empty() {
let original_code: Arc<str> = render_output.code.into();
let mut magic_string = string_wizard::MagicString::new(&*original_code);
for mutation in &self.ecma_view.mutations {
mutation.apply(&mut magic_string);
}
let code = magic_string.to_string();
let mutated_map = magic_string.source_map(SourceMapOptions {
source: Arc::clone(&original_code),
..Default::default()
});
let map =
render_output.map.map(|original| collapse_sourcemaps(&[&original, &mutated_map]));
return Some(ModuleRenderOutput { code, map });
}
Some(ModuleRenderOutput { code: render_output.code, map: render_output.map })
}
}
}
pub fn is_included(&self) -> bool {
self.ecma_view.meta.is_included()
}
#[expect(clippy::cast_precision_loss)]
pub fn size(&self) -> f64 {
self.ecma_view.source.len() as f64
}
pub fn is_hmr_self_accepting_module(&self) -> bool {
self.ast_usage.contains(EcmaModuleAstUsage::HmrSelfAccept)
}
pub fn can_accept_hmr_dependency_for(&self, module_id: &ModuleId) -> bool {
self.hmr_info.deps.contains(module_id)
}
}
#[derive(Debug)]
pub struct DebugNormalModuleForTreeShaking {
pub id: String,
pub is_included: bool,
pub stmt_infos: Vec<DebugStmtInfoForTreeShaking>,
}
impl Deref for NormalModule {
type Target = EcmaView;
fn deref(&self) -> &Self::Target {
&self.ecma_view
}
}
impl DerefMut for NormalModule {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.ecma_view
}
}
pub enum ModuleRenderArgs<'any> {
Ecma { ast: &'any EcmaAst },
}