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
2 changes: 0 additions & 2 deletions src/bundler/bundle_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1980,7 +1980,6 @@ pub mod bv2_impl {

let this_transpiler: &Transpiler<'a> = &*self.transpiler;
let this_compile = this_transpiler.options.compile_mode.is_executable();
let this_env = this_transpiler.env;

// SAFETY: `self.transpiler` (and the data its `&'a` fields borrow)
// outlives this `BundleV2<'a>`; `for_worker` widens those borrows to
Expand Down Expand Up @@ -2035,7 +2034,6 @@ pub mod bv2_impl {
// Re-project the resolver subset now that `target`/`conditions` etc.
// have been overwritten for the browser.
boxed.sync_resolver_opts();
boxed.resolver.env_loader = NonNull::new(this_env.cast());

// Park the owning Box first, then derive both the published `NonNull`
// and the returned `&mut` from its final resting place. Taking the
Expand Down
14 changes: 12 additions & 2 deletions src/bundler/transpiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ impl<'a> Transpiler<'a> {
}

/// Reborrow the `DotEnv::Loader`. Returned lifetime is decoupled from
/// `&self` so call sites in `configure_defines` / `run_env_loader` can
/// `&self` so call sites in `configure_defines` / `load_env` can
/// hold it across disjoint `&mut self.options` / `&mut self.resolver`
/// borrows.
#[inline]
Expand Down Expand Up @@ -605,6 +605,14 @@ impl<'a> Transpiler<'a> {
/// Load `.env` files into the env loader according to
/// `options.env.behavior`.
pub fn run_env_loader(&mut self, skip_default_env: bool) -> crate::Result<()> {
let loaded = self.load_env(skip_default_env);
// `load_env` also returns early with the process env loaded: an error, or a cwd that it cannot list.
let node_path = self.env().get(b"NODE_PATH").unwrap_or(b"");
self.resolver.set_node_path(node_path);
loaded
}

fn load_env(&mut self, skip_default_env: bool) -> crate::Result<()> {
use bun_options_types::schema::api::DotEnvBehavior;
// Derived once up front; no other live `&mut` to this `Loader` exists
// for the duration of this call.
Expand Down Expand Up @@ -1118,6 +1126,8 @@ impl<'a> Transpiler<'a> {
}
},
};
let env_loader_nn =
core::ptr::NonNull::new(env_loader).expect("Transpiler::init: env loader is non-null");

if dot_env::instance().is_none() {
dot_env::set_instance(env_loader);
Expand Down Expand Up @@ -1161,7 +1171,7 @@ impl<'a> Transpiler<'a> {
outbase,
..Default::default()
},
resolver: Resolver::init1(log_nn, fs, resolver_opts),
resolver: Resolver::init1(log_nn, fs, env_loader_nn, resolver_opts),
output_files: Vec::new(),
resolve_results,
resolve_queue: ResolveQueue::default(),
Expand Down
2 changes: 1 addition & 1 deletion src/install/auto_installer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ fn __bun_resolver_parse_dependency(
// process-lifetime; `init_with_runtime` stores it raw).
// • `install` is `BundleOptions.install` (`?*Api.BunInstall`). The pointee is
// the CLI-owned `Box<BunInstall>` (process-lifetime), read-only.
// • `env` is the resolver's unwrapped `env_loader` (Transpiler-owned,
// • `env` is the resolver's `env_loader` (Transpiler-owned,
// process-lifetime). `init_with_runtime` stores it as `NonNull<Loader>`.
#[unsafe(no_mangle)]
#[inline(never)]
Expand Down
8 changes: 0 additions & 8 deletions src/install_jsc/install_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ use bun_jsc::bindgen::JsScope;
use bun_jsc::{JSValue, StringJsc as _};

pub(crate) fn parse_lockfile(cx: &mut JsScope<'_>, cwd: &[u8]) -> bun_jsc::JsResult<JSValue> {
use core::ptr::NonNull;

use bstr::BStr;
use bun_install::lockfile::lockfile_json_stringify_for_debugging::{
WriteStream, WriteStreamOptions, json_stringify,
Expand Down Expand Up @@ -35,15 +33,9 @@ pub(crate) fn parse_lockfile(cx: &mut JsScope<'_>, cwd: &[u8]) -> bun_jsc::JsRes

let mut lockfile_ = Lockfile::default();

// `bun_vm()` returns a raw `*mut VirtualMachine`; deref locally so the
// env-loader fixup and the package-manager borrow are scoped
// independently.
// SAFETY: `bun_vm()` returns the live VM that owns `global`; this host fn
// runs on the JS thread so no concurrent `&mut VirtualMachine` exists.
let vm = global.bun_vm().as_mut();
if vm.transpiler.resolver.env_loader.is_none() {
vm.transpiler.resolver.env_loader = NonNull::new(vm.transpiler.env);
}

// as long as we aren't migration from `package-lock.json`, leaving this undefined is okay
let manager = vm.package_manager();
Expand Down
33 changes: 27 additions & 6 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4271,18 +4271,20 @@ pub enum ResolveMode {
RequireResolve,
/// `import.meta.resolve()`, `Bun.resolveSync()`: ESM resolution that loads nothing.
EsmResolve,
/// The key of a module mock: as `Esm`, but under `bun test` a package that only a hoist folder of `NODE_PATH` has does not resolve.
ModuleMock,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Comment thread
robobun marked this conversation as resolved.
Resolved
}

impl ResolveMode {
#[inline]
pub fn is_esm(self) -> bool {
matches!(self, Self::Esm | Self::EsmResolve)
matches!(self, Self::Esm | Self::EsmResolve | Self::ModuleMock)
}

#[inline]
pub fn import_kind(self) -> bun_ast::ImportKind {
match self {
Self::Esm | Self::EsmResolve => bun_ast::ImportKind::Stmt,
Self::Esm | Self::EsmResolve | Self::ModuleMock => bun_ast::ImportKind::Stmt,
Self::Require => bun_ast::ImportKind::Require,
Self::RequireResolve => bun_ast::ImportKind::RequireResolve,
}
Expand Down Expand Up @@ -5501,7 +5503,7 @@ impl VirtualMachine {
ret: &mut ResolveFunctionResult<'r>,
specifier: &'r [u8],
source: &[u8],
is_esm: bool,
mode: ResolveMode,
is_a_file_path: bool,
global_cache: bun_resolver::GlobalCache,
) -> crate::CrateResult<()> {
Expand Down Expand Up @@ -5573,7 +5575,7 @@ impl VirtualMachine {
// the first miss.
let mut retry_on_not_found = bun_paths::is_absolute(source_to_use);
let result: bun_resolver::Result = loop {
let import_kind = if is_esm {
let import_kind = if mode.is_esm() {
bun_ast::ImportKind::Stmt
} else {
bun_ast::ImportKind::Require
Expand All @@ -5584,7 +5586,26 @@ impl VirtualMachine {
import_kind,
global_cache,
) {
ResultUnion::Success(r) => break r,
ResultUnion::Success(r) => {
if r.flags.is_from_node_path() {
// The chai that vitest depends on comes before one that only NODE_PATH has.
if let Some(chai) = self.resolve_chai_next_to_vitest(
normalized_specifier,
source_to_use,
import_kind,
) {
break chai;
}
// Reported as not found: the mock then keeps its name as the key, and so it replaces every copy of the package.
if mode == ResolveMode::ModuleMock
&& r.flags.is_from_hoist_folder()
&& self.transpiler.options.rewrite_jest_for_tests
{
return Err(crate::CrateError::ModuleNotFound);
Comment thread
robobun marked this conversation as resolved.
Resolved
}
}
break r;
}
Comment thread
robobun marked this conversation as resolved.
Resolved
ResultUnion::Failure(e) => return Err(e.into()),
ResultUnion::Pending(_) | ResultUnion::NotFound => {
if !retry_on_not_found {
Expand Down Expand Up @@ -5974,7 +5995,7 @@ impl VirtualMachine {
&mut result,
specifier_utf8.slice(),
normalize_source(source_utf8.slice()),
mode.is_esm(),
mode,
IS_A_FILE_PATH,
global_cache,
);
Expand Down
1 change: 1 addition & 0 deletions src/jsc/bindings/BunPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3938,6 +3938,7 @@ __attribute__((minsize)) static bool resolveModuleMockSpecifier(Zig::GlobalObjec
BunString from = Bun::toString(fromString);
// Not resolving is fine (mocking a module that does not exist yet); anything else thrown
// while resolving (e.g. by an onResolve plugin) propagates.
// Under `bun test` a package that only a hoist folder of NODE_PATH has does not resolve here either: see ResolveMode::ModuleMock.
auto result = JSValue::decode(Bun__resolveSyncWithSourceIfExists(globalObject, JSValue::encode(specifierString), &from, true));
RETURN_IF_EXCEPTION(scope, false);

Expand Down
3 changes: 2 additions & 1 deletion src/jsc/bindings/BunPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,8 @@ class BunPlugin {
enum class Asker : uint8_t { Import,
Require,
RequireResolve,
ImportResolve };
ImportResolve,
ModuleMock };
JSC::EncodedJSValue run(JSC::JSGlobalObject* globalObject, const BunString* namespaceString, const BunString* path, const BunString* importer, const BunString* importerNamespace, const BunString* resolveDir, Asker);

// Set by import() and consumed by the loader's resolve hook.
Expand Down
1 change: 0 additions & 1 deletion src/jsc/web_worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1062,7 +1062,6 @@ impl WebWorker {
// SAFETY: `vm` is a valid heap-allocated VM ptr (checked above).
unsafe {
let b = &mut (*vm).transpiler;
b.resolver.env_loader = NonNull::new(b.env);
b.options.env.behavior =
bun_options_types::schema::api::DotEnvBehavior::LoadAllWithoutInlining;

Expand Down
Loading
Loading