Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 34 additions & 67 deletions src/bundler/barrel_imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,13 +315,9 @@ struct BarrelWorkItem<'a> {
is_star: bool,
}

/// Resolve, process, and patch import records for a single barrel.
/// Used to inline-resolve deferred records whose source_index is still invalid.
fn resolve_barrel_records(
this: &mut BundleV2,
barrel_idx: u32,
barrels_to_resolve: &mut ArrayHashMap<u32, ()>,
) -> i32 {
/// Resolve the records the BFS just un-deferred in one barrel (`un_deferred`,
/// ascending indices), schedule their modules, and patch their source indices.
Comment thread
robobun marked this conversation as resolved.
fn resolve_barrel_records(this: &mut BundleV2, barrel_idx: u32, un_deferred: &[u32]) -> i32 {
let idx = barrel_idx as usize;
let target = this.graph.ast.items_target()[idx];
let loader = this.graph.input_files.items_loader()[idx];
Expand All @@ -340,6 +336,7 @@ fn resolve_barrel_records(
source: &source,
loader,
target,
only_records: Some(un_deferred),
});

this.graph.input_files.items_source_mut()[idx] = source;
Expand All @@ -353,22 +350,22 @@ fn resolve_barrel_records(
source_path,
loader,
target,
force_save: true,
only_records: Some(un_deferred),
..Default::default()
},
);

this.graph.ast.items_import_records_mut()[idx] = barrel_ir;

let _ = barrels_to_resolve.swap_remove(&barrel_idx);
scheduled
}

/// After a new file's import records are patched with source_indices,
/// record what this file requests from each target in requested_exports
/// (eagerly, before barrels are known), then BFS through barrel chains
/// to un-defer needed records. Un-deferred records are re-resolved through
/// resolveImportRecords (same path as initial resolution).
/// to un-defer needed records. Each un-deferred record is resolved at once
/// through resolveImportRecords (same path as initial resolution), so the BFS
/// can continue into the module it points at.
Comment thread
robobun marked this conversation as resolved.
/// Returns the number of newly scheduled parse tasks.
pub(crate) fn schedule_barrel_deferred_imports(
this: &mut BundleV2,
Expand Down Expand Up @@ -679,8 +676,6 @@ pub(crate) fn schedule_barrel_deferred_imports(
// dedup via requested_exports to prevent cycles.
let initial_queue_len = queue.len();

let mut barrels_to_resolve: ArrayHashMap<u32, ()> = ArrayHashMap::default();

let mut newly_scheduled: i32 = 0;
let mut qi: usize = 0;
while qi < queue.len() {
Expand Down Expand Up @@ -732,24 +727,15 @@ pub(crate) fn schedule_barrel_deferred_imports(
let barrel_ir = &mut this.graph.ast.items_import_records_mut()[barrel_idx as usize];

if item_is_star {
// Read flags by index, then mutate (borrowck).
let len = barrel_ir.len();
let mut un_deferred_any = false;
for idx in 0..len {
let flags = barrel_ir.as_slice()[idx].flags;
if flags.contains(import_record::Flags::IS_UNUSED)
&& !flags.contains(import_record::Flags::IS_INTERNAL)
{
if un_defer_record(barrel_ir, idx) {
barrels_to_resolve.put(barrel_idx, ())?;
un_deferred_any = true;
}
let mut un_deferred: Vec<u32> = Vec::new();
for idx in 0..barrel_ir.len() {
if un_defer_record(barrel_ir, idx) {
un_deferred.push(idx as u32);
}
}
// Resolve now: propagation below needs source indices.
if un_deferred_any {
newly_scheduled +=
resolve_barrel_records(this, barrel_idx, &mut barrels_to_resolve);
if !un_deferred.is_empty() {
newly_scheduled += resolve_barrel_records(this, barrel_idx, &un_deferred);
}

// A namespace request covers every export: request each
Expand Down Expand Up @@ -846,18 +832,12 @@ pub(crate) fn schedule_barrel_deferred_imports(
continue;
}
if un_defer_record(barrel_ir, star_idx as usize) {
barrels_to_resolve.put(barrel_idx, ())?;
}
let mut star_rec_si = barrel_ir.as_slice()[star_idx as usize].source_index;
if !star_rec_si.is_valid() {
// Deferred record was never resolved — resolve inline now.
newly_scheduled +=
resolve_barrel_records(this, barrel_idx, &mut barrels_to_resolve);
// Re-derive after resolution may have mutated slices.
star_rec_si = this.graph.ast.items_import_records_mut()[barrel_idx as usize]
.as_slice()[star_idx as usize]
.source_index;
// Resolve now: propagation below needs the source index.
newly_scheduled += resolve_barrel_records(this, barrel_idx, &[star_idx]);
}
let star_rec_si = this.graph.ast.items_import_records()[barrel_idx as usize]
.as_slice()[star_idx as usize]
.source_index;
if star_rec_si.is_valid() {
queue.push(BarrelWorkItem {
barrel_source_index: star_rec_si.get(),
Expand All @@ -872,7 +852,9 @@ pub(crate) fn schedule_barrel_deferred_imports(

let barrel_ir = &mut this.graph.ast.items_import_records_mut()[barrel_idx as usize];
if un_defer_record(barrel_ir, resolution.import_record_index as usize) {
barrels_to_resolve.put(barrel_idx, ())?;
// Resolve now: propagation below needs the source index.
newly_scheduled +=
resolve_barrel_records(this, barrel_idx, &[resolution.import_record_index]);
}

// `original_alias` is an arena-backed `StoreStr` valid for the
Expand All @@ -881,38 +863,23 @@ pub(crate) fn schedule_barrel_deferred_imports(
Some(p) => p.slice(),
None => alias,
};
if (resolution.import_record_index as usize) < barrel_ir.len() {
let mut rec_si =
barrel_ir.as_slice()[resolution.import_record_index as usize].source_index;
if !rec_si.is_valid() {
// Deferred record was never resolved — resolve inline now.
newly_scheduled +=
resolve_barrel_records(this, barrel_idx, &mut barrels_to_resolve);
rec_si = this.graph.ast.items_import_records_mut()[barrel_idx as usize].as_slice()
[resolution.import_record_index as usize]
.source_index;
}
if rec_si.is_valid() {
// When the barrel re-exports a namespace import (`import * as X; export { X }`),
// propagate as a star import so the target barrel loads all exports.
queue.push(BarrelWorkItem {
barrel_source_index: rec_si.get(),
alias: propagate_alias,
is_star: resolution.alias_is_star,
});
}
let rec_si = this.graph.ast.items_import_records()[barrel_idx as usize]
.as_slice()
.get(resolution.import_record_index as usize)
.map(|rec| rec.source_index);
if let Some(rec_si) = rec_si.filter(|si| si.is_valid()) {
// When the barrel re-exports a namespace import (`import * as X; export { X }`),
// propagate as a star import so the target barrel loads all exports.
Comment thread
robobun marked this conversation as resolved.
queue.push(BarrelWorkItem {
barrel_source_index: rec_si.get(),
alias: propagate_alias,
is_star: resolution.alias_is_star,
});
}

qi += 1;
}

// Re-resolve any remaining un-deferred records through the normal resolution path.
while barrels_to_resolve.count() > 0 {
let barrel_source_index = barrels_to_resolve.keys()[0];
newly_scheduled +=
resolve_barrel_records(this, barrel_source_index, &mut barrels_to_resolve);
}

Ok(newly_scheduled)
}

Expand Down
40 changes: 32 additions & 8 deletions src/bundler/bundle_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5879,6 +5879,7 @@ pub mod bv2_impl {
source: &result.source,
loader: result.loader,
target,
only_records: None,
});

if let Some(err) = resolve_result.last_error {
Expand Down Expand Up @@ -5937,16 +5938,26 @@ pub mod bv2_impl {
pub(crate) source: &'a bun_ast::Source,
pub(crate) loader: Loader,
pub(crate) target: options::Target,
/// See `only_selected_record`.
pub(crate) only_records: Option<&'a [u32]>,
}

pub(crate) struct ResolveImportRecordResult {
pub(crate) resolve_queue: ResolveQueue,
pub(crate) last_error: Option<Error>,
}

/// `only_records`: barrel un-deferral passes the ascending indices of the records
/// it just un-deferred. A missing `source_index` does not mean a record is still
/// unresolved (external, failed, or waiting for an onResolve plugin).
Comment thread
robobun marked this conversation as resolved.
#[inline]
fn only_selected_record(only_records: Option<&[u32]>, record_index: usize) -> bool {
only_records.is_none_or(|only| only.binary_search(&(record_index as u32)).is_ok())
}

impl<'a> BundleV2<'a> {
/// Resolve all unresolved import records for a module. Skips records that
/// are already resolved (valid source_index), unused, or internal.
/// Resolve all unresolved import records for a module, or only `ctx.only_records`.
/// Skips records that are already resolved (valid source_index), unused, or internal.
Comment thread
robobun marked this conversation as resolved.
/// Returns a resolve queue of new modules to schedule, plus any fatal error.
/// Used by both initial parse resolution and barrel un-deferral.
pub(crate) fn resolve_import_records(
Expand All @@ -5956,6 +5967,8 @@ pub mod bv2_impl {
let source = ctx.source;
let loader = ctx.loader;
let source_dir = source.path.source_dir();
let only_records = ctx.only_records;
debug_assert!(only_records.is_none_or(<[u32]>::is_sorted));
let mut estimated_resolve_queue_count: usize = 0;
for import_record in ctx.import_records.iter_mut() {
if import_record
Expand Down Expand Up @@ -5988,12 +6001,19 @@ pub mod bv2_impl {
|| import_record.source_index.is_valid()))
as usize;
}
if let Some(only) = only_records {
estimated_resolve_queue_count = estimated_resolve_queue_count.min(only.len());
}
let mut resolve_queue = ResolveQueue::default();
resolve_queue.reserve(estimated_resolve_queue_count);

let mut last_error: Option<Error> = None;

'outer: for (i, import_record) in ctx.import_records.iter_mut().enumerate() {
if !only_selected_record(only_records, i) {
continue;
}

// Preserve original import specifier before resolution modifies path
if import_record.original_path.is_empty() {
import_record.original_path = import_record.path.text;
Expand Down Expand Up @@ -6758,9 +6778,9 @@ pub mod bv2_impl {
pub(crate) loader: Loader,
pub(crate) target: options::Target,
pub(crate) redirect_import_record_index: u32,
/// When true, always save source indices regardless of dev_server/loader.
/// Used for barrel un-deferral where records must always be connected.
pub(crate) force_save: bool,
/// See `only_selected_record`. `Some` also saves the source indices regardless
/// of dev_server/loader: the barrel BFS follows them.
Comment thread
robobun marked this conversation as resolved.
pub(crate) only_records: Option<&'a [u32]>,
}

impl Default for PatchImportRecordsCtx<'_> {
Expand All @@ -6771,7 +6791,7 @@ pub mod bv2_impl {
loader: Loader::File,
target: Target::Browser,
redirect_import_record_index: u32::MAX,
force_save: false,
only_records: None,
}
}
}
Expand All @@ -6789,7 +6809,8 @@ pub mod bv2_impl {
// across the `&mut self.graph.build_graphs[...]` borrow
// below, so address the disjoint `self.graph.*` fields directly instead.
let input_file_loaders = self.graph.input_files.items_loader();
let save_import_record_source_index = ctx.force_save
debug_assert!(ctx.only_records.is_none_or(<[u32]>::is_sorted));
let save_import_record_source_index = ctx.only_records.is_some()
|| self.dev_server.is_none()
|| ctx.loader == Loader::Html
|| ctx.loader.is_css();
Expand All @@ -6816,6 +6837,9 @@ pub mod bv2_impl {
// so borrowck sees it as disjoint from `self.graph.input_files` above.
let path_to_source_index_map = &mut self.graph.build_graphs[ctx.target];
for (i, record) in import_records.as_mut_slice().iter_mut().enumerate() {
if !only_selected_record(ctx.only_records, i) {
continue;
}
if let Some(source_index) = path_to_source_index_map.get_path(&record.path) {
if save_import_record_source_index
|| input_file_loaders[source_index as usize].is_css()
Expand Down Expand Up @@ -7127,7 +7151,7 @@ pub mod bv2_impl {
loader: result.loader,
target: result.ast.target,
redirect_import_record_index: result.ast.redirect_import_record_index,
force_save: false,
only_records: None,
},
);

Expand Down
Loading
Loading