Skip to content
Open
46 changes: 26 additions & 20 deletions src/ast/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,11 @@ pub struct Imports {
pub __promiseAll: Ref,
pub __MEMO_CACHE_SENTINEL: Ref,
pub __EARLY_RETURN_SENTINEL: Ref,
pub __glob: Ref,
}

impl Imports {
pub const ALL: [&'static [u8]; 27] = [
pub const ALL: [&'static [u8]; 28] = [
b"__name",
b"__require",
b"__export",
Expand Down Expand Up @@ -206,12 +207,13 @@ impl Imports {
b"__promiseAll",
b"__MEMO_CACHE_SENTINEL",
b"__EARLY_RETURN_SENTINEL",
b"__glob",
];

/// Rust stable cannot sort in `const`; precomputed here and verified by
/// the test in `tests` below.
#[cfg_attr(not(test), allow(dead_code))]
const ALL_SORTED: [&'static [u8]; 27] = [
const ALL_SORTED: [&'static [u8]; 28] = [
b"$$typeof",
b"__EARLY_RETURN_SENTINEL",
b"__MEMO_CACHE_SENTINEL",
Expand All @@ -222,6 +224,7 @@ impl Imports {
b"__export",
b"__exportDefault",
b"__exportValue",
b"__glob",
b"__jsonParse",
b"__legacyDecorateClassTS",
b"__legacyDecorateParamTS",
Expand All @@ -243,34 +246,35 @@ impl Imports {

/// When generating the list of runtime imports, we sort it for determinism.
/// This is a lookup table so we don't need to resort the strings each time
pub const ALL_SORTED_INDEX: [usize; 27] = [
15, // __name
24, // __require
pub const ALL_SORTED_INDEX: [usize; 28] = [
16, // __name
25, // __require
7, // __export
23, // __reExport
24, // __reExport
9, // __exportValue
8, // __exportDefault
14, // __merge
11, // __legacyDecorateClassTS
12, // __legacyDecorateParamTS
13, // __legacyMetadataTS
22, // __publicField
18, // __privateIn
17, // __privateGet
16, // __privateAdd
20, // __privateSet
19, // __privateMethod
15, // __merge
12, // __legacyDecorateClassTS
13, // __legacyDecorateParamTS
14, // __legacyMetadataTS
23, // __publicField
19, // __privateIn
18, // __privateGet
17, // __privateAdd
21, // __privateSet
20, // __privateMethod
6, // __decoratorStart
5, // __decoratorMetadata
25, // __runInitializers
26, // __runInitializers
4, // __decorateElement
0, // $$typeof
26, // __using
27, // __using
3, // __callDispose
10, // __jsonParse
21, // __promiseAll
11, // __jsonParse
22, // __promiseAll
2, // __MEMO_CACHE_SENTINEL
1, // __EARLY_RETURN_SENTINEL
10, // __glob
];

pub const NAME: &'static [u8] = b"bun:wrap";
Expand Down Expand Up @@ -306,6 +310,7 @@ impl Imports {
24 => self.__promiseAll,
25 => self.__MEMO_CACHE_SENTINEL,
26 => self.__EARLY_RETURN_SENTINEL,
27 => self.__glob,
_ => return None,
};
r.to_nullable()
Expand Down Expand Up @@ -341,6 +346,7 @@ impl Imports {
24 => Some(&mut self.__promiseAll),
25 => Some(&mut self.__MEMO_CACHE_SENTINEL),
26 => Some(&mut self.__EARLY_RETURN_SENTINEL),
27 => Some(&mut self.__glob),
_ => None,
}
}
Expand Down
1 change: 1 addition & 0 deletions src/bundler/ParseTask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2412,6 +2412,7 @@ pub mod parse_worker {
// a worker-owned `Transpiler` that outlives the parse.
// SAFETY: ARENA — `topts` outlives `opts` (worker-owned for the bundle pass).
opts.allow_unresolved = unsafe { bun_collections::detach_ref(&topts.allow_unresolved) };
opts.glob_resolver = Some(crate::options::parser_glob_resolver);
// `Transpiler.macro_context` is `Option<bun_ast::Macro::MacroContext>`
// (same nominal type as `ParserOptions.macro_context`'s pointee). Reborrow
// through the raw `*mut Transpiler` so the `&mut MacroContext` is disjoint
Expand Down
42 changes: 42 additions & 0 deletions src/bundler/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,48 @@
// `&transpiler.options.allow_unresolved` straight through.
pub use bun_js_parser::options::AllowUnresolved;

/// `glob_resolver` for the parser's esbuild-style template-literal
/// `require()` / `import()` support. Walks `source_dir` for files matching
/// `pattern` and returns each match as a relative specifier with the same
/// `./` / `../` prefix as `pattern`, so the string the bundler records as
/// an import path is also the string the emitted `__glob({...})` map is
/// keyed on.
Comment thread
robobun marked this conversation as resolved.
Outdated
pub fn parser_glob_resolver(source_dir: &[u8], pattern: &[u8]) -> Vec<Box<[u8]>> {
let mut out: Vec<Box<[u8]>> = Vec::new();
if !(pattern.starts_with(b"./") || pattern.starts_with(b"../")) {
return out;
}

let walker = match bun_glob::BunGlobWalker::init_with_cwd(
pattern, source_dir, /* dot */ true, /* absolute */ false,
/* follow_symlinks */ true, /* error_on_broken_symlinks */ false,
/* only_files */ true, None,
Comment thread
robobun marked this conversation as resolved.
Outdated
) {
Ok(Ok(w)) => w,
_ => return out,
};
let mut walker = Box::new(walker);
let mut iter = bun_glob::walk::Iterator::new(&mut *walker);
if iter.init().map(|m| m.is_err()).unwrap_or(true) {
return out;
}
while let Ok(Ok(Some(m))) = iter.next() {
let p: &[u8] = &m;
// The walker is free to return either separator; normalise to `/` so
// map keys match the template literal's runtime value on every
// platform.
Comment thread
robobun marked this conversation as resolved.
Outdated
let mut s: Vec<u8> = p
.iter()
.map(|&b| if b == b'\\' { b'/' } else { b })
.collect();

Check warning on line 113 in src/bundler/options.rs

View check run for this annotation

Claude / Claude Code Review

Unconditional backslash→slash normalization corrupts POSIX filenames

The `\\` → `/` rewrite here is unconditional, but on POSIX `\\` is a legal filename byte, not a separator — a matched file literally named e.g. `weird\\name.js` gets its map key and import-record path corrupted to `./mods/weird/name.js`, so the runtime template value `./mods/weird\\name.js` misses the map. `write_sanitized_parent_dirs` in this same file already encodes the correct pattern (`b == b'/' || (cfg!(windows) && b == b'\\\\')`) with a comment explaining exactly this; gate the rewrite th
Comment thread
claude[bot] marked this conversation as resolved.
Outdated
if !(s.starts_with(b"./") || s.starts_with(b"../")) {
s.splice(0..0, b"./".iter().copied());
}
out.push(s.into_boxed_slice());
}
out
}

// Canonical defs live in `bun_resolver::options` (lower tier; resolver is the
// runtime consumer of `.patterns`/`.abs_paths`/`.node_modules`). Re-export so
// `BundleOptions.external` and `Resolver.opts.external` are the SAME nominal
Expand Down
1 change: 1 addition & 0 deletions src/bundler/transpiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1550,6 +1550,7 @@ impl<'a> Transpiler<'a> {
macro_context: None,
warn_about_unbundled_modules: !target.is_bun(),
allow_unresolved: &p_opts::AllowUnresolved::DEFAULT,
glob_resolver: None,
module_type: to_parser_module_type(this_parse.module_type),
output_format: p_opts::Format::Esm,
transform_only: self.options.transform_only,
Expand Down
Loading
Loading