Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 0 additions & 3 deletions src/ast/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -878,9 +878,6 @@ impl ExportsKind {
Self::EsmWithDynamicFallback | Self::EsmWithDynamicFallbackFromCjs
)
}

// `to_module_type()` lives in `bun_options_types` as
// `impl From<ExportsKind> for ModuleType` (would cycle here).
}

#[derive(Copy, Clone)]
Expand Down
1 change: 1 addition & 0 deletions src/bundler/AstBuilder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> {
export_star_import_records: bun_alloc::AstAlloc::vec(),
approximate_newline_count: 1,
exports_kind: ExportsKind::Esm,
module_type: crate::options::ModuleType::Unknown,
named_imports: core::mem::take(&mut self.named_imports),
named_exports: core::mem::take(&mut self.named_exports),
dynamic_import_aliases: Default::default(),
Expand Down
2 changes: 1 addition & 1 deletion src/bundler/LinkerContext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2287,7 +2287,7 @@ impl<'a> LinkerContext<'a> {

minify_whitespace: self.options.minify_whitespace,
minify_syntax: self.options.minify_syntax,
input_module_type: ast.exports_kind.into(),
input_module_type: ast.module_type,
module_type: self.options.output_format,
print_dce_annotations: self.options.emit_dce_annotations,
has_run_symbol_renamer: true,
Expand Down
9 changes: 9 additions & 0 deletions src/bundler/ParseTask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2606,6 +2606,14 @@ pub mod parse_worker {
topts.tree_shaking
};
opts.code_splitting = topts.code_splitting;
// A task that did not come through the resolver (a plugin `onResolve`
// result, an in-memory source) has no module type yet. The extension
// still decides it, as it does in the resolver.
Comment thread
robobun marked this conversation as resolved.
Resolved
if task.module_type == options::ModuleType::Unknown {
if let Some(from_ext) = _resolver::module_type_from_ext(task.path.name().ext) {
task.module_type = from_ext;
}
}
opts.module_type = task.module_type;
opts.is_entry_point = task.is_entry_point;

Expand Down Expand Up @@ -2667,6 +2675,7 @@ pub mod parse_worker {
};

ast.target = target;
ast.module_type = task.module_type;
if ast.parts.len() <= 1
&& ast.css.is_none()
&& (task.loader.is_none() || task.loader.unwrap() != Loader::Html)
Expand Down
9 changes: 8 additions & 1 deletion src/bundler/bundle_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3769,6 +3769,7 @@ pub mod bv2_impl {
source: &mut bun_ast::Source,
loader: Loader,
known_target: options::Target,
module_type: options::ModuleType,
) -> Result<IndexInt, AllocError> {
let source_index = Index::init(u32::try_from(self.graph.ast.len()).expect("int cast"));
let _ = self.graph.ast.append(JSAst::empty_in(self.graph.heap)); // OOM/capacity: fire-and-forget
Expand Down Expand Up @@ -3822,7 +3823,7 @@ pub mod bv2_impl {
side_effects: bun_ast::SideEffects::HasSideEffects,
jsx,
source_index: bun_ast::Index::init(source_index.get()),
module_type: options::ModuleType::Unknown,
module_type,
emit_decorator_metadata: false, // TODO
package_version: bun_ast::StoreStr::EMPTY,
loader: Some(loader),
Expand Down Expand Up @@ -7437,6 +7438,10 @@ pub mod bv2_impl {
// and `.clone()` where an owned copy is needed.
let source_loader: Loader =
this.graph.input_files.items_loader()[result_source_index];
// The re-parsed copies keep the module type the resolver
// found for the original file.
Comment thread
robobun marked this conversation as resolved.
Resolved
let source_module_type: options::ModuleType =
this.graph.ast.items_module_type()[result_source_index];

let (reference_source_index, ssr_index) = if separate_ssr_graph {
// Enqueue two files, one in server graph, one in ssr graph.
Expand Down Expand Up @@ -7476,6 +7481,7 @@ pub mod bv2_impl {
&mut ssr_source,
source_loader,
Target::ServerComponentsSsr,
source_module_type,
)
.expect("oom");

Expand All @@ -7499,6 +7505,7 @@ pub mod bv2_impl {
&mut server_source,
source_loader,
Target::Browser,
source_module_type,
)
.expect("oom");

Expand Down
16 changes: 15 additions & 1 deletion src/bundler/bundled_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub(crate) type CssCol = Option<CssAstRef>;
use bun_ast::import_record;
use bun_core::strings;

use crate::options::ModuleType;
use bun_ast::ast_result::Ast;
use bun_ast::{CharFreq, ExportsKind, Ref, Scope, SlotCounts, StoreStr, TlaCheck};
use bun_ast::{part, symbol};
Expand All @@ -43,14 +44,22 @@ pub type TopLevelSymbolToParts = bun_ast::ast_result::TopLevelSymbolToParts;
// `BundledAstColumns` (`items_named_imports()`,
// `items_named_exports()`, …) at `crate::bundled_ast::*`.
//
// 26 fields ≤ `multi_array_list::MAX_FIELDS` (32).
// 29 fields ≤ `multi_array_list::MAX_FIELDS` (32).

pub struct BundledAst<'arena> {
pub(crate) approximate_newline_count: u32,
pub(crate) nested_scope_slot_counts: SlotCounts,

pub(crate) exports_kind: ExportsKind,

/// The module type from the file extension (`.mjs`, `.mts`, `.cjs`, `.cts`)
/// or else the nearest package.json `"type"`. Unlike `exports_kind` it does
/// not depend on the syntax the file uses. The printer passes
/// `isNodeMode = 1` to `__toESM` only when this is `Esm`: then a default
/// import of a CommonJS module is the whole `module.exports`, as in Node.
/// Every other importer honors the `__esModule` marker, as esbuild does.
Comment thread
robobun marked this conversation as resolved.
Resolved
pub(crate) module_type: ModuleType,

/// These are stored at the AST level instead of on individual AST nodes so
/// they can be manipulated efficiently without a full AST traversal
pub(crate) import_records: import_record::List<'arena>,
Expand Down Expand Up @@ -106,6 +115,7 @@ bun_collections::multi_array_columns! {
approximate_newline_count: u32,
nested_scope_slot_counts: SlotCounts,
exports_kind: ExportsKind,
module_type: ModuleType,
import_records: import_record::List<'arena>,
hashbang: StoreStr,
export_default_alias_of_import: Ref,
Expand Down Expand Up @@ -163,6 +173,7 @@ impl<'arena> BundledAst<'arena> {
approximate_newline_count: 0,
nested_scope_slot_counts: SlotCounts::default(),
exports_kind: ExportsKind::None,
module_type: ModuleType::Unknown,
import_records: import_record::List::new_in(arena),
hashbang: StoreStr::EMPTY,
export_default_alias_of_import: Ref::NONE,
Expand Down Expand Up @@ -296,6 +307,9 @@ impl<'arena> BundledAst<'arena> {
nested_scope_slot_counts: ast.nested_scope_slot_counts,

exports_kind: ast.exports_kind,
// The parser does not record it; `ParseTask::run` sets it from the
// resolver result after `init`, next to `target`.
Comment thread
robobun marked this conversation as resolved.
Resolved
module_type: ModuleType::Unknown,

import_records: ast.import_records,

Expand Down
12 changes: 10 additions & 2 deletions src/js_printer/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1352,8 +1352,11 @@ pub struct Options<'a> {

pub require_or_import_meta_for_source_callback: RequireOrImportMetaCallback,

/// The module type of the importing file (after linking), used to determine interop helper behavior.
/// Controls whether __toESM uses Node ESM semantics (isNodeMode=1 for .esm) or respects __esModule markers.
/// The module type of the file being printed, from its extension (`.mjs`,
/// `.mts`, `.cjs`, `.cts`) or the nearest package.json `"type"`, not from
/// the syntax it uses. `Esm` makes `__toESM` run with `isNodeMode = 1`: a
/// default import of a CommonJS module is then the whole `module.exports`,
/// as in Node. Otherwise `__toESM` honors the `__esModule` marker.
Comment thread
robobun marked this conversation as resolved.
Resolved
pub input_module_type: bundle_opts::ModuleType,
pub module_type: bundle_opts::Format,

Expand Down Expand Up @@ -2935,6 +2938,11 @@ pub(crate) mod __gated_printer {
self.print(b")");

if wrap_with_to_esm {
if self.options.input_module_type == bundle_opts::ModuleType::Esm {
self.print(b",");
self.print_space();
self.print(b"1");
}
self.print(b")");
}
if wrap {
Expand Down
15 changes: 0 additions & 15 deletions src/options_types/bundle_enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,18 +263,3 @@ pub enum BuiltInModule {
Import(Box<[u8]>),
Code(Box<[u8]>),
}

// `ExportsKind::to_module_type` — moved here from `bun_ast::nodes` to avoid
// the `bun_options_types → bun_ast → bun_options_types` cycle.
impl From<bun_ast::ExportsKind> for ModuleType {
fn from(k: bun_ast::ExportsKind) -> Self {
use bun_ast::ExportsKind as K;
match k {
K::None => ModuleType::Unknown,
K::Cjs => ModuleType::Cjs,
K::EsmWithDynamicFallback | K::EsmWithDynamicFallbackFromCjs | K::Esm => {
ModuleType::Esm
}
}
}
}
4 changes: 3 additions & 1 deletion src/resolver/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ pub use tsconfig_json::TSConfigJSON;
// `result` / `standalone_module_graph` sibling modules.
/// Re-export so dependents can spell `bun_resolver::install_types::AutoInstaller`.
pub use ::bun_install_types::resolver_hooks as install_types;
pub use resolver::{AnyResolveWatcher, BrowserMapPathKind, Bufs, Dirname, Resolver};
pub use resolver::{
AnyResolveWatcher, BrowserMapPathKind, Bufs, Dirname, Resolver, module_type_from_ext,
};
pub use result::{
DebugLogs, DirEntryResolveQueueItem, ExternalKind, FlushMode, LoadResult, MatchResult,
MatchStatus, PathPair, PendingResolution, PendingResolutionTag, Result, ResultFlags,
Expand Down
18 changes: 2 additions & 16 deletions src/resolver/package_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1255,8 +1255,6 @@ pub type ConditionsMap = StringArrayHashMap<()>;
pub(crate) struct ESModule<'a> {
pub(crate) debug_logs: Option<&'a mut resolver::DebugLogs>,
pub(crate) conditions: &'a ConditionsMap,
// allocator dropped — global mimalloc
pub(crate) module_type: &'a mut ModuleType,
}

#[derive(Clone)]
Expand Down Expand Up @@ -1535,8 +1533,8 @@ fn module_bufs() -> *mut ModuleBufs {
})
}

// `module_type` / `debug_logs` are `&'a mut T`, so reading/writing them
// requires `&mut self`. All resolution methods take `&mut self`.
// `debug_logs` is `&'a mut T`, so writing to it requires `&mut self`. All
// resolution methods take `&mut self`.
Comment thread
robobun marked this conversation as resolved.
Resolved
impl<'a> ESModule<'a> {
pub(crate) fn resolve(
&mut self,
Expand Down Expand Up @@ -2090,26 +2088,16 @@ impl<'a> ESModule<'a> {
));
}

let prev_module_type = *self.module_type;
let result = self.resolve_target::<PATTERN>(
package_url,
&entry.value,
subpath,
internal,
);
if result.status.is_undefined() {
*self.module_type = prev_module_type;
continue;
}

if key == b"import" {
*self.module_type = ModuleType::Esm;
}

if key == b"require" {
*self.module_type = ModuleType::Cjs;
}

return result;
}

Expand Down Expand Up @@ -2149,7 +2137,6 @@ impl<'a> ESModule<'a> {

for target_value in array.iter() {
// Let resolved be the result, continuing the loop on any Invalid Package Target error.
let prev_module_type = *self.module_type;
let result = self.resolve_target::<PATTERN>(
package_url,
target_value,
Expand All @@ -2163,7 +2150,6 @@ impl<'a> ESModule<'a> {
}

if result.status.is_undefined() {
*self.module_type = prev_module_type;
continue;
}

Expand Down
34 changes: 11 additions & 23 deletions src/resolver/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1570,17 +1570,6 @@ impl<'a> Resolver<'a> {
}
}

// If you use mjs or mts, then you're using esm
// If you use cjs or cts, then you're using cjs
// This should win out over the module type from package.json
if !kind.is_from_css()
&& module_type == options::ModuleType::Unknown
&& name.ext.len() == 4
{
module_type =
module_type_from_ext(name.ext).unwrap_or(options::ModuleType::Unknown);
}

// Probe the listing in one `entries_mutex` critical section: a
// concurrent resolver at a newer generation rewrites this `DirEntry`'s
// map in place under that lock. The entry pointer stays valid after
Expand Down Expand Up @@ -1684,6 +1673,14 @@ impl<'a> Resolver<'a> {
}
}

// `.mjs`, `.mts`, `.cjs` and `.cts` decide the module type by
// themselves, over any package.json "type".
Comment thread
robobun marked this conversation as resolved.
Resolved
if !kind.is_from_css() {
if let Some(from_ext) = module_type_from_ext(result.path_pair.primary.name().ext) {
module_type = from_ext;
}
}

result.module_type = module_type;
Ok(())
}
Expand Down Expand Up @@ -2623,7 +2620,6 @@ impl<'a> Resolver<'a> {
if let Some(package_json) = pkg_dir_info.package_json() {
if let Some(exports_map) = package_json.exports.as_ref() {
// The condition set is determined by the kind of import
let mut module_type = package_json.module_type;
// NOTE: keeping a single
// `ESModule` (which holds `&mut self.debug_logs`) alive across a
// `&mut self` call is aliased-&mut UB. Build a fresh short-lived
Expand All @@ -2649,7 +2645,6 @@ impl<'a> Resolver<'a> {
_ => &self.opts.conditions.import,
},
debug_logs: self.debug_logs.as_mut(),
module_type: &mut module_type,
}
.resolve(b"/", esm.subpath, &exports_map.root);
// ESModule temporary dropped here; `self` is unborrowed.
Expand All @@ -2666,7 +2661,6 @@ impl<'a> Resolver<'a> {
.is_success()
{
out.is_node_module = true;
out.module_type = module_type;
self.extension_order = prev_extension_order;
if let Some(d) = self.debug_logs.as_mut() {
d.decrease_indent();
Expand Down Expand Up @@ -2706,7 +2700,6 @@ impl<'a> Resolver<'a> {
_ => &self.opts.conditions.import,
},
debug_logs: self.debug_logs.as_mut(),
module_type: &mut module_type,
}
.resolve(
b"/",
Expand All @@ -2725,7 +2718,6 @@ impl<'a> Resolver<'a> {
.is_success()
{
out.is_node_module = true;
out.module_type = module_type;
self.extension_order = prev_extension_order;
if let Some(d) = self.debug_logs.as_mut() {
d.decrease_indent();
Expand Down Expand Up @@ -3123,7 +3115,6 @@ impl<'a> Resolver<'a> {
Ok(dir_info_to_use_) => {
if let Some(pkg_dir_info) = dir_info_to_use_ {
let abs_package_path = pkg_dir_info.abs_path;
let mut module_type = options::ModuleType::Unknown;
if let Some(package_json) = pkg_dir_info.package_json() {
if let Some(exports_map) = package_json.exports.as_ref() {
// The condition set is determined by the kind of import
Expand All @@ -3144,7 +3135,6 @@ impl<'a> Resolver<'a> {
_ => &self.opts.conditions.import,
},
debug_logs: self.debug_logs.as_mut(),
module_type: &mut module_type,
}
.resolve(b"/", esm.subpath, &exports_map.root);

Expand Down Expand Up @@ -3183,7 +3173,6 @@ impl<'a> Resolver<'a> {
_ => &self.opts.conditions.import,
},
debug_logs: self.debug_logs.as_mut(),
module_type: &mut module_type,
}
.resolve(
b"/",
Expand Down Expand Up @@ -4982,7 +4971,6 @@ impl<'a> Resolver<'a> {
}
return MatchStatus::NotFound;
}
let mut module_type = options::ModuleType::Unknown;

// NOTE: keeping the `ESModule`'s borrow of `self.debug_logs` alive
// across the subsequent `&mut self` calls would be aliased-&mut UB, so
Expand All @@ -4996,10 +4984,8 @@ impl<'a> Resolver<'a> {
_ => &self.opts.conditions.import,
},
debug_logs: self.debug_logs.as_mut(),
module_type: &mut module_type,
}
.resolve_imports(import_path, &imports_map.root);
let _ = module_type;

if esm_resolution.status == crate::package_json::Status::PackageResolve {
// https://github.com/oven-sh/bun/issues/4972
Expand Down Expand Up @@ -6741,8 +6727,10 @@ bun_core::comptime_string_map! {
};
}

/// The module type a file extension decides by itself: `.mjs` and `.mts` are
/// ESM, `.cjs` and `.cts` are CommonJS. `None` for every other extension.
Comment thread
robobun marked this conversation as resolved.
Resolved
#[inline]
fn module_type_from_ext(ext: &[u8]) -> Option<options::ModuleType> {
pub fn module_type_from_ext(ext: &[u8]) -> Option<options::ModuleType> {
MODULE_TYPE_FROM_EXT.get(ext).copied()
}

Expand Down
Loading
Loading