Skip to content
Open
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
1 change: 1 addition & 0 deletions src/bundler/aot/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ impl FileFinder {
search.specifier,
kind,
GlobalCache::disable,
None,
);
let failure = resolver.esm_failure.take();
let result = match result {
Expand Down
2 changes: 2 additions & 0 deletions src/jsc/JSGlobalObject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,7 @@ extern "C" fn Zig__GlobalObject__resolve(
source,
Some(query),
crate::virtual_machine::ResolveMode::Esm,
None,
) {
Ok(Ok(path)) => *res = ErrorableString::ok(path),
Ok(Err(value)) => *res = ErrorableString::err(value),
Expand Down Expand Up @@ -1404,6 +1405,7 @@ extern "C" fn Bun__resolveWithOnResolve(
Some(query),
crate::virtual_machine::ResolveMode::Esm,
true,
None,
) {
Ok(None) => return false,
Ok(Some(Ok(path))) => *res = ErrorableString::ok(path),
Expand Down
1 change: 1 addition & 0 deletions src/jsc/NodeModuleModule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ fn find_path_inner(
cur_path,
None,
crate::virtual_machine::ResolveMode::RequireResolve,
None,
)?
.ok())
}
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/RuntimeTranspilerStore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ impl TranspilerJob {
// a bytewise copy. SAFETY: `vm.transpiler` is read via
// `addr_of!` (no `&VirtualMachine` formed); every internal raw pointer in the copy
// still targets memory owned by `vm.transpiler` (resolver caches, define, env) which
// outlives this stack frame; `vm.transpiler` is not concurrently mutated.
// outlives this stack frame. The JS thread writes `vm.transpiler` while jobs run.
// The by-value copy must not be deinitialized; `ManuallyDrop` suppresses Drop so owned
// fields aren't double-freed against `vm.transpiler`.
let mut transpiler_storage =
Expand Down
37 changes: 27 additions & 10 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5518,6 +5518,7 @@ impl VirtualMachine {
is_esm: bool,
is_a_file_path: bool,
global_cache: bun_resolver::GlobalCache,
paths: Option<&[bun_core::String]>,
) -> crate::CrateResult<()> {
use bun_js_parser::Macro;
use bun_resolver::{ResultUnion, node_fallbacks};
Expand Down Expand Up @@ -5600,6 +5601,7 @@ impl VirtualMachine {
normalized_specifier,
import_kind,
global_cache,
paths,
) {
ResultUnion::Success(r) => break r,
ResultUnion::Failure(e) => return Err(e.into()),
Expand All @@ -5616,11 +5618,14 @@ impl VirtualMachine {
continue;
}
if !retry_on_not_found {
if let Some(chai) = self.resolve_chai_next_to_vitest(
normalized_specifier,
source_to_use,
import_kind,
) {
// `paths` replaces the importer, which is where that lookup starts.
if paths.is_none()
&& let Some(chai) = self.resolve_chai_next_to_vitest(
normalized_specifier,
source_to_use,
import_kind,
)
{
break chai;
}
return Err(crate::CrateError::ModuleNotFound);
Expand Down Expand Up @@ -5747,12 +5752,19 @@ impl VirtualMachine {
b"vitest/package.json",
kind,
GlobalCache::disable,
None,
) else {
return None;
};
let vitest_dir =
bun_resolver::fs::PathName::init(vitest.path_const()?.text).dir_with_trailing_slash();
match resolver.resolve_and_auto_install(vitest_dir, b"chai", kind, GlobalCache::disable) {
match resolver.resolve_and_auto_install(
vitest_dir,
b"chai",
kind,
GlobalCache::disable,
None,
) {
ResultUnion::Success(chai) => Some(chai),
_ => None,
}
Expand All @@ -5767,6 +5779,7 @@ impl VirtualMachine {
source: &bun_core::String,
mut query_string: Option<&mut bun_core::String>,
mode: ResolveMode,
paths: Option<&[bun_core::String]>,
) -> JsResult<Result<bun_core::String, JSValue>> {
if let Some(resolved) = Self::resolve_with_on_resolve::<IS_A_FILE_PATH>(
global,
Expand All @@ -5775,6 +5788,7 @@ impl VirtualMachine {
query_string.as_deref_mut(),
mode,
false,
paths,
)? {
return Ok(resolved);
}
Expand All @@ -5785,17 +5799,20 @@ impl VirtualMachine {
query_string,
mode,
global.bun_vm().transpiler.resolver.opts.global_cache,
paths,
)
}

/// `None` when no `onResolve` claims `specifier`, or `same_is_no_answer` is set and it returns `specifier` unchanged.
/// `paths` is where the path that a callback returns is looked up. A resolve that a callback makes has its own.
pub(crate) fn resolve_with_on_resolve<const IS_A_FILE_PATH: bool>(
global: &JSGlobalObject,
specifier: &bun_core::String,
source: &bun_core::String,
query_string: Option<&mut bun_core::String>,
mode: ResolveMode,
same_is_no_answer: bool,
paths: Option<&[bun_core::String]>,
) -> JsResult<Option<Result<bun_core::String, JSValue>>> {
let answer = match run_on_resolve(global, specifier, source)? {
None => return Ok(None),
Expand Down Expand Up @@ -5834,6 +5851,7 @@ impl VirtualMachine {
query_string,
mode,
global_cache,
paths,
)?;
// Not on disk, for an `onLoad` to serve.
if resolved.is_err() && global.has_on_load(&answer.to_utf8())? {
Expand All @@ -5849,6 +5867,7 @@ impl VirtualMachine {
query_string: Option<&mut bun_core::String>,
mode: ResolveMode,
global_cache: bun_resolver::GlobalCache,
paths: Option<&[bun_core::String]>,
) -> JsResult<Result<bun_core::String, JSValue>> {
const MAX_LEN: usize = (bun_paths::MAX_PATH_BYTES as f64 * 1.5) as usize;
// `data:` URLs carry the module source inline and never touch the
Expand Down Expand Up @@ -5994,6 +6013,7 @@ impl VirtualMachine {
mode.is_esm(),
IS_A_FILE_PATH,
global_cache,
paths,
);
if let Err(err) = resolve_result {
let import_kind = mode.import_kind();
Expand Down Expand Up @@ -8365,27 +8385,24 @@ fn wrap_unhandled_rejection_error_for_uncaught_exception(
.to_js())
}

/// Keeps a resolve made inside an `onResolve` callback from inheriting the outer resolve's state.
/// The VM is inside an `onResolve` callback for as long as this lives.
struct OnResolveScope<'a> {
vm: &'a VirtualMachine,
was_in_on_resolve: bool,
custom_dir_paths: Option<&'static [bun_core::String]>,
}

impl<'a> OnResolveScope<'a> {
fn enter(vm: &'a VirtualMachine) -> Self {
Self {
vm,
was_in_on_resolve: vm.is_in_on_resolve.replace(true),
custom_dir_paths: vm.as_mut().transpiler.resolver.custom_dir_paths.take(),
}
}
}

impl Drop for OnResolveScope<'_> {
fn drop(&mut self) {
self.vm.is_in_on_resolve.set(self.was_in_on_resolve);
self.vm.as_mut().transpiler.resolver.custom_dir_paths = self.custom_dir_paths;
}
}

Expand Down
36 changes: 20 additions & 16 deletions src/resolver/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,13 +566,6 @@ pub struct Resolver<'a> {
/// over "module" in package.json
pub prefer_module_field: bool,

/// This is an array of paths to resolve against. Used for passing an
/// object '{ paths: string[] }' to `require` and `resolve`; This field
/// is overwritten while the resolution happens.
///
/// When this is null, it is as if it is set to `&.{ path.dirname(referrer) }`.
pub custom_dir_paths: Option<&'a [bun_core::String]>,

/// Why "exports" or "imports" led to no file: the first time since this was last `None`.
pub esm_failure: Option<crate::package_json::Status>,
}
Expand Down Expand Up @@ -649,9 +642,6 @@ impl<'a> Resolver<'a> {
mutex: from.mutex,
dir_cache: from.dir_cache,
prefer_module_field: from.prefer_module_field,
// Transient per-resolve scratch (only set for `require(..., {paths})`);
// never carried across worker init.
custom_dir_paths: None,
esm_failure: None,
}
}
Expand Down Expand Up @@ -1006,7 +996,6 @@ impl<'a> Resolver<'a> {
store_fd: false,
standalone_module_graph: None,
prefer_module_field: true,
custom_dir_paths: None,
esm_failure: None,
}
}
Expand Down Expand Up @@ -1254,12 +1243,14 @@ impl<'a> Resolver<'a> {
.or_else(|| graph.file_at_source_path(path))
}

/// `paths` is the option of `require.resolve()`: where a relative or bare `import_path` is looked up, in place of `source_dir`.
pub fn resolve_and_auto_install<'r>(
&mut self,
source_dir: &[u8],
import_path: &'r [u8],
kind: ast::ImportKind,
global_cache: GlobalCache,
paths: Option<&[bun_core::String]>,
) -> ResultUnion<'r> {
let _tracer = ::bun_perf::trace(::bun_perf::PerfEvent::ModuleResolverResolve);

Expand Down Expand Up @@ -1619,8 +1610,13 @@ impl<'a> Resolver<'a> {
return ResultUnion::NotFound;
}

let mut tmp =
self.resolve_without_symlinks(source_dir_normalized, import_path, kind, global_cache);
let mut tmp = self.resolve_without_symlinks(
source_dir_normalized,
import_path,
kind,
global_cache,
paths,
);
if matches!(tmp, ResultUnion::NotFound)
&& let Some(path) =
self.resolve_at_source_path(source_dir_normalized, import_path, kind)
Expand Down Expand Up @@ -1659,6 +1655,7 @@ impl<'a> Resolver<'a> {
&import_path[0..suffix],
kind,
global_cache,
paths,
);
if matches!(result2, ResultUnion::NotFound) {
break 'try_without_suffix;
Expand Down Expand Up @@ -1768,7 +1765,13 @@ impl<'a> Resolver<'a> {
import_path: &'r [u8],
kind: ast::ImportKind,
) -> crate::CrateResult<Result<'r>> {
match self.resolve_and_auto_install(source_dir, import_path, kind, GlobalCache::disable) {
match self.resolve_and_auto_install(
source_dir,
import_path,
kind,
GlobalCache::disable,
None,
) {
ResultUnion::Success(result) => Ok(result),
ResultUnion::Pending(_) | ResultUnion::NotFound => Err(crate::Error::ModuleNotFound),
ResultUnion::Failure(e) => Err(e),
Expand Down Expand Up @@ -2008,6 +2011,7 @@ impl<'a> Resolver<'a> {
input_import_path: &'r [u8],
kind: ast::ImportKind,
global_cache: GlobalCache,
paths: Option<&[bun_core::String]>,
) -> ResultUnion<'r> {
debug_assert!(bun_paths::is_absolute(source_dir));

Expand Down Expand Up @@ -2142,7 +2146,7 @@ impl<'a> Resolver<'a> {
let check_package = is_package_path_;

if check_relative {
if let Some(custom_paths) = self.custom_dir_paths {
if let Some(custom_paths) = paths {
// @branchHint(.unlikely)
bun_core::hint::cold();
for custom_path in custom_paths {
Expand Down Expand Up @@ -2284,7 +2288,7 @@ impl<'a> Resolver<'a> {
}
}

if let Some(custom_paths) = self.custom_dir_paths {
if let Some(custom_paths) = paths {
bun_core::hint::cold();
for custom_path in custom_paths {
let custom_utf8 = custom_path.to_utf8();
Expand Down
21 changes: 8 additions & 13 deletions src/runtime/api/BunObject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -780,8 +780,9 @@ fn do_resolve_with_args<const IS_FILE_PATH: bool>(
specifier: &BunString,
from: &BunString,
mode: ResolveMode,
paths: Option<&[BunString]>,
) -> JsResult<BunString> {
match resolve_with_args::<IS_FILE_PATH>(ctx, specifier, from, mode)? {
match resolve_with_args::<IS_FILE_PATH>(ctx, specifier, from, mode, paths)? {
Resolved::Found(value) if mode == ResolveMode::RequireResolve => {
Ok(find_source_path_of_compiled_file(specifier, &value).unwrap_or(value))
}
Expand Down Expand Up @@ -815,6 +816,7 @@ fn resolve_with_args<const IS_FILE_PATH: bool>(
specifier: &BunString,
from: &BunString,
mode: ResolveMode,
paths: Option<&[BunString]>,
) -> JsResult<Resolved> {
let mut query_string = BunString::EMPTY;

Expand All @@ -832,6 +834,7 @@ fn resolve_with_args<const IS_FILE_PATH: bool>(
from,
Some(&mut query_string),
mode,
paths,
)? {
Ok(path) => path,
Err(err) if err.as_class_ref::<jsc::ResolveMessage>().is_some() => {
Expand Down Expand Up @@ -870,7 +873,7 @@ pub(crate) fn resolve_sync(
} else {
ResolveMode::Require
};
do_resolve_with_args::<false>(global_this, &module_id, &parent, mode)
do_resolve_with_args::<false>(global_this, &module_id, &parent, mode, None)
}

pub(crate) fn resolve(
Expand Down Expand Up @@ -919,6 +922,7 @@ pub(crate) fn bun_resolve_sync(
&specifier_str,
&source_str,
ResolveMode::from_ffi_bools(is_esm, is_user_require_resolve),
None,
)?
.into_js(global)
})
Expand Down Expand Up @@ -966,23 +970,13 @@ pub(crate) fn bun_resolve_sync_with_paths(
return JSValue::ZERO;
};

// SAFETY: bun_vm() returns the live thread-local VM for a Bun-owned global.
let bun_vm = global.bun_vm().as_mut();
debug_assert!(bun_vm.transpiler.resolver.custom_dir_paths.is_none());
// SAFETY: `paths` borrows C++-owned BunStrings valid for the duration of
// this synchronous resolve call; lifetime is erased for the resolver slot.
bun_vm.transpiler.resolver.custom_dir_paths = Some(unsafe { bun_ptr::detach_lifetime(paths) });
scopeguard::defer! {
// SAFETY: same VM pointer; called before returning to C++.
global.bun_vm().as_mut().transpiler.resolver.custom_dir_paths = None;
}

jsc::to_js_host_call(global, || {
do_resolve_with_args::<true>(
global,
&specifier_str,
&source_str,
ResolveMode::from_ffi_bools(is_esm, is_user_require_resolve),
Some(paths),
)?
.into_js(global)
})
Expand All @@ -1008,6 +1002,7 @@ pub(crate) fn bun_resolve_sync_with_source_if_exists(
&specifier_str,
source,
ResolveMode::from_ffi_bools(is_esm, false),
None,
)? {
Resolved::Found(value) => value.into_js(global),
Resolved::NotFound(_) => Ok(JSValue::UNDEFINED),
Expand Down
1 change: 1 addition & 0 deletions src/runtime/jsc_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,7 @@ unsafe fn load_preloads(vm: *mut VirtualMachine) -> bun_jsc::CrateResult<*mut JS
normalized,
ImportKind::Stmt,
global_cache,
None,
)
} {
ResolveResultUnion::Success(r) => r,
Expand Down
Loading
Loading