use std::fmt::Debug;
use std::sync::Arc;
use crate::css::css_view::CssView;
use crate::ecmascript::ecma_view::EsmNamespaceInCjs;
use crate::types::module_render_output::ModuleRenderOutput;
use crate::{
AssetView, Comments, DebugStmtInfoForTreeShaking, ExportsKind, ImportRecordIdx, ImportRecordMeta,
ModuleId, ModuleIdx, ModuleInfo, NormalizedBundlerOptions, RawImportRecord, RuntimeModuleBrief,
StmtInfo, SymbolRef, SymbolRefDb,
};
use crate::{EcmaAstIdx, EcmaView, IndexModules, Interop, Module, ModuleType};
use std::ops::{Deref, DerefMut};
use either::Either;
use oxc::codegen::LegalComment;
use oxc_index::IndexVec;
use rolldown_ecmascript::{EcmaAst, EcmaCompiler, PrintOptions};
use rolldown_rstr::Rstr;
use rolldown_sourcemap::collapse_sourcemaps;
use rolldown_utils::concat_string;
use rolldown_utils::ecmascript::legitimize_identifier_name;
use rustc_hash::FxHashSet;
use string_wizard::SourceMapOptions;
#[derive(Debug)]
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>,
}
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::IS_EXPORT_STAR))
.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().map(|e| e.as_str().into()).collect::<Vec<_>>();
if let Some(e) = raw_import_records {
exports.extend(
e.iter()
.filter(|&rec| rec.meta.contains(ImportRecordMeta::IS_EXPORT_STAR))
.map(|_| "*".into()),
);
} else {
exports.extend(
self
.ecma_view
.import_records
.iter()
.filter(|&rec| rec.meta.contains(ImportRecordMeta::IS_EXPORT_STAR))
.map(|_| "*".into()),
);
}
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 Rstr>,
) {
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 ecma_ast_idx(&self) -> EcmaAstIdx {
self.ecma_view.ecma_ast_idx.expect("ecma_ast_idx should be set in this stage")
}
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::IS_EXPORT_STAR) {
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(&self) -> bool {
self.ecma_view.def_format.is_esm()
}
pub fn render(
&self,
options: &NormalizedBundlerOptions,
args: &ModuleRenderArgs,
) -> Option<ModuleRenderOutput> {
match args {
ModuleRenderArgs::Ecma { ast } => {
let enable_sourcemap = options.sourcemap.is_some() && !self.is_virtual();
let comments = match options.comments {
Comments::None => Either::Left(false),
Comments::Preserve => Either::Left(true),
Comments::PreserveLegal => Either::Right(LegalComment::Inline),
};
let render_output = EcmaCompiler::print_with(
ast,
PrintOptions { sourcemap: enable_sourcemap, filename: self.id.to_string(), comments },
);
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(vec![&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()
}
pub fn generate_esm_namespace_in_cjs_stmt(
&mut self,
symbol_db: &mut SymbolRefDb,
runtime_module: &RuntimeModuleBrief,
wrap_ref: SymbolRef,
) {
if self.esm_namespace_in_cjs.is_some() {
return;
}
let esm_namespace_ref_derived_from_module_exports = symbol_db.create_facade_root_symbol_ref(
self.idx,
&concat_string!("import_", legitimize_identifier_name(&self.repr_name)),
);
let stmt_info_idx = self.stmt_infos.add_stmt_info(StmtInfo {
stmt_idx: None,
declared_symbols: vec![esm_namespace_ref_derived_from_module_exports],
referenced_symbols: vec![wrap_ref.into(), runtime_module.resolve_symbol("__toESM").into()],
debug_label: Some("esm_namespace_ref_derived_from_module_exports".to_string()),
..Default::default()
});
self.esm_namespace_in_cjs = Some(EsmNamespaceInCjs {
namespace_ref: esm_namespace_ref_derived_from_module_exports,
stmt_info_idx,
});
}
pub fn generate_esm_namespace_in_cjs_node_mode_stmt(
&mut self,
symbol_db: &mut SymbolRefDb,
runtime_module: &RuntimeModuleBrief,
wrap_ref: SymbolRef,
) {
if self.esm_namespace_in_cjs_node_mode.is_some() {
return;
}
let esm_namespace_ref_derived_from_module_exports = symbol_db.create_facade_root_symbol_ref(
self.idx,
&concat_string!("import_", legitimize_identifier_name(&self.repr_name)),
);
let stmt_info_idx = self.stmt_infos.add_stmt_info(StmtInfo {
stmt_idx: None,
declared_symbols: vec![esm_namespace_ref_derived_from_module_exports],
referenced_symbols: vec![wrap_ref.into(), runtime_module.resolve_symbol("__toESM").into()],
debug_label: Some("esm_namespace_ref_derived_from_module_exports node".to_string()),
..Default::default()
});
self.esm_namespace_in_cjs_node_mode = Some(EsmNamespaceInCjs {
namespace_ref: esm_namespace_ref_derived_from_module_exports,
stmt_info_idx,
});
}
#[expect(clippy::cast_precision_loss)]
pub fn size(&self) -> f64 {
self.ecma_view.source.len() as f64
}
}
#[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 },
}