use std::collections::VecDeque;
use std::collections::hash_map::Entry;
use std::sync::Arc;
use arcstr::ArcStr;
use futures::future::join_all;
use itertools::Itertools;
use oxc::semantic::{ScopeId, Scoping};
use oxc::span::Span;
use oxc::transformer_plugins::ReplaceGlobalDefinesConfig;
use oxc_allocator::Address;
use oxc_index::IndexVec;
use rolldown_common::dynamic_import_usage::DynamicImportExportsUsage;
use rolldown_common::{
EcmaModuleAstUsage, EcmaRelated, EntryPoint, EntryPointKind, ExternalModule,
ExternalModuleTaskResult, FlatOptions, HybridIndexVec, ImportKind, ImportRecordIdx,
ImportRecordMeta, ImportedExports, ImporterRecord, Module, ModuleId, ModuleIdx, ModuleLoaderMsg,
ModuleType, NormalModuleTaskResult, PreserveEntrySignatures, RUNTIME_MODULE_ID, ResolvedId,
RuntimeModuleBrief, RuntimeModuleTaskResult, ScanMode, SourceMapGenMsg, StmtInfoIdx, StmtInfos,
SymbolRefDb, SymbolRefDbForModule,
};
use rolldown_ecmascript::EcmaAst;
use rolldown_error::{
BuildDiagnostic, BuildResult, DiagnosableResolveError, consolidate_diagnostics,
};
use rolldown_fs::FileSystem;
use rolldown_plugin::SharedPluginDriver;
use rolldown_utils::indexmap::FxIndexSet;
use rolldown_utils::rayon::{IntoParallelIterator, ParallelIterator};
use rolldown_utils::rustc_hash::FxHashSetExt;
use rustc_hash::{FxHashMap, FxHashSet};
use tracing::Instrument;
use crate::module_loader::module_task::ModuleTaskOwner;
use crate::types::scan_stage_cache::ScanStageCache;
use crate::utils::load_entry_module::load_entry_module;
use crate::{SharedOptions, SharedResolver};
use super::external_module_task::ExternalModuleTask;
use super::module_task::ModuleTask;
use super::runtime_module_task::RuntimeModuleTask;
use super::task_context::{TaskContext, TaskContextMeta};
pub struct IntermediateNormalModules {
pub modules: HybridIndexVec<ModuleIdx, Option<Module>>,
pub importers: IndexVec<ModuleIdx, Vec<ImporterRecord>>,
pub index_ecma_ast: HybridIndexVec<ModuleIdx, Option<EcmaAst>>,
pub stmt_infos: HybridIndexVec<ModuleIdx, Option<StmtInfos>>,
}
impl IntermediateNormalModules {
pub fn new(is_full_scan: bool, importers: IndexVec<ModuleIdx, Vec<ImporterRecord>>) -> Self {
Self {
modules: if is_full_scan {
HybridIndexVec::IndexVec(IndexVec::default())
} else {
HybridIndexVec::Map(FxHashMap::default())
},
importers,
index_ecma_ast: if is_full_scan {
HybridIndexVec::IndexVec(IndexVec::default())
} else {
HybridIndexVec::Map(FxHashMap::default())
},
stmt_infos: if is_full_scan {
HybridIndexVec::IndexVec(IndexVec::default())
} else {
HybridIndexVec::Map(FxHashMap::default())
},
}
}
pub fn alloc_ecma_module_idx(&mut self) -> ModuleIdx {
let id = self.modules.push(None);
self.index_ecma_ast.push(None);
self.stmt_infos.push(None);
self.importers.push(Vec::new());
id
}
pub fn alloc_ecma_module_idx_sparse(&mut self, i: ModuleIdx) -> ModuleIdx {
self.modules.insert(i, None);
self.index_ecma_ast.insert(i, None);
self.stmt_infos.insert(i, None);
if i >= self.importers.len() {
self.importers.push(Vec::new());
}
i
}
}
#[derive(Debug, Clone, Copy)]
pub enum VisitState {
Seen(ModuleIdx),
Invalidate(ModuleIdx),
}
impl VisitState {
pub fn idx(self) -> ModuleIdx {
match self {
VisitState::Seen(idx) | VisitState::Invalidate(idx) => idx,
}
}
}
pub struct ModuleLoader<'a, Fs: FileSystem + Clone + 'static> {
pub shared_context: Arc<TaskContext<Fs>>,
rx: tokio::sync::mpsc::UnboundedReceiver<ModuleLoaderMsg>,
remaining: u32,
intermediate_normal_modules: IntermediateNormalModules,
symbol_ref_db: SymbolRefDb,
is_full_scan: bool,
new_added_modules_from_partial_scan: FxIndexSet<ModuleIdx>,
cache: &'a mut ScanStageCache,
pub flat_options: FlatOptions,
pub magic_string_tx: Option<Arc<std::sync::mpsc::Sender<SourceMapGenMsg>>>,
tla_module_count: usize,
tla_keyword_span_map: FxHashMap<ModuleIdx, Span>,
}
pub struct ModuleLoaderOutput {
pub module_table: HybridIndexVec<ModuleIdx, Module>,
pub index_ecma_ast: HybridIndexVec<ModuleIdx, Option<EcmaAst>>,
pub stmt_infos: HybridIndexVec<ModuleIdx, StmtInfos>,
pub symbol_ref_db: SymbolRefDb,
pub entry_points: Vec<EntryPoint>,
pub runtime: RuntimeModuleBrief,
pub warnings: Vec<BuildDiagnostic>,
pub dynamic_import_exports_usage_map: FxHashMap<ModuleIdx, DynamicImportExportsUsage>,
pub new_added_modules_from_partial_scan: FxIndexSet<ModuleIdx>,
pub overrode_preserve_entry_signature_map: FxHashMap<ModuleIdx, PreserveEntrySignatures>,
pub entry_point_to_reference_ids: FxHashMap<EntryPoint, Vec<ArcStr>>,
pub flat_options: FlatOptions,
pub user_defined_entry_modules: FxHashSet<ModuleIdx>,
pub tla_module_count: usize,
pub tla_keyword_span_map: FxHashMap<ModuleIdx, Span>,
}
impl<Fs: FileSystem + Clone> Drop for ModuleLoader<'_, Fs> {
fn drop(&mut self) {
self.cache.importers = std::mem::take(&mut self.intermediate_normal_modules.importers);
}
}
impl<'a, Fs: FileSystem + Clone + 'static> ModuleLoader<'a, Fs> {
pub fn new(
fs: Fs,
options: SharedOptions,
resolver: SharedResolver<Fs>,
plugin_driver: SharedPluginDriver,
cache: &'a mut ScanStageCache,
is_full_scan: bool,
magic_string_tx: Option<Arc<std::sync::mpsc::Sender<SourceMapGenMsg>>>,
) -> BuildResult<Self> {
if is_full_scan {
std::mem::take(cache);
}
let flat_options = FlatOptions::from_shared_options(&options);
let symbol_ref_db = SymbolRefDb::new();
let meta = TaskContextMeta {
replace_global_define_config: if options.define.is_empty() {
None
} else {
ReplaceGlobalDefinesConfig::new(&options.define).map(Some).map_err(|errs| {
errs
.into_iter()
.map(|err| BuildDiagnostic::invalid_define_config(err.message.to_string()))
.collect::<Vec<BuildDiagnostic>>()
})?
},
};
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let shared_context = Arc::new(TaskContext { options, tx, resolver, fs, plugin_driver, meta });
let importers = std::mem::take(&mut cache.importers);
let intermediate_normal_modules = IntermediateNormalModules::new(is_full_scan, importers);
Ok(Self {
rx,
cache,
remaining: 0,
is_full_scan,
shared_context,
symbol_ref_db,
intermediate_normal_modules,
new_added_modules_from_partial_scan: FxIndexSet::default(),
flat_options,
magic_string_tx,
tla_module_count: 0,
tla_keyword_span_map: FxHashMap::default(),
})
}
#[expect(clippy::rc_buffer)]
fn try_spawn_new_task(
&mut self,
resolved_id: ResolvedId,
owner: Option<ModuleTaskOwner>,
is_user_defined_entry: bool,
assert_module_type: Option<&ModuleType>,
user_defined_entries: &Arc<Vec<(Option<ArcStr>, ResolvedId)>>,
) -> ModuleIdx {
let idx = match self.cache.module_id_to_idx.get(&resolved_id.id).copied() {
Some(VisitState::Seen(idx)) => {
if self.flat_options.is_lazy_barrel_enabled() && owner.is_none() {
if let Some(barrel_module_state) = self.cache.barrel_state.barrel_infos.get(&idx) {
if barrel_module_state.is_some() {
self.process_barrel_import_record(
&mut VecDeque::from_iter([(idx, ImportedExports::All)]),
user_defined_entries,
);
}
} else {
self.cache.barrel_state.requested_exports.insert(idx, ImportedExports::All);
}
}
return idx;
}
Some(VisitState::Invalidate(idx)) => {
self.intermediate_normal_modules.alloc_ecma_module_idx_sparse(idx);
self.cache.module_id_to_idx.insert(resolved_id.id.clone(), VisitState::Seen(idx));
idx
}
None if !self.is_full_scan => {
let len = self.cache.module_id_to_idx.len();
let idx = self.intermediate_normal_modules.alloc_ecma_module_idx_sparse(len.into());
self.new_added_modules_from_partial_scan.insert(idx);
self.cache.module_id_to_idx.insert(resolved_id.id.clone(), VisitState::Seen(idx));
idx
}
None => {
let idx = self.intermediate_normal_modules.alloc_ecma_module_idx();
self.cache.module_id_to_idx.insert(resolved_id.id.clone(), VisitState::Seen(idx));
idx
}
};
let ctx = Arc::clone(&self.shared_context);
if resolved_id.external.is_external() {
let task = ExternalModuleTask::new(ctx, idx, resolved_id, Arc::clone(user_defined_entries));
tokio::spawn(task.run().instrument(tracing::info_span!("external_module_task")));
} else {
let task = ModuleTask::new(
ctx,
idx,
resolved_id,
owner,
is_user_defined_entry,
assert_module_type.cloned(),
self.flat_options,
self.magic_string_tx.clone(),
);
tokio::spawn(task.run().instrument(tracing::info_span!("normal_module_task")));
}
self.remaining += 1;
idx
}
#[tracing::instrument(level = "debug", skip_all)]
#[expect(clippy::too_many_lines)]
pub async fn fetch_modules(
&mut self,
fetch_mode: ScanMode<ResolvedId>,
) -> BuildResult<ModuleLoaderOutput> {
let mut errors = vec![];
let mut all_warnings = vec![];
if let Entry::Vacant(e) = self.cache.module_id_to_idx.entry(RUNTIME_MODULE_ID) {
let idx = self.intermediate_normal_modules.alloc_ecma_module_idx();
let task = RuntimeModuleTask::new(idx, Arc::clone(&self.shared_context), self.flat_options);
tokio::spawn(task.run().instrument(tracing::info_span!("runtime_module_task")));
e.insert(VisitState::Seen(idx));
self.remaining += 1;
}
let user_defined_entries = Arc::new(match fetch_mode {
ScanMode::Full => self.resolve_user_defined_entries().await?,
ScanMode::Partial(_) => vec![],
});
let entries_count = user_defined_entries.len() + 1;
self.intermediate_normal_modules.modules.reserve(entries_count);
self.intermediate_normal_modules.index_ecma_ast.reserve(entries_count);
let mut entry_points = FxIndexSet::default();
let mut user_defined_entry_ids = FxHashSet::with_capacity(user_defined_entries.len());
for (name, resolved_id) in user_defined_entries.iter().cloned() {
let idx = self.try_spawn_new_task(resolved_id, None, true, None, &user_defined_entries);
user_defined_entry_ids.insert(idx);
entry_points.insert(EntryPoint {
idx,
name,
kind: EntryPointKind::UserDefined,
file_name: None,
related_stmt_infos: vec![],
});
}
if self.is_full_scan && self.shared_context.options.experimental.is_incremental_build_enabled()
{
self
.cache
.user_defined_entry
.extend(user_defined_entries.iter().map(|(_, resolved_id)| resolved_id.id.clone()));
}
for resolved_id in fetch_mode.iter().cloned() {
self.shared_context.plugin_driver.invalidate_context_load_module(&resolved_id.id);
if let Entry::Occupied(mut occ) = self.cache.module_id_to_idx.entry(resolved_id.id.clone()) {
let idx = occ.get().idx();
occ.insert(VisitState::Invalidate(idx));
self.cache.barrel_state.barrel_infos.remove(&idx);
}
let is_user_defined_entry = self.cache.user_defined_entry.contains(&resolved_id.id);
self.try_spawn_new_task(
resolved_id,
None,
is_user_defined_entry,
None,
&user_defined_entries,
);
}
let mut work_queue: VecDeque<(ModuleIdx, ImportedExports)> = VecDeque::new();
let mut dynamic_import_entry_ids: FxHashMap<
ModuleIdx,
Vec<(ModuleIdx, StmtInfoIdx, Address, ImportRecordIdx)>,
> = FxHashMap::default();
let mut dynamic_import_exports_usage_pairs = vec![];
let mut extra_entry_points = vec![];
let mut entry_point_to_reference_ids: FxHashMap<EntryPoint, Vec<ArcStr>> = FxHashMap::default();
let mut runtime_brief = None;
let mut overrode_preserve_entry_signature_map = FxHashMap::default();
while self.remaining > 0 {
let Some(msg) = self.rx.recv().await else {
break;
};
match msg {
ModuleLoaderMsg::NormalModuleDone(task_result) => {
let NormalModuleTaskResult {
mut module,
mut barrel_info,
ecma_related:
EcmaRelated {
ast,
symbols,
mut dynamic_import_rec_exports_usage,
preserve_jsx,
stmt_infos,
},
resolved_deps,
raw_import_records,
warnings,
tla_keyword_span,
} = *task_result;
all_warnings.extend(warnings);
let module_idx = module.idx();
if !self.is_full_scan
&& let Some(previous_module) =
self.cache.get_snapshot().module_table.modules.get(module_idx)
{
let resolved_deps =
previous_module.import_records().into_iter().filter_map(|r| r.resolved_module);
for dep_idx in resolved_deps {
self.intermediate_normal_modules.importers[dep_idx]
.retain(|v| v.importer_idx != module_idx);
}
}
let normal_module = module.as_normal().unwrap();
if normal_module.ast_usage.contains(EcmaModuleAstUsage::TopLevelAwait) {
self.tla_module_count += 1;
}
if let Some(span) = tla_keyword_span {
self.tla_keyword_span_map.insert(module_idx, span);
}
let mut import_records = IndexVec::with_capacity(raw_import_records.len());
let mut tracked_records = FxHashMap::default();
let mut initialized_barrel_tracking =
self.flat_options.is_lazy_barrel_enabled().then(|| {
self.cache.barrel_state.initialize_barrel_tracking(
normal_module,
&raw_import_records,
&mut barrel_info,
)
});
for ((rec_idx, mut raw_rec), resolved_id) in
raw_import_records.into_iter_enumerated().zip(resolved_deps)
{
if self.shared_context.options.experimental.vite_mode.unwrap_or_default()
&& resolved_id.id.as_str().ends_with(".json")
{
raw_rec.meta.insert(ImportRecordMeta::JsonModule);
}
if let Some((ref imported_exports_per_record, ref initial_needed_records)) =
initialized_barrel_tracking
&& raw_rec.kind == ImportKind::Import
{
if imported_exports_per_record.contains_key(&rec_idx) {
tracked_records.insert(rec_idx, (raw_rec.state.clone(), resolved_id.clone()));
}
if !initial_needed_records.contains_key(&rec_idx) {
import_records.push(raw_rec.into_resolved(None));
continue;
}
}
let is_external = resolved_id.external.is_external();
let idx = self.try_spawn_new_task(
resolved_id,
Some(ModuleTaskOwner::new(normal_module, raw_rec.span)),
false,
raw_rec.asserted_module_type.as_ref(),
&user_defined_entries,
);
if let Some((_, ref mut initial_needed_records)) = initialized_barrel_tracking {
let imported_exports = if raw_rec.kind == ImportKind::Import {
initial_needed_records.remove(&rec_idx).expect(
"If initial_needed_records does not contain the record idx, it should have been skipped above"
)
} else {
ImportedExports::All
};
work_queue.push_back((idx, imported_exports));
}
self.intermediate_normal_modules.importers[idx].push(ImporterRecord {
kind: raw_rec.kind,
importer_path: module.id().clone(),
importer_idx: module_idx,
});
if let Some(usage) = dynamic_import_rec_exports_usage.remove(&rec_idx) {
dynamic_import_exports_usage_pairs.push((idx, usage));
}
if matches!(raw_rec.kind, ImportKind::DynamicImport)
&& !is_external
&& !user_defined_entry_ids.contains(&idx)
{
match dynamic_import_entry_ids.entry(idx) {
Entry::Vacant(vac) => match raw_rec.dynamic_import_expr_info.as_ref() {
Some(info) => {
vac.insert(vec![(module_idx, info.stmt_info_idx, info.address, rec_idx)]);
}
None => {
vac.insert(vec![]);
}
},
Entry::Occupied(mut occ) => {
if let Some(info) = raw_rec.dynamic_import_expr_info.as_ref() {
occ.get_mut().push((module_idx, info.stmt_info_idx, info.address, rec_idx));
}
}
}
}
import_records.push(raw_rec.into_resolved(Some(idx)));
}
module.set_import_records(import_records);
*self.intermediate_normal_modules.index_ecma_ast.get_mut(module_idx) = Some(ast);
*self.intermediate_normal_modules.stmt_infos.get_mut(module_idx) = Some(stmt_infos);
*self.intermediate_normal_modules.modules.get_mut(module_idx) = Some(module);
if let Some((imported_exports_per_record, _)) = initialized_barrel_tracking {
self.cache.barrel_state.barrel_infos.insert(
module_idx,
if tracked_records.is_empty() {
None
} else {
barrel_info.map(|info| {
info.into_barrel_module_state(tracked_records, imported_exports_per_record)
})
},
);
self.process_barrel_import_record(&mut work_queue, &user_defined_entries);
}
self.symbol_ref_db.store_local_db(module_idx, symbols);
if preserve_jsx {
self.symbol_ref_db.set_has_module_preserve_jsx();
}
self.remaining -= 1;
}
ModuleLoaderMsg::ExternalModuleDone(task_result) => {
let ExternalModuleTaskResult {
id,
name,
idx,
identifier_name,
side_effects,
need_renormalize_render_path,
} = *task_result;
self.symbol_ref_db.store_local_db(
idx,
SymbolRefDbForModule::new(Scoping::default(), idx, ScopeId::new(0)),
);
let symbol_ref = self.symbol_ref_db.create_facade_root_symbol_ref(idx, &identifier_name);
let external_module = Module::External(Box::new(ExternalModule::new(
idx,
id,
name,
identifier_name,
side_effects,
symbol_ref,
need_renormalize_render_path,
)));
*self.intermediate_normal_modules.modules.get_mut(idx) = Some(external_module);
self.remaining -= 1;
}
ModuleLoaderMsg::RuntimeNormalModuleDone(task_result) => {
let RuntimeModuleTaskResult {
local_symbol_ref_db,
mut module,
stmt_infos,
runtime,
ast,
raw_import_records,
resolved_deps,
} = *task_result;
let mut import_records = IndexVec::with_capacity(raw_import_records.len());
for (raw_rec, info) in raw_import_records.into_iter().zip(resolved_deps) {
let idx = self.try_spawn_new_task(
info,
None,
false,
raw_rec.asserted_module_type.as_ref(),
&user_defined_entries,
);
self.intermediate_normal_modules.importers[idx].push(ImporterRecord {
kind: raw_rec.kind,
importer_path: module.id.clone(),
importer_idx: module.idx,
});
import_records.push(raw_rec.into_resolved(Some(idx)));
}
module.import_records = import_records;
*self.intermediate_normal_modules.modules.get_mut(runtime.id()) = Some(module.into());
*self.intermediate_normal_modules.index_ecma_ast.get_mut(runtime.id()) = Some(ast);
*self.intermediate_normal_modules.stmt_infos.get_mut(runtime.id()) = Some(stmt_infos);
self.symbol_ref_db.store_local_db(runtime.id(), local_symbol_ref_db);
self.remaining -= 1;
errors.extend(runtime.validate_symbols(&rolldown_common::RUNTIME_HELPER_NAMES));
runtime_brief = Some(runtime);
}
ModuleLoaderMsg::FetchModule(resolve_id) => {
self.try_spawn_new_task(*resolve_id, None, false, None, &user_defined_entries);
}
ModuleLoaderMsg::AddEntryModule(msg) => {
let data = msg.chunk;
let result = load_entry_module(
&self.shared_context.resolver,
&self.shared_context.plugin_driver,
&data.id,
data.importer.as_deref(),
)
.await;
let module_idx = match result {
Ok(resolved_id) => {
self.try_spawn_new_task(resolved_id, None, true, None, &user_defined_entries)
}
Err(e) => {
errors.push(e);
continue;
}
};
if let Some(preserve_entry_signatures) = data.preserve_entry_signatures {
overrode_preserve_entry_signature_map.insert(module_idx, preserve_entry_signatures);
}
user_defined_entry_ids.insert(module_idx);
let entry = EntryPoint {
name: data.name.clone(),
idx: module_idx,
kind: EntryPointKind::EmittedUserDefined,
file_name: data.file_name.clone(),
related_stmt_infos: vec![],
};
entry_point_to_reference_ids
.entry(entry.clone())
.or_default()
.push(msg.reference_id.clone());
extra_entry_points.push(entry);
}
ModuleLoaderMsg::BuildErrors(e) => {
errors.extend(e);
self.remaining -= 1;
}
}
}
if !errors.is_empty() {
for error in &mut errors {
if let Some(resolve_error) = error.downcast_mut::<DiagnosableResolveError>() {
let chain = self
.trace_import_chain_from_modules(&resolve_error.importer_id, &user_defined_entry_ids);
if !chain.is_empty() {
resolve_error.import_chain = Some(chain);
}
}
}
let errors = consolidate_diagnostics(errors);
return Err(errors.into());
}
if let Some(tx) = self.magic_string_tx.as_ref() {
tx.send(SourceMapGenMsg::Terminate).expect(
"SourceMapGen: failed to send Terminate message - sourcemap worker thread died unexpectedly"
);
}
let dynamic_import_exports_usage_map = dynamic_import_exports_usage_pairs.into_iter().fold(
FxHashMap::default(),
|mut acc, (idx, usage)| {
match acc.entry(idx) {
Entry::Vacant(vac) => {
vac.insert(usage);
}
Entry::Occupied(mut occ) => {
occ.get_mut().merge(usage);
}
}
acc
},
);
let mut idx_of_module_info_need_update = vec![];
let is_dense_index_vec = self.intermediate_normal_modules.modules.is_index_vec();
let modules_iter = std::mem::take(&mut self.intermediate_normal_modules.modules)
.into_iter_enumerated()
.into_iter()
.map(|(idx, module)| {
let mut module = module.expect("Module tasks did't complete as expected");
if let Some(module) = module.as_normal_mut() {
let importers = &self.intermediate_normal_modules.importers[idx];
for importer in importers {
if importer.kind.is_static() {
module.importers.insert(importer.importer_path.clone());
module.importers_idx.insert(importer.importer_idx);
} else {
module.dynamic_importers.insert(importer.importer_path.clone());
}
}
if !importers.is_empty() {
idx_of_module_info_need_update.push(idx);
}
}
(idx, module)
});
let module_table = if is_dense_index_vec {
let vec = modules_iter.map(|(_, module)| module).collect();
HybridIndexVec::IndexVec(IndexVec::from_vec(vec))
} else {
let map = modules_iter.collect::<FxHashMap<_, _>>();
HybridIndexVec::Map(map)
};
let stmt_infos_iter = std::mem::take(&mut self.intermediate_normal_modules.stmt_infos)
.into_iter_enumerated()
.into_iter()
.map(|(idx, stmt_infos)| (idx, stmt_infos.unwrap_or_else(StmtInfos::new)));
let stmt_infos = if is_dense_index_vec {
let vec = stmt_infos_iter.map(|(_, s)| s).collect();
HybridIndexVec::IndexVec(IndexVec::from_vec(vec))
} else {
let map = stmt_infos_iter.collect::<FxHashMap<_, _>>();
HybridIndexVec::Map(map)
};
idx_of_module_info_need_update.extend(extra_entry_points.iter().map(|item| item.idx));
idx_of_module_info_need_update.into_par_iter().for_each(|idx| {
let module = module_table.get(idx);
let Some(module) = module.as_normal() else {
return;
};
self.shared_context.plugin_driver.set_module_info(
&module.id,
Arc::new(module.to_module_info(None, user_defined_entry_ids.contains(&module.idx))),
);
});
let emitted_entry_indices: FxHashSet<ModuleIdx> =
extra_entry_points.iter().map(|e| e.idx).collect();
for (idx, related_stmt_infos) in dynamic_import_entry_ids {
if !emitted_entry_indices.contains(&idx) {
entry_points.insert(EntryPoint {
name: None,
idx,
kind: EntryPointKind::DynamicImport,
file_name: None,
related_stmt_infos,
});
}
}
entry_points.extend(extra_entry_points);
if entry_points.is_empty() && self.is_full_scan {
Err(BuildDiagnostic::invalid_option(rolldown_error::InvalidOptionType::NoEntryPoint))?;
}
let entry_points = entry_points.into_iter().collect_vec();
let runtime = if self.is_full_scan {
tracing::debug!("changed_resolved_ids: {fetch_mode:#?}");
runtime_brief.expect("Failed to find runtime module. This should not happen")
} else {
RuntimeModuleBrief::dummy()
};
Ok(ModuleLoaderOutput {
runtime,
entry_points,
module_table,
warnings: all_warnings,
dynamic_import_exports_usage_map,
overrode_preserve_entry_signature_map,
entry_point_to_reference_ids,
symbol_ref_db: std::mem::take(&mut self.symbol_ref_db),
index_ecma_ast: std::mem::take(&mut self.intermediate_normal_modules.index_ecma_ast),
stmt_infos,
new_added_modules_from_partial_scan: std::mem::take(
&mut self.new_added_modules_from_partial_scan,
),
flat_options: self.flat_options,
user_defined_entry_modules: user_defined_entry_ids,
tla_module_count: self.tla_module_count,
tla_keyword_span_map: std::mem::take(&mut self.tla_keyword_span_map),
})
}
#[tracing::instrument(target = "devtool", level = "debug", skip_all)]
pub async fn resolve_user_defined_entries(
&self,
) -> BuildResult<Vec<(Option<ArcStr>, ResolvedId)>> {
let resolved_ids =
join_all(self.shared_context.options.input.iter().map(|input_item| async move {
let resolved = load_entry_module(
&self.shared_context.resolver,
&self.shared_context.plugin_driver,
&input_item.import,
None,
)
.await;
resolved.map(|info| (input_item.name.as_ref().map(Into::into), info))
}))
.await;
let mut ret = Vec::with_capacity(self.shared_context.options.input.len());
let mut errors = vec![];
for resolve_id in resolved_ids {
match resolve_id {
Ok(item) => {
ret.push(item);
}
Err(e) => errors.push(e),
}
}
if !errors.is_empty() {
Err(errors)?;
}
Ok(ret)
}
fn trace_import_chain_from_modules(
&self,
importer_id: &str,
user_defined_entry_ids: &FxHashSet<ModuleIdx>,
) -> Vec<String> {
let importer_module_id = ModuleId::new(importer_id);
let Some(visit_state) = self.cache.module_id_to_idx.get(&importer_module_id) else {
return vec![];
};
let start_idx = visit_state.idx();
let mut chain = Vec::new();
let mut visited = FxHashSet::default();
let mut current = Some(start_idx);
while let Some(idx) = current {
if visited.contains(&idx) {
break;
}
visited.insert(idx);
let module_opt = self.intermediate_normal_modules.modules.get(idx);
if let Some(module) = module_opt {
if let Some(normal) = module.as_normal() {
chain.push(normal.id.to_string());
}
}
if user_defined_entry_ids.contains(&idx) {
break;
}
current = self
.intermediate_normal_modules
.importers
.get(idx)
.and_then(|importers| importers.first().map(|rec| rec.importer_idx));
}
chain
}
#[expect(clippy::rc_buffer)]
fn process_barrel_import_record(
&mut self,
work_queue: &mut VecDeque<(ModuleIdx, ImportedExports)>,
user_defined_entries: &Arc<Vec<(Option<ArcStr>, ResolvedId)>>,
) {
while let Some((idx, imported_exports)) = work_queue.pop_front() {
let Some(barrel_module_state) = self.cache.barrel_state.barrel_infos.get_mut(&idx) else {
match self.cache.barrel_state.requested_exports.entry(idx) {
Entry::Occupied(mut occ) => {
imported_exports.subtract_and_merge_into(occ.get_mut());
}
Entry::Vacant(vac) => {
vac.insert(imported_exports);
}
}
continue;
};
let Some(barrel_module_state) = barrel_module_state else {
self.cache.barrel_state.requested_exports.remove(&idx);
continue;
};
if barrel_module_state.tracked_records.is_empty() {
continue;
}
let new_exports = match self.cache.barrel_state.requested_exports.entry(idx) {
Entry::Occupied(mut occ) => {
let Some(diff) = imported_exports.subtract_and_merge_into(occ.get_mut()) else {
continue;
};
diff
}
Entry::Vacant(vac) => {
vac.insert(imported_exports.clone());
imported_exports
}
};
let mut tracked_records = std::mem::take(&mut barrel_module_state.tracked_records);
let mut remaining_imported_specifiers =
std::mem::take(&mut barrel_module_state.remaining_imported_specifiers);
let mut needed_records = barrel_module_state
.info
.take_needed_records(&new_exports, &mut remaining_imported_specifiers);
tracked_records.retain(|&rec_idx, (import_record_state, resolved_id)| {
let Some(needed_record) = needed_records.remove(&rec_idx) else {
return true;
};
let mut is_module_from_cache_snapshot = false;
let barrel_normal_module = match &self.intermediate_normal_modules.modules {
HybridIndexVec::IndexVec(modules) => modules[idx]
.as_ref()
.expect("Barrel module should exists in full build")
.as_normal()
.unwrap(),
HybridIndexVec::Map(modules) => {
if let Some(module) = modules.get(&idx) {
module
.as_ref()
.expect("Barrel module should exists in partial build")
.as_normal()
.unwrap()
} else {
is_module_from_cache_snapshot = true;
self
.cache
.get_snapshot()
.module_table
.get(idx)
.expect("Barrel module should exist in cache snapshot in partial scan mode")
.as_normal()
.unwrap()
}
}
};
let target_idx = match barrel_normal_module.import_records[rec_idx].resolved_module {
Some(existing_idx) => existing_idx,
None => {
let importer_record = ImporterRecord {
kind: barrel_normal_module.import_records[rec_idx].kind,
importer_path: barrel_normal_module.id.clone(),
importer_idx: barrel_normal_module.idx,
};
let new_idx = self.try_spawn_new_task(
resolved_id.clone(),
Some(ModuleTaskOwner::new(barrel_normal_module, import_record_state.span)),
false,
import_record_state.asserted_module_type.as_ref(),
user_defined_entries,
);
self.intermediate_normal_modules.importers[new_idx].push(importer_record);
if is_module_from_cache_snapshot {
self
.cache
.barrel_state
.resolved_barrel_modules
.entry(idx)
.or_default()
.push((rec_idx, new_idx));
} else {
self
.intermediate_normal_modules
.modules
.get_mut(idx)
.as_mut()
.expect("barrel module should exist")
.as_normal_mut()
.unwrap()
.import_records[rec_idx]
.resolved_module = Some(new_idx);
}
new_idx
}
};
let keep_tracking = match self.cache.barrel_state.barrel_infos.get(&target_idx) {
Some(Some(s)) => target_idx == idx || !s.tracked_records.is_empty(),
Some(None) => false,
None => true,
};
work_queue.push_back((target_idx, needed_record));
if keep_tracking {
remaining_imported_specifiers.contains_key(&rec_idx)
} else {
remaining_imported_specifiers.remove(&rec_idx);
false
}
});
let barrel_module_state =
self.cache.barrel_state.barrel_infos.get_mut(&idx).unwrap().as_mut().unwrap();
barrel_module_state.tracked_records = tracked_records;
barrel_module_state.remaining_imported_specifiers = remaining_imported_specifiers;
}
}
}