Reproduction
// node_modules/cond-pkg/package.json
{ "name": "cond-pkg", "exports": { "import": "./esm.mjs", "default": "./default.cjs" } }
// node_modules/cond-pkg/esm.mjs
export const flavor = 'import';
// node_modules/cond-pkg/default.cjs
exports.flavor = 'default';
// first.ts
export const first: string = 'first.ts';
$ node --import @oxc-node/core/register -e "require('./first.ts'); import('cond-pkg').then((m) => console.log(m.flavor))"
default
$ node --import @oxc-node/core/register -e "import('cond-pkg').then((m) => console.log(m.flavor))"
import
import('cond-pkg') should pick the import condition both times. The same on Node.js 22.18, 24.21, and 26.5 with @oxc-node/core 0.1.5.
Cause
The resolver lives in a process-wide OnceLock, RESOLVER_AND_TSCONFIG, and keeps the condition_names of whichever caller initializes it:
OxcTransformer::transform (the pirates path) and the transform task initialize it with no conditions (src/lib.rs#L437, #L492);
create_resolve initializes it with the request's context.conditions (#L937) and reuses it for every later request, whatever that request's conditions.
When a require() of a TypeScript file runs first (-e, the REPL, or a CommonJS preload that oxc-node transforms), later imports resolve with no conditions. A package with a default export then resolves to it, as above. Without default, oxc's resolution fails and the hook falls back to Node.js' resolver, which hides the bug but skips tsconfig paths and the format detection.
Suggested fix
Resolve each request with its own conditions, for example by keeping one resolver per condition set, cloned from the base so they share the caches:
static CONDITION_RESOLVERS: Mutex<Vec<(Vec<String>, Arc<Resolver>)>> = Mutex::new(Vec::new());
fn resolver_for_conditions(base: &Resolver, conditions: &[String]) -> Arc<Resolver> {
let mut resolvers = CONDITION_RESOLVERS.lock().unwrap_or_else(PoisonError::into_inner);
if let Some((_, resolver)) = resolvers.iter().find(|(names, _)| names == conditions) {
return Arc::clone(resolver);
}
let resolver = Arc::new(base.clone_with_options(ResolveOptions {
condition_names: conditions.to_vec(),
..base.options().clone()
}));
resolvers.push((conditions.to_vec(), Arc::clone(&resolver)));
resolver
}
Vite+ carries this change in its vendored copy.
Reproduction
import('cond-pkg')should pick theimportcondition both times. The same on Node.js 22.18, 24.21, and 26.5 with@oxc-node/core0.1.5.Cause
The resolver lives in a process-wide
OnceLock,RESOLVER_AND_TSCONFIG, and keeps thecondition_namesof whichever caller initializes it:OxcTransformer::transform(thepiratespath) and the transform task initialize it with no conditions (src/lib.rs#L437, #L492);create_resolveinitializes it with the request'scontext.conditions(#L937) and reuses it for every later request, whatever that request's conditions.When a
require()of a TypeScript file runs first (-e, the REPL, or a CommonJS preload that oxc-node transforms), later imports resolve with no conditions. A package with adefaultexport then resolves to it, as above. Withoutdefault, oxc's resolution fails and the hook falls back to Node.js' resolver, which hides the bug but skips tsconfigpathsand the format detection.Suggested fix
Resolve each request with its own conditions, for example by keeping one resolver per condition set, cloned from the base so they share the caches:
Vite+ carries this change in its vendored copy.