Skip to content

implement import.meta.glob - #21459

Closed
RiskyMH wants to merge 39 commits into
mainfrom
riskymh/import.meta.glob
Closed

RiskyMH wants to merge 39 commits into
mainfrom
riskymh/import.meta.glob

Conversation

@RiskyMH

@RiskyMH RiskyMH commented Jul 29, 2025 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #6060 by implementing most of vite's import.meta.glob api. This allows for easy bundle time file including. It is missing the eager option because that's quite a bit extra of a change, but can work it out if needed. Also in future a direct import glob would be cool too.

const modules = import.meta.glob('./modules/*.js')

// turns into:
const modules = {
  './modules/foo.js': () => import('./modules/foo.js'),
  './modules/bar.js': () => import('./modules/bar.js'),
}

also while trying out edgecases, I made the bundler actually support await import('./script.js', { with: { type: 'text' } }); (right now it just does based on extension instead of specified one like runtime)

How did you verify your code works?

made tests + manual

@robobun

robobun commented Jul 29, 2025 •

Copy link
Copy Markdown
Collaborator
Updated 3:05 AM PT - Dec 1st, 2025

❌ @RiskyMH, your commit a6ecf14 has 1 failures in Build #32747 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 21459

That installs a local version of the PR into your bun-21459 executable, so you can run:

bun-21459 --bun

@RiskyMH
RiskyMH marked this pull request as draft July 29, 2025 22:31
@RiskyMH
RiskyMH marked this pull request as ready for review July 30, 2025 04:07
@RiskyMH
RiskyMH requested review from a team and pfgithub and removed request for a team July 30, 2025 05:00
@RiskyMH

RiskyMH commented Aug 5, 2025

Copy link
Copy Markdown
Contributor Author

Vite has eager mode which converts to proper imports which is nice, but with current implementation it doesn't appear as simple of a task to do with ast. However rewriting it to an object of dynamic imports is pretty simple.

Also import files from "./abc/*.ts" would also be cool, but not sure how well TS would like it and if good idea.

RiskyMH added 3 commits August 7, 2025 00:23
Properly integrated import.meta.glob support into new parser structure:
- Added handleImportMetaGlobCall to P.zig
- Added import_meta_glob case handling in visitExpr.zig
- Added glob detection for import.meta.glob in maybe.zig
- Updated SideEffects.zig to handle import_meta_glob
@RiskyMH
RiskyMH requested a review from Jarred-Sumner August 8, 2025 02:06
Comment thread src/bun.js/RuntimeTranspilerCache.zig Outdated
Comment thread packages/bun-types/globals.d.ts Outdated
Comment thread packages/bun-types/globals.d.ts Outdated
Comment on lines +1343 to +1355
/**
* Import attributes to pass to the import statement.
* This is the standard way to specify import options.
*
* @example
* const modules = import.meta.glob('./assets/*.txt', { with: { type: 'text' } })
*
* // code produced by bun
* const modules = {
* './assets/file.txt': () => import('./assets/file.txt', { with: { type: 'text' } }),
* }
*/
with?: ImportAttributes;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vite doesn't have this one, i just thought it was nice because often you want the text content or path (like when using the dynamic import normally)

Comment thread packages/bun-types/globals.d.ts Outdated
Comment on lines +1310 to +1316
// todo:
// /**
// * If true, imports all modules eagerly (synchronously).
// * If false (default), returns functions that import modules lazily.
// */
// eager?: Eager;
eager?: false;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do want to do this, but its way more change so for a different pr. Personally it makes more sense to use a different way for eger instead like in the top level import itself:

import files from "./src/*.ts";

Comment thread packages/bun-types/globals.d.ts

@alii alii left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small change for the type definitions

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/bun-types/globals.d.ts (1)

1307-1316: Remove generic Eager overload from import.meta.glob (type unsoundness)

The generic Eager lets callers force eager typing (e.g. import.meta.glob<true, T>) while runtime still returns lazy loader functions. Remove the generic overload so eager can only be selected via runtime options.

File: packages/bun-types/globals.d.ts (around lines 1307–1316)

-  glob<Eager extends boolean = false, TModule = unknown>(
-    pattern: string | string[],
-    options?: ImportMetaGlobOptions<Eager>,
-  ): Eager extends true ? Record<string, TModule> : Record<string, () => Promise<TModule>>;
   glob<TModule = unknown>(
     pattern: string | string[],
     options?: ImportMetaGlobOptions<false>,
   ): Record<string, () => Promise<TModule>>;
-  glob<TModule = unknown>(pattern: string | string[], options?: ImportMetaGlobOptions<true>): Record<string, TModule>;
+  // TODO: add an overload for eager: true once implemented

Repo-wide search for import.meta.glob<true returned 0 matches.

🧹 Nitpick comments (1)
packages/bun-types/globals.d.ts (1)

1324-1349: Minor: remove unused generic from ImportMetaGlobOptions

ImportMetaGlobOptions<Eager> no longer needs the Eager parameter if you drop the generic function overload. Simplify:

-interface ImportMetaGlobOptions<Eager extends boolean = false> {
+interface ImportMetaGlobOptions {
   // eager not implemented yet
   eager?: false;
   …
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 72cb84c and c431a72.

📒 Files selected for processing (3)
  • docs/api/import-meta.md (2 hunks)
  • packages/bun-types/globals.d.ts (1 hunks)
  • test/integration/bun-types/fixture/import-meta.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/api/import-meta.md
🧰 Additional context used
📓 Path-based instructions (4)
test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place all tests under the test/ directory

Files:

  • test/integration/bun-types/fixture/import-meta.ts
test/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/**/*.{js,ts}: Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test
Prefer data-driven tests (e.g., test.each) to reduce boilerplate
Use shared utilities from test/harness.ts where applicable

Files:

  • test/integration/bun-types/fixture/import-meta.ts
test/integration/**

📄 CodeRabbit inference engine (test/CLAUDE.md)

Place integration tests under test/integration/

Files:

  • test/integration/bun-types/fixture/import-meta.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format JavaScript/TypeScript files with Prettier (bun run prettier)

Files:

  • test/integration/bun-types/fixture/import-meta.ts
  • packages/bun-types/globals.d.ts
🧠 Learnings (15)
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/**/*.{js,ts} : Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test

Applied to files:

  • test/integration/bun-types/fixture/import-meta.ts
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Name test files `*.test.ts` and use `bun:test`

Applied to files:

  • test/integration/bun-types/fixture/import-meta.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/bun/**/*.{js,ts} : Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Applied to files:

  • test/integration/bun-types/fixture/import-meta.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/{dev/*.test.ts,dev-and-prod.ts} : Import testing utilities (devTest, prodTest, devAndProductionTest, Dev, Client) from test/bake/bake-harness.ts

Applied to files:

  • test/integration/bun-types/fixture/import-meta.ts
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Import common test utilities from `harness` (e.g., `bunExe`, `bunEnv`, `tempDirWithFiles`, `tmpdirSync`, platform checks, GC helpers)

Applied to files:

  • test/integration/bun-types/fixture/import-meta.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/html.test.ts : html.test.ts should contain tests relating to HTML files themselves

Applied to files:

  • test/integration/bun-types/fixture/import-meta.ts
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*-fixture.ts : Name test fixture files that are spawned by tests with the suffix `-fixture.ts`

Applied to files:

  • test/integration/bun-types/fixture/import-meta.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/bundle.test.ts : bundle.test.ts should contain DevServer-specific bundling tests

Applied to files:

  • test/integration/bun-types/fixture/import-meta.ts
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : When spawning Bun in tests, use `bunExe()` and `bunEnv` from `harness`

Applied to files:

  • test/integration/bun-types/fixture/import-meta.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/esm.test.ts : esm.test.ts should cover ESM feature behavior in development mode

Applied to files:

  • test/integration/bun-types/fixture/import-meta.ts
  • packages/bun-types/globals.d.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/bake-harness.ts : Provide and maintain shared test utilities: devTest, prodTest, devAndProductionTest, Dev, Client, and helpers in the harness

Applied to files:

  • test/integration/bun-types/fixture/import-meta.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/**/*.{js,ts} : Use shared utilities from test/harness.ts where applicable

Applied to files:

  • test/integration/bun-types/fixture/import-meta.ts
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to test/**/*.test.{ts,tsx} : Avoid shell commands (e.g., find, grep) in tests; use Bun.Glob and built-ins instead

Applied to files:

  • test/integration/bun-types/fixture/import-meta.ts
  • packages/bun-types/globals.d.ts
📚 Learning: 2025-09-07T05:41:52.563Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-09-07T05:41:52.563Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{js,ts} : Author modules as CommonJS-style with `require(...)` and export via `export default {}` (no ESM `import`/named exports)

Applied to files:

  • packages/bun-types/globals.d.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/ecosystem.test.ts : ecosystem.test.ts should focus on concrete library integration bugs rather than whole-package coverage

Applied to files:

  • packages/bun-types/globals.d.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (3)
test/integration/bun-types/fixture/import-meta.ts (2)

1-35: LGTM overall: good coverage of patterns and typing ergonomics

Covers arrays of patterns, with.type, base, explicit TModule, and negative tests for eager. Nice.


3-7: Make runtime loop conditional to keep this file a pure typing fixture

If this file is only for type-checking, avoid top-level await that performs real imports; wrap the loop in a never-executed guard:

-const fixtures = import.meta.glob("./**/*.ts");
-for (const [file, importFn] of Object.entries(fixtures)) {
-  console.log(file, await importFn());
-}
+const fixtures = import.meta.glob("./**/*.ts");
+if (false) {
+  for (const [file, importFn] of Object.entries(fixtures)) {
+    console.log(file, await importFn());
+  }
+}

Confirm whether this fixture is executed by any test runner.

packages/bun-types/globals.d.ts (1)

1387-1387: Avoid shadowing TS lib's ImportAttributes — prefer TS lib type or a Bun‑scoped alias

Global interface ImportAttributes can merge with TypeScript's lib and cause conflicts. I couldn't verify ImportCallOptions in the environment — confirm whether the TS lib provides it and, if so, prefer ImportCallOptions["with"]; otherwise apply the permissive Bun‑scoped variant below.

-  with?: ImportAttributes;
+  // Use TS lib type if available; otherwise a permissive record with a typed `type` field.
+  with?: ({ type?: "css" | "file" | "json" | "jsonc" | "toml" | "yaml" | "txt" | "text" | "html" | (string & {}) } & Record<string, string>);

And delete the global interface:

-interface ImportAttributes {
-  type: "css" | "file" | "json" | "jsonc" | "toml" | "yaml" | "txt" | "text" | "html" | (string & {});
-}

Also applies to: 1403-1405.

Comment thread test/integration/bun-types/fixture/import-meta.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c431a72 and 490c551.

📒 Files selected for processing (6)
  • packages/bun-types/globals.d.ts (1 hunks)
  • src/ast/E.zig (1 hunks)
  • src/ast/P.zig (2 hunks)
  • src/ast/SideEffects.zig (1 hunks)
  • src/ast/visitExpr.zig (1 hunks)
  • src/bun.js/RuntimeTranspilerCache.zig (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/RuntimeTranspilerCache.zig
  • src/ast/SideEffects.zig
  • src/ast/visitExpr.zig
  • src/ast/E.zig
  • src/ast/P.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/RuntimeTranspilerCache.zig
src/**/*.zig

📄 CodeRabbit inference engine (CLAUDE.md)

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: Use the # prefix to declare private fields in Zig structs (e.g., struct { #foo: u32 })
Prefer decl literals when initializing values in Zig (e.g., const decl: Decl = .{ .binding = 0, .value = 0 })
Place @import directives at the bottom of Zig files
Use @import("bun") instead of @import("root").bun

When adding debug logs in Zig, create a scoped logger and log via Bun APIs: const log = bun.Output.scoped(.${SCOPE}, .hidden); then log("...", .{})

Files:

  • src/bun.js/RuntimeTranspilerCache.zig
  • src/ast/SideEffects.zig
  • src/ast/visitExpr.zig
  • src/ast/E.zig
  • src/ast/P.zig
🧠 Learnings (1)
📚 Learning: 2025-09-06T03:37:41.154Z
Learnt from: taylordotfish
PR: oven-sh/bun#22229
File: src/bundler/LinkerGraph.zig:0-0
Timestamp: 2025-09-06T03:37:41.154Z
Learning: In Bun's codebase, when checking import record source indices in src/bundler/LinkerGraph.zig, prefer using `if (import_index >= self.import_records.len)` bounds checking over `isValid()` checks, as the bounds check is more robust and `isValid()` is a strict subset of this condition.

Applied to files:

  • src/ast/P.zig

Comment thread packages/bun-types/globals.d.ts
Comment thread src/ast/P.zig
Comment on lines +6129 to +6159
var path_buf: bun.PathBuffer = undefined;
const slash_normalized = if (comptime bun.Environment.isWindows)
strings.normalizeSlashesOnly(&path_buf, path, '/')
else
path;

const duped = bun.handleOom(p.allocator.dupe(u8, slash_normalized));
bun.handleOom(matched_files.append(duped));
}
}

std.sort.block([]const u8, matched_files.items, {}, struct {
fn lessThan(_: void, a: []const u8, b_path: []const u8) bool {
return strings.order(a, b_path) == .lt;
}
}.lessThan);

// Create dynamic imports
var properties: []G.Property = bun.handleOom(p.allocator.alloc(G.Property, matched_files.items.len));

for (matched_files.items, 0..) |file_path, i| {
// add the base path and/or query string to the import path
const import_path: []const u8 = if (base_path) |base|
if (query) |q|
bun.handleOom(std.fmt.allocPrint(p.allocator, "{s}/{s}{s}", .{ base, file_path, q }))
else
bun.handleOom(std.fmt.allocPrint(p.allocator, "{s}/{s}", .{ base, file_path }))
else if (query) |q|
bun.handleOom(std.fmt.allocPrint(p.allocator, "{s}{s}", .{ file_path, q }))
else
file_path;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Return relative module specifiers, not absolute paths

matched_files.append(duped) stores the walker’s absolute path. The keys we emit (and the string we pass to addImportRecord) therefore become absolute filesystem paths, while Vite’s import.meta.glob semantics require module specifiers relative to the importer (e.g. ./modules/foo.js). The same regression means base/query are concatenated with a full absolute path, producing nonsensical specifiers and broken bundler resolution.

Please compute the path relative to search_dir (trim the shared prefix with the proper separator guard) before appending it, then use that relative slice both for the object key and as the import string (prefix .//../ as needed). A safe pattern is:

-                    const duped = bun.handleOom(p.allocator.dupe(u8, slash_normalized));
-                    bun.handleOom(matched_files.append(duped));
+                    const rel_path = blk: {
+                        if (strings.hasPrefix(slash_normalized, search_dir) and slash_normalized.len >= search_dir.len) {
+                            var start = search_dir.len;
+                            if (start < slash_normalized.len and (slash_normalized[start] == '/' or slash_normalized[start] == '\\')) {
+                                start += 1;
+                            }
+                            break :blk slash_normalized[start..];
+                        }
+                        break :blk slash_normalized;
+                    };
+                    const specifier = if (rel_path.len == 0 or rel_path[0] == '.' or rel_path[0] == '/')
+                        rel_path
+                    else
+                        bun.handleOom(std.fmt.allocPrint(p.allocator, "./{s}", .{rel_path}));
+                    const duped = bun.handleOom(p.allocator.dupe(u8, specifier));
+                    bun.handleOom(matched_files.append(duped));

Then use that specifier when building import_path (prepending base_path/query afterwards). This restores the expected module specifiers and keeps us aligned with Vite.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var path_buf: bun.PathBuffer = undefined;
const slash_normalized = if (comptime bun.Environment.isWindows)
strings.normalizeSlashesOnly(&path_buf, path, '/')
else
path;
const duped = bun.handleOom(p.allocator.dupe(u8, slash_normalized));
bun.handleOom(matched_files.append(duped));
}
}
std.sort.block([]const u8, matched_files.items, {}, struct {
fn lessThan(_: void, a: []const u8, b_path: []const u8) bool {
return strings.order(a, b_path) == .lt;
}
}.lessThan);
// Create dynamic imports
var properties: []G.Property = bun.handleOom(p.allocator.alloc(G.Property, matched_files.items.len));
for (matched_files.items, 0..) |file_path, i| {
// add the base path and/or query string to the import path
const import_path: []const u8 = if (base_path) |base|
if (query) |q|
bun.handleOom(std.fmt.allocPrint(p.allocator, "{s}/{s}{s}", .{ base, file_path, q }))
else
bun.handleOom(std.fmt.allocPrint(p.allocator, "{s}/{s}", .{ base, file_path }))
else if (query) |q|
bun.handleOom(std.fmt.allocPrint(p.allocator, "{s}{s}", .{ file_path, q }))
else
file_path;
var path_buf: bun.PathBuffer = undefined;
const slash_normalized = if (comptime bun.Environment.isWindows)
strings.normalizeSlashesOnly(&path_buf, path, '/')
else
path;
// Compute a module‐specifier relative to search_dir
const rel_path = blk: {
if (strings.hasPrefix(slash_normalized, search_dir) and slash_normalized.len >= search_dir.len) {
var start = search_dir.len;
if (start < slash_normalized.len and
(slash_normalized[start] == '/' or slash_normalized[start] == '\\'))
{
start += 1;
}
break :blk slash_normalized[start..];
}
break :blk slash_normalized;
};
const specifier = if (rel_path.len == 0 or rel_path[0] == '.' or rel_path[0] == '/')
rel_path
else
bun.handleOom(std.fmt.allocPrint(p.allocator, "./{s}", .{rel_path}));
const duped = bun.handleOom(p.allocator.dupe(u8, specifier));
bun.handleOom(matched_files.append(duped));
}
}
🤖 Prompt for AI Agents
In src/ast/P.zig around lines 6129 to 6159, the code currently appends the
walker’s absolute path into matched_files causing emitted keys and import
strings to be absolute; compute a path specifier relative to search_dir instead:
trim the common prefix between the absolute file path and search_dir ensuring
you only cut at a path separator boundary (handle Windows vs POSIX separators),
produce a relative slice and if it does not start with "." or ".." prefix it
with "./", then append that relative specifier (not the absolute path) to
matched_files; when building import_path use this specifier and then prepend
base_path and append query as needed so the object keys and import strings are
proper module specifiers for Vite.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 490c551 and 533011d.

📒 Files selected for processing (2)
  • src/bundler/bundle_v2.zig (1 hunks)
  • test/no-validate-exceptions.txt (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bundler/bundle_v2.zig
src/**/*.zig

📄 CodeRabbit inference engine (CLAUDE.md)

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

src/**/*.zig: Use the # prefix to declare private fields in Zig structs (e.g., struct { #foo: u32 })
Prefer decl literals when initializing values in Zig (e.g., const decl: Decl = .{ .binding = 0, .value = 0 })
Place @import directives at the bottom of Zig files
Use @import("bun") instead of @import("root").bun

When adding debug logs in Zig, create a scoped logger and log via Bun APIs: const log = bun.Output.scoped(.${SCOPE}, .hidden); then log("...", .{})

Files:

  • src/bundler/bundle_v2.zig
test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place all tests under the test/ directory

Files:

  • test/no-validate-exceptions.txt
🧠 Learnings (11)
📚 Learning: 2025-09-06T03:37:41.154Z
Learnt from: taylordotfish
PR: oven-sh/bun#22229
File: src/bundler/LinkerGraph.zig:0-0
Timestamp: 2025-09-06T03:37:41.154Z
Learning: In Bun's codebase, when checking import record source indices in src/bundler/LinkerGraph.zig, prefer using `if (import_index >= self.import_records.len)` bounds checking over `isValid()` checks, as the bounds check is more robust and `isValid()` is a strict subset of this condition.

Applied to files:

  • src/bundler/bundle_v2.zig
📚 Learning: 2025-10-12T02:22:34.349Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.349Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` for files ending with `*.test.{ts,js,jsx,tsx,mjs,cjs}`

Applied to files:

  • test/no-validate-exceptions.txt
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/bundle.test.ts : bundle.test.ts should contain DevServer-specific bundling tests

Applied to files:

  • test/no-validate-exceptions.txt
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/bun/**/*.{js,ts} : Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Applied to files:

  • test/no-validate-exceptions.txt
📚 Learning: 2025-10-04T09:51:30.294Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T09:51:30.294Z
Learning: Applies to test/**/*.test.{ts,tsx} : Avoid shell commands like find or grep in tests; use Bun’s Glob and built-in tools instead

Applied to files:

  • test/no-validate-exceptions.txt
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/node/test/** : Vendored Node.js tests under test/js/node/test/ are exceptions and do not follow Bun’s test style

Applied to files:

  • test/no-validate-exceptions.txt
📚 Learning: 2025-10-04T09:51:30.294Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T09:51:30.294Z
Learning: Applies to test/**/*.test.{ts,tsx} : Use Bun’s Jest-compatible runner (import { test, expect } from "bun:test") for tests

Applied to files:

  • test/no-validate-exceptions.txt
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/**/*.{js,ts} : Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test

Applied to files:

  • test/no-validate-exceptions.txt
📚 Learning: 2025-10-12T02:22:34.349Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.349Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : Do not set explicit test timeouts; Bun already has timeouts

Applied to files:

  • test/no-validate-exceptions.txt
📚 Learning: 2025-10-12T02:22:34.349Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.349Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : Import common utilities from `harness` (e.g., `bunExe`, `bunEnv`, `tempDirWithFiles`, platform helpers, GC helpers)

Applied to files:

  • test/no-validate-exceptions.txt
📚 Learning: 2025-10-12T02:22:34.349Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.349Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : When spawning Bun in tests, use `bunExe()` and `bunEnv` from `harness`

Applied to files:

  • test/no-validate-exceptions.txt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format

Comment thread src/bundler/bundle_v2.zig
Comment on lines +2468 to +2474
if (this.graph.ast.len > resolve.import_record.importer_source_index) {
const record: *ImportRecord = &this.graph.ast.items(.import_records)[resolve.import_record.importer_source_index].slice()[resolve.import_record.import_record_index];
if (record.loader) |override_loader| {
break :blk override_loader;
}
}
break :blk path.loader(&this.transpiler.options.loaders) orelse options.Loader.file;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Still missing the import_record_index bounds check

We now guard importer_source_index, but import_record_index can still point past the slice when it comes from plugins or stale resolve data. That will read beyond the list and crash. Please add the per‐slice length check and fall back to the default loader when it’s out of range.

-                        const loader = blk: {
-                            if (this.graph.ast.len > resolve.import_record.importer_source_index) {
-                                const record: *ImportRecord = &this.graph.ast.items(.import_records)[resolve.import_record.importer_source_index].slice()[resolve.import_record.import_record_index];
-                                if (record.loader) |override_loader| {
-                                    break :blk override_loader;
-                                }
-                            }
-                            break :blk path.loader(&this.transpiler.options.loaders) orelse options.Loader.file;
-                        };
+                        const loader = blk: {
+                            const all_import_records = this.graph.ast.items(.import_records);
+                            if (resolve.import_record.importer_source_index >= all_import_records.len)
+                                break :blk path.loader(&this.transpiler.options.loaders) orelse options.Loader.file;
+                            const list = all_import_records[resolve.import_record.importer_source_index].slice();
+                            if (resolve.import_record.import_record_index >= list.len)
+                                break :blk path.loader(&this.transpiler.options.loaders) orelse options.Loader.file;
+                            const record: *ImportRecord = &list[resolve.import_record.import_record_index];
+                            if (record.loader) |override_loader| break :blk override_loader;
+                            break :blk path.loader(&this.transpiler.options.loaders) orelse options.Loader.file;
+                        };
🤖 Prompt for AI Agents
In src/bundler/bundle_v2.zig around lines 2468 to 2474, the code checks
importer_source_index but does not verify import_record_index against the length
of the slice, which can lead to out-of-bounds access; update the block to get
the slice for .import_records at importer_source_index, check that
resolve.import_record.import_record_index < slice.len before indexing, and only
use record.loader when that bounds check passes; if the index is out of range,
fall back to the default loader path.loader(&this.transpiler.options.loaders)
orelse options.Loader.file.

Comment on lines +1383 to +1406
// todo:
// /**
// * If true, imports all modules eagerly (synchronously).
// * If false (default), returns functions that import modules lazily.
// *
// * @example
// * const eager = import.meta.glob('./src/*.ts', { eager: true })
// * const normal = import.meta.glob('./src/*.ts', { eager: false })
// *
// * // code produced by bun
// * import * as __modules_foo from './src/foo.ts'
// * import * as __modules_bar from './src/bar.ts'
// * const eager = {
// * './src/foo.ts': __modules_foo,
// * './src/bar.ts': __modules_bar,
// * }
// *
// * const normal = {
// * './src/foo.ts': () => import('./src/foo.ts'),
// * './src/bar.ts': () => import('./src/bar.ts'),
// * }
// */
// eager?: Eager;
eager?: false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs could use a little bit of love, imo.

Also, why expose this if it is not currently supported?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realized I didn't leave a very helpful message when I said they could use a little bit more love. What I'm specifically referring to is:

  • the docs could offer an example that is a "birds-eye view" of the function, and does not assume that the user has a mental model of "code transformation" if you will
  • perhaps the docs could explain what "eager" loading means in terms of how it impacts the execution of the module
  • perhaps you might want to remove the (synchronously) since I think it just aliases the word eagerly and doesn't necessarily shed much light on the concept (imo)

My opinions -- feel free to disagree

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, the docs are basically written the same way as vite. But yeah dynamic imports may be confusing. I dont really know of a better way to describe the limbo land of not proper import but a function that returns promise of imported file.

Comment thread packages/bun-types/globals.d.ts Outdated
Comment thread test/no-validate-exceptions.txt
Comment thread docs/api/import-meta.md Outdated
Comment on lines +76 to +82
```ts
const modules = import.meta.glob("./modules/*.ts");
// const modules = {
// './modules/a.ts': () => import('./modules/a.ts'),
// './modules/b.ts': () => import('./modules/b.st'),
// }
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const modules = import.meta.glob("./modules/*.ts");

resolves to:

const modules = {
  './modules/a.ts': () => import('./modules/a.ts'),
  './modules/b.ts': () => import('./modules/b.st'),
}

Maybe? Idk, no strong opinions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might also want to talk a little bit about vite compatibility:

https://vite.dev/guide/features

Do we have support for multiple patterns, negative patterns, etc?

@RiskyMH RiskyMH Oct 14, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh oops I forgot about negative patterns, but yeah it supports the array of patterns. The issue is i dont know at what point the docs get bloated (and if nextjs and others would be adding this then could get weird)

Yeah it has those, its just eager that isn't supported here.

Regarding documenting that, i'm not sure how to do it here but still keep things simple to understand and all (+consistent to rest of this page).

Comment thread packages/bun-types/globals.d.ts
* const module = await modules['./src/foo.ts']()
*/
glob<Eager extends boolean = false, TModule = unknown>(
pattern: string | string[],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like we support multiple patterns, but it's not 100% clear how to use that, perhaps some docs would be neat?

@markovejnovic

markovejnovic commented Oct 15, 2025 •

Copy link
Copy Markdown
Contributor

Support list

Supported

Future implementation

  • Eager loading -- There was a lot of back and forth with eager. It's not easy to implement. About 40% of all import.meta.glob users use eager so it has a huge market-share. This should be
  • Tree-shaking through eager + setup, this is pending eager.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/bundler/bundle_v2.zig (1)

661-667: Critical: Add defensive bounds checks before reading ImportRecord

The code at line 662 indexes into import records using import_record.importer_source_index and import_record.import_record_index without any bounds checking. These indices come from resolve input and can be stale or invalid, leading to out-of-bounds access and crashes.

Based on learnings: prefer bounds checking over isValid() checks when accessing import records.

Apply this diff to add defensive bounds checks:

                         const loader: Loader = brk: {
-                            const record: *ImportRecord = &this.graph.ast.items(.import_records)[import_record.importer_source_index].slice()[import_record.import_record_index];
-                            if (record.loader) |out_loader| {
-                                break :brk out_loader;
-                            }
-                            break :brk path.loader(&transpiler.options.loaders) orelse options.Loader.file;
+                            const all_import_records = this.graph.ast.items(.import_records);
+                            if (import_record.importer_source_index >= all_import_records.len)
+                                break :brk path.loader(&transpiler.options.loaders) orelse options.Loader.file;
+                            const list = all_import_records[import_record.importer_source_index].slice();
+                            if (import_record.import_record_index >= list.len)
+                                break :brk path.loader(&transpiler.options.loaders) orelse options.Loader.file;
+                            const record: *ImportRecord = &list[import_record.import_record_index];
+                            if (record.loader) |out_loader| break :brk out_loader;
+                            break :brk path.loader(&transpiler.options.loaders) orelse options.Loader.file;
                         };
♻️ Duplicate comments (4)
src/ast/P.zig (1)

6139-6240: Return relative specifiers instead of absolute paths.

BunGlobWalker yields absolute filesystem paths, but you append them directly into matched_files and then reuse those slices for the object keys and the dynamic import strings. That turns every import.meta.glob() entry into /abs/path/to/file.js, which breaks Vite semantics (keys must be module specifiers relative to the importer such as ./modules/foo.js) and produces nonsensical strings once base/query are concatenated. This regresses the exact bug called out in the earlier review at Lines 6139‑6169. Fix by trimming the search_dir prefix (guard the separator) to obtain a relative specifier, prefix with ./ when needed, and use that normalized slice both for the object key and addImportRecord input (apply base/query afterwards).

Apply:

-                    const duped = bun.handleOom(p.allocator.dupe(u8, slash_normalized));
-                    bun.handleOom(matched_files.append(duped));
+                    const rel_path = blk: {
+                        if (strings.hasPrefix(slash_normalized, search_dir) and slash_normalized.len >= search_dir.len) {
+                            var start = search_dir.len;
+                            if (start < slash_normalized.len and (slash_normalized[start] == '/' or slash_normalized[start] == '\\')) {
+                                start += 1;
+                            }
+                            break :blk slash_normalized[start..];
+                        }
+                        break :blk slash_normalized;
+                    };
+                    const specifier = if (rel_path.len == 0 or rel_path[0] == '.' or rel_path[0] == '/')
+                        bun.handleOom(p.allocator.dupe(u8, rel_path))
+                    else
+                        bun.handleOom(std.fmt.allocPrint(p.allocator, "./{s}", .{rel_path}));
+                    bun.handleOom(matched_files.append(specifier));
src/bundler/bundle_v2.zig (1)

2467-2473: Still missing the import_record_index bounds check

The code checks importer_source_index at line 2467 but does not verify that import_record_index is within the slice bounds before indexing at line 2468. Plugin-provided indices can be stale or invalid, leading to out-of-bounds access and crashes.

Based on learnings: prefer bounds checking over isValid() checks when accessing import records.

Apply this diff to add the missing per-slice bounds check:

                         const loader = blk: {
-                            if (this.graph.ast.len > resolve.import_record.importer_source_index) {
-                                const record: *ImportRecord = &this.graph.ast.items(.import_records)[resolve.import_record.importer_source_index].slice()[resolve.import_record.import_record_index];
-                                if (record.loader) |override_loader| {
-                                    break :blk override_loader;
-                                }
-                            }
-                            break :blk path.loader(&this.transpiler.options.loaders) orelse options.Loader.file;
+                            const all_import_records = this.graph.ast.items(.import_records);
+                            if (resolve.import_record.importer_source_index >= all_import_records.len)
+                                break :blk path.loader(&this.transpiler.options.loaders) orelse options.Loader.file;
+                            const list = all_import_records[resolve.import_record.importer_source_index].slice();
+                            if (resolve.import_record.import_record_index >= list.len)
+                                break :blk path.loader(&this.transpiler.options.loaders) orelse options.Loader.file;
+                            const record: *ImportRecord = &list[resolve.import_record.import_record_index];
+                            if (record.loader) |override_loader| break :blk override_loader;
+                            break :blk path.loader(&this.transpiler.options.loaders) orelse options.Loader.file;
                         };
src/js_printer.zig (1)

2099-2102: Fallback still doesn’t throw — invoke the IIFE.

The .import_meta_glob branch is still printing a bare function expression, so the runtime gets a function value instead of throwing. Please append () (and a terminator) to actually execute the IIFE.

-                        p.print("(function() { throw new Error('import.meta.glob was not transformed at build time'); })");
+                        p.print("(function(){throw new Error('import.meta.glob was not transformed at build time')})();");
packages/bun-types/globals.d.ts (1)

1366-1374: Type soundness violation: Remove the eager overload.

The third overload at line 1374 promises Record<string, TModule> when eager mode is enabled, but this is unsound:

  1. ImportMetaGlobOptions.eager is explicitly constrained to false (line 1408), making this overload unreachable through normal usage
  2. The PR description confirms eager mode is not implemented in the runtime
  3. The runtime will always emit lazy loader functions () => Promise<TModule>, never raw modules

Additionally, the first overload's conditional return type Eager extends true ? Record<string, TModule> : Record<string, () => Promise<TModule>> is misleading since ImportMetaGlobOptions<Eager> can only accept false as a valid eager value.

This was flagged in a previous review but appears to still be present. The types must match runtime behavior to prevent false expectations and potential runtime errors.

Apply this diff to remove the unsound overload and simplify the signature:

-  glob<Eager extends boolean = false, TModule = unknown>(
+  glob<TModule = unknown>(
     pattern: string | string[],
-    options?: ImportMetaGlobOptions<Eager>,
-  ): Eager extends true ? Record<string, TModule> : Record<string, () => Promise<TModule>>;
-  glob<TModule = unknown>(
-    pattern: string | string[],
-    options?: ImportMetaGlobOptions<false>,
+    options?: ImportMetaGlobOptions,
   ): Record<string, () => Promise<TModule>>;
-  glob<TModule = unknown>(pattern: string | string[], options?: ImportMetaGlobOptions<true>): Record<string, TModule>;

When eager mode is implemented in the future, add back an overload with a separate interface like ImportMetaGlobEagerOptions that explicitly sets eager: true.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 533011d and 8a6710f.

📒 Files selected for processing (6)
  • docs/api/import-meta.md (2 hunks)
  • packages/bun-types/globals.d.ts (1 hunks)
  • src/ast/P.zig (3 hunks)
  • src/bundler/bundle_v2.zig (1 hunks)
  • src/js_printer.zig (2 hunks)
  • test/js/bun/glob/import-meta-glob.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (13)
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/ast/P.zig
  • src/bundler/bundle_v2.zig
  • src/js_printer.zig
src/**/*.zig

📄 CodeRabbit inference engine (CLAUDE.md)

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

When adding debug logs in Zig, create a scoped logger and log via Bun APIs: const log = bun.Output.scoped(.${SCOPE}, .hidden); then log("...", .{})

src/**/*.zig: Use private fields in Zig with the # prefix (e.g., struct { #foo: u32 };)
Prefer decl literals in Zig (e.g., const decl: Decl = .{ .binding = 0, .value = 0 };)
Prefer placing @import statements at the bottom of the Zig file (formatter may reorder automatically)
Prefer @import("bun") rather than @import("root").bun or @import("../bun.zig")

Files:

  • src/ast/P.zig
  • src/bundler/bundle_v2.zig
  • src/js_printer.zig
test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place all tests under the test/ directory

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/js/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place JavaScript and TypeScript tests under test/js/

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/js/bun/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/**/*.{js,ts}: Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test
Prefer data-driven tests (e.g., test.each) to reduce boilerplate
Use shared utilities from test/harness.ts where applicable

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must live under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or roll your own random port
Prefer normalizeBunSnapshot for snapshotting test output instead of asserting raw strings
Do not write tests that assert absence of crashes (e.g., 'no panic' or 'no uncaught exception')
Use Bun’s Jest-compatible runner (import { test, expect } from "bun:test") for tests
Avoid shell commands like find or grep in tests; use Bun’s Glob and built-in tools instead
Prefer running tests via bun bd test and use provided harness utilities (bunEnv, bunExe, tempDir)
Use Bun.spawn with proper stdio handling and await proc.exited in process-spawning tests

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/js/bun/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place Bun-specific API tests under test/js/bun/

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Use bun:test for files ending with *.test.{ts,js,jsx,tsx,mjs,cjs}
Prefer concurrent tests (test.concurrent/describe.concurrent) over sequential when feasible
Organize tests with describe blocks to group related tests
Use utilities like describe.each, toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach) and track resources for cleanup

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/**/*.{ts,tsx,js,jsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

For large/repetitive strings, use Buffer.alloc(count, fill).toString() instead of "A".repeat(count)

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/js/{bun,node}/**

📄 CodeRabbit inference engine (test/CLAUDE.md)

Organize unit tests by module under /test/js/bun/ and /test/js/node/

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
src/**/js_*.zig

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

src/**/js_*.zig: Implement JavaScript bindings in a Zig file named with a js_ prefix (e.g., js_smtp.zig, js_your_feature.zig)
Handle reference counting correctly with ref()/deref() in JS-facing Zig code
Always implement proper cleanup in deinit() and finalize() for JS-exposed types

Files:

  • src/js_printer.zig
src/{**/js_*.zig,bun.js/api/**/*.zig}

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Files:

  • src/js_printer.zig
🧠 Learnings (12)
📚 Learning: 2025-09-06T03:37:41.154Z
Learnt from: taylordotfish
PR: oven-sh/bun#22229
File: src/bundler/LinkerGraph.zig:0-0
Timestamp: 2025-09-06T03:37:41.154Z
Learning: In Bun's codebase, when checking import record source indices in src/bundler/LinkerGraph.zig, prefer using `if (import_index >= self.import_records.len)` bounds checking over `isValid()` checks, as the bounds check is more robust and `isValid()` is a strict subset of this condition.

Applied to files:

  • src/ast/P.zig
  • src/bundler/bundle_v2.zig
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/bun/**/*.{js,ts} : Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-10-04T09:51:30.294Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T09:51:30.294Z
Learning: Applies to test/**/*.test.{ts,tsx} : Use Bun’s Jest-compatible runner (import { test, expect } from "bun:test") for tests

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` for files ending with `*.test.{ts,js,jsx,tsx,mjs,cjs}`

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : When spawning Bun in tests, use `bunExe()` and `bunEnv` from `harness`

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/bundle.test.ts : bundle.test.ts should contain DevServer-specific bundling tests

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/cli/**/*.{js,ts} : When testing Bun as a CLI, use spawn with bunExe() and bunEnv from harness, and capture stdout/stderr via pipes

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-10-04T09:51:30.294Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T09:51:30.294Z
Learning: Applies to test/**/*.test.{ts,tsx} : Prefer running tests via bun bd test <file> and use provided harness utilities (bunEnv, bunExe, tempDir)

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/**/*.{js,ts} : Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js,test/**/*-fixture.ts} : Use `using`/`await using` for resource cleanup with Bun APIs (e.g., `Bun.spawn`, `Bun.listen`, `Bun.serve`)

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : Import common utilities from `harness` (e.g., `bunExe`, `bunEnv`, `tempDirWithFiles`, platform helpers, GC helpers)

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-09-13T17:01:41.393Z
Learnt from: RiskyMH
PR: oven-sh/bun#21459
File: docs/api/import-meta.md:0-0
Timestamp: 2025-09-13T17:01:41.393Z
Learning: For docs/api/import-meta.md, keep documentation entries concise and consistent with the existing style - brief descriptions with small inline examples, not extensive bullet points or detailed explanations.

Applied to files:

  • docs/api/import-meta.md
🧬 Code graph analysis (1)
test/js/bun/glob/import-meta-glob.test.ts (1)
test/harness.ts (3)
  • tempDirWithFiles (259-266)
  • bunExe (102-105)
  • tempDir (277-284)
🪛 LanguageTool
docs/api/import-meta.md

[grammar] ~74-~74: Use correct spacing
Context: ...ng file paths to lazy-loading functions. ts const modules = import.meta.glob("./src/*.ts"); // const modules = { // './src/a.ts': () => import('./src/a.ts'), // './src/b.ts': () => import('./src/b.ts'), // } {% /table %}

(QB_NEW_EN_OTHER_ERROR_IDS_5)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (1)
packages/bun-types/globals.d.ts (1)

1463-1465: LGTM!

The ImportAttributes interface correctly defines supported import types with a flexible catch-all for custom loaders. The (string & {}) pattern preserves autocomplete for common types while allowing any string value.

Comment thread packages/bun-types/globals.d.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/ast/P.zig (1)

6138-6243: Return module specifiers relative to the importer

We’re still emitting absolute filesystem paths as both object keys and import targets (slash_normalized is appended directly), so the generated map looks like { "/Users/me/project/src/foo.js": () => import("/Users/me/project/src/foo.js") }. That breaks bundle-time resolution and diverges from Vite’s contract, which requires specifiers relative to the current module (e.g. ./foo.js). Please slice off search_dir, guard the separator, add a ./ prefix when needed, and reuse that relative specifier when building import_path (then apply base/query). Example fix:

-                    const duped = bun.handleOom(p.allocator.dupe(u8, slash_normalized));
-                    bun.handleOom(matched_files.append(duped));
+                    const rel = blk: {
+                        if (strings.hasPrefix(slash_normalized, search_dir) and slash_normalized.len >= search_dir.len) {
+                            var start = search_dir.len;
+                            if (start < slash_normalized.len and (slash_normalized[start] == '/' or slash_normalized[start] == '\\')) {
+                                start += 1;
+                            }
+                            break :blk slash_normalized[start..];
+                        }
+                        break :blk slash_normalized;
+                    };
+                    const specifier = if (rel.len == 0 or rel[0] == '.' or rel[0] == '/')
+                        bun.handleOom(p.allocator.dupe(u8, rel))
+                    else
+                        bun.handleOom(std.fmt.allocPrint(p.allocator, "./{s}", .{rel}));
+                    bun.handleOom(matched_files.append(specifier));

Then build import_path from specifier (apply base / query on top). This keeps us compatible with Vite and makes bundler resolution work again.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bd38223 and abcfcd5.

📒 Files selected for processing (2)
  • src/ast/P.zig (3 hunks)
  • test/js/bun/glob/import-meta-glob.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/ast/P.zig
src/**/*.zig

📄 CodeRabbit inference engine (CLAUDE.md)

In Zig code, manage memory carefully and use defer for cleanup of allocations/resources

When adding debug logs in Zig, create a scoped logger and log via Bun APIs: const log = bun.Output.scoped(.${SCOPE}, .hidden); then log("...", .{})

src/**/*.zig: Use private fields in Zig with the # prefix (e.g., struct { #foo: u32 };)
Prefer decl literals in Zig (e.g., const decl: Decl = .{ .binding = 0, .value = 0 };)
Prefer placing @import statements at the bottom of the Zig file (formatter may reorder automatically)
Prefer @import("bun") rather than @import("root").bun or @import("../bun.zig")

Files:

  • src/ast/P.zig
test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place all tests under the test/ directory

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/js/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place JavaScript and TypeScript tests under test/js/

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/js/bun/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/**/*.{js,ts}: Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test
Prefer data-driven tests (e.g., test.each) to reduce boilerplate
Use shared utilities from test/harness.ts where applicable

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must live under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or roll your own random port
Prefer normalizeBunSnapshot for snapshotting test output instead of asserting raw strings
Do not write tests that assert absence of crashes (e.g., 'no panic' or 'no uncaught exception')
Use Bun’s Jest-compatible runner (import { test, expect } from "bun:test") for tests
Avoid shell commands like find or grep in tests; use Bun’s Glob and built-in tools instead
Prefer running tests via bun bd test and use provided harness utilities (bunEnv, bunExe, tempDir)
Use Bun.spawn with proper stdio handling and await proc.exited in process-spawning tests

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/js/bun/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place Bun-specific API tests under test/js/bun/

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Use bun:test for files ending with *.test.{ts,js,jsx,tsx,mjs,cjs}
Prefer concurrent tests (test.concurrent/describe.concurrent) over sequential when feasible
Organize tests with describe blocks to group related tests
Use utilities like describe.each, toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach) and track resources for cleanup

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/**/*.{ts,tsx,js,jsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

For large/repetitive strings, use Buffer.alloc(count, fill).toString() instead of "A".repeat(count)

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
test/js/{bun,node}/**

📄 CodeRabbit inference engine (test/CLAUDE.md)

Organize unit tests by module under /test/js/bun/ and /test/js/node/

Files:

  • test/js/bun/glob/import-meta-glob.test.ts
🧠 Learnings (11)
📚 Learning: 2025-09-06T03:37:41.154Z
Learnt from: taylordotfish
PR: oven-sh/bun#22229
File: src/bundler/LinkerGraph.zig:0-0
Timestamp: 2025-09-06T03:37:41.154Z
Learning: In Bun's codebase, when checking import record source indices in src/bundler/LinkerGraph.zig, prefer using `if (import_index >= self.import_records.len)` bounds checking over `isValid()` checks, as the bounds check is more robust and `isValid()` is a strict subset of this condition.

Applied to files:

  • src/ast/P.zig
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/bun/**/*.{js,ts} : Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-10-04T09:51:30.294Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T09:51:30.294Z
Learning: Applies to test/**/*.test.{ts,tsx} : Use Bun’s Jest-compatible runner (import { test, expect } from "bun:test") for tests

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` for files ending with `*.test.{ts,js,jsx,tsx,mjs,cjs}`

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : When spawning Bun in tests, use `bunExe()` and `bunEnv` from `harness`

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-10-04T09:51:30.294Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T09:51:30.294Z
Learning: Applies to test/**/*.test.{ts,tsx} : Avoid shell commands like find or grep in tests; use Bun’s Glob and built-in tools instead

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/**/*.{js,ts} : Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js,test/**/*-fixture.ts} : Use `using`/`await using` for resource cleanup with Bun APIs (e.g., `Bun.spawn`, `Bun.listen`, `Bun.serve`)

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/cli/**/*.{js,ts} : When testing Bun as a CLI, use spawn with bunExe() and bunEnv from harness, and capture stdout/stderr via pipes

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-10-04T09:51:30.294Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T09:51:30.294Z
Learning: Applies to test/**/*.test.{ts,tsx} : Use Bun.spawn with proper stdio handling and await proc.exited in process-spawning tests

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/bundle.test.ts : bundle.test.ts should contain DevServer-specific bundling tests

Applied to files:

  • test/js/bun/glob/import-meta-glob.test.ts
🧬 Code graph analysis (1)
test/js/bun/glob/import-meta-glob.test.ts (1)
test/harness.ts (3)
  • tempDirWithFiles (259-266)
  • bunExe (102-105)
  • tempDir (277-284)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (2)
test/js/bun/glob/import-meta-glob.test.ts (2)

856-856: Bundler transformation check is appropriate.

The assertion that the bundled output doesn't contain "glob" is a reasonable sanity check to verify that import.meta.glob calls were transformed away during bundling. While string searches can be fragile, this check serves as a quick validation that the transformation occurred.


5-880: Excellent comprehensive test coverage.

The test suite thoroughly covers:

  • Runtime behavior with various glob patterns (single, multiple, recursive, negative)
  • Named export extraction via the import option
  • Query and with option propagation
  • Base path semantics with relative and parent directories
  • Bundler integration with various scenarios (basic, splitting, negative patterns)
  • Error cases (eager mode not yet supported, empty results)

The tests properly use bunExe() and bunEnv from harness, spawn processes with correct stdio handling, and await process completion. The organization into runtime and bundler behavior sections is clear and maintainable.

Based on learnings: Tests follow the guidelines for using bun:test, spawning Bun with bunExe() and bunEnv, and organizing tests under test/js/bun/glob/.

Comment on lines +7 to +36
test("returns lazy-loading functions for matched files", async () => {
const dir = tempDirWithFiles("import-glob-basic", {
"index.js": `
const modules = import.meta.glob('./modules/*.js');
console.log(JSON.stringify(Object.keys(modules)));
console.log(typeof modules['./modules/a.js']);
`,
"modules/a.js": `export const name = "a";`,
"modules/b.js": `export const name = "b";`,
"modules/c.js": `export const name = "c";`,
});

await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: dir,
});

const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);

expect(exitCode).toBe(0);
expect(stderr).toBe("");
const lines = stdout.trim().split("\n");
expect(JSON.parse(lines[0])).toEqual(["./modules/a.js", "./modules/b.js", "./modules/c.js"]);
expect(lines[1]).toBe("function");
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Apply consistent resource cleanup pattern.

This test and several others (lines 38, 90, 122, 149, 195, 236, 267, 294) use tempDirWithFiles without automatic cleanup. Per coding guidelines, prefer using dir = tempDir(...) to ensure temp directories are cleaned up even if tests fail.

Apply this pattern consistently across all tests:

-    const dir = tempDirWithFiles("import-glob-basic", {
+    using dir = tempDir("import-glob-basic", {
       "index.js": `

Then update the cwd to use String(dir) if needed:

       cwd: dir,
+      cwd: String(dir),

This refactor should be applied to tests at lines 7, 38, 90, 122, 149, 195, 236, 267, and 294 for consistency with tests at lines 342, 386, 424, 736, and 783 that already follow this pattern.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test("returns lazy-loading functions for matched files", async () => {
const dir = tempDirWithFiles("import-glob-basic", {
"index.js": `
const modules = import.meta.glob('./modules/*.js');
console.log(JSON.stringify(Object.keys(modules)));
console.log(typeof modules['./modules/a.js']);
`,
"modules/a.js": `export const name = "a";`,
"modules/b.js": `export const name = "b";`,
"modules/c.js": `export const name = "c";`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: dir,
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
expect(exitCode).toBe(0);
expect(stderr).toBe("");
const lines = stdout.trim().split("\n");
expect(JSON.parse(lines[0])).toEqual(["./modules/a.js", "./modules/b.js", "./modules/c.js"]);
expect(lines[1]).toBe("function");
});
test("returns lazy-loading functions for matched files", async () => {
using dir = tempDir("import-glob-basic", {
"index.js": `
const modules = import.meta.glob('./modules/*.js');
console.log(JSON.stringify(Object.keys(modules)));
console.log(typeof modules['./modules/a.js']);
`,
"modules/a.js": `export const name = "a";`,
"modules/b.js": `export const name = "b";`,
"modules/c.js": `export const name = "c";`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: String(dir),
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
expect(exitCode).toBe(0);
expect(stderr).toBe("");
const lines = stdout.trim().split("\n");
expect(JSON.parse(lines[0])).toEqual(["./modules/a.js", "./modules/b.js", "./modules/c.js"]);
expect(lines[1]).toBe("function");
});
🤖 Prompt for AI Agents
In test/js/bun/glob/import-meta-glob.test.ts around lines 7 to 36, replace the
raw tempDirWithFiles(...) call with the RAII pattern so the temp dir is always
cleaned up: declare it as "using dir = tempDirWithFiles(...)" (or "using dir =
tempDir(...)" if you prefer the plain tempDir helper) and update the Bun.spawn
cwd to use String(dir). Apply the same change to the other test locations listed
(lines 38, 90, 122, 149, 195, 236, 267, 294) to ensure consistent automatic
cleanup.

Comment on lines +229 to +234
test("handles empty results gracefully", () => {
const modules = import.meta.glob("./non-existent/*.js");
expect(typeof modules).toBe("object");
expect(Object.keys(modules)).toHaveLength(0);
expect(JSON.stringify(modules)).toBe("{}");
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Isolate test in spawned process for consistency.

Unlike other tests in this suite, this test runs import.meta.glob directly in the test process rather than spawning a separate Bun process. While this works, it creates inconsistency and could cause issues if the test file's directory structure changes.

Consider spawning a process like other tests for consistency:

-  test("handles empty results gracefully", () => {
-    const modules = import.meta.glob("./non-existent/*.js");
-    expect(typeof modules).toBe("object");
-    expect(Object.keys(modules)).toHaveLength(0);
-    expect(JSON.stringify(modules)).toBe("{}");
-  });
+  test("handles empty results gracefully", async () => {
+    using dir = tempDir("import-glob-empty", {
+      "index.js": `
+        const modules = import.meta.glob("./non-existent/*.js");
+        console.log('TYPE:', typeof modules);
+        console.log('LENGTH:', Object.keys(modules).length);
+        console.log('JSON:', JSON.stringify(modules));
+      `,
+    });
+
+    await using proc = Bun.spawn({
+      cmd: [bunExe(), "index.js"],
+      env: bunEnv,
+      cwd: String(dir),
+    });
+
+    const [stdout, stderr, exitCode] = await Promise.all([
+      new Response(proc.stdout).text(),
+      new Response(proc.stderr).text(),
+      proc.exited,
+    ]);
+
+    expect(exitCode).toBe(0);
+    expect(stderr).toBe("");
+    expect(stdout).toContain("TYPE: object");
+    expect(stdout).toContain("LENGTH: 0");
+    expect(stdout).toContain('JSON: {}');
+  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test("handles empty results gracefully", () => {
const modules = import.meta.glob("./non-existent/*.js");
expect(typeof modules).toBe("object");
expect(Object.keys(modules)).toHaveLength(0);
expect(JSON.stringify(modules)).toBe("{}");
});
test("handles empty results gracefully", async () => {
using dir = tempDir("import-glob-empty", {
"index.js": `
const modules = import.meta.glob("./non-existent/*.js");
console.log('TYPE:', typeof modules);
console.log('LENGTH:', Object.keys(modules).length);
console.log('JSON:', JSON.stringify(modules));
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: String(dir),
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
expect(exitCode).toBe(0);
expect(stderr).toBe("");
expect(stdout).toContain("TYPE: object");
expect(stdout).toContain("LENGTH: 0");
expect(stdout).toContain('JSON: {}');
});
🤖 Prompt for AI Agents
In test/js/bun/glob/import-meta-glob.test.ts around lines 229 to 234, this test
calls import.meta.glob directly in the test process which is inconsistent with
other tests; change it to spawn a separate Bun process that runs a small script
performing the import.meta.glob call and prints/asserts the result (or returns
JSON) so the parent test can read and assert that the result is an empty object;
ensure the spawned process is created with the same environment/working
directory as other tests, capture stdout/stderr, parse the output and replace
the direct assertions with assertions against the spawned process output and
exit code to maintain consistency with the suite.

Comment on lines +458 to +493
test("preserves import.meta.glob functionality after bundling", async () => {
const dir = tempDirWithFiles("import-glob-bundle", {
"index.js": `
const modules = import.meta.glob('./src/*.js');
console.log('COUNT:', Object.keys(modules).length);
console.log('FIRST_TYPE:', typeof Object.values(modules)[0]);
`,
"src/a.js": `export default "a";`,
"src/b.js": `export default "b";`,
});

await using buildProc = Bun.spawn({
cmd: [bunExe(), "build", "index.js", "--outfile", "dist/bundle.js"],
env: bunEnv,
cwd: dir,
});
await buildProc.exited;

await using runProc = Bun.spawn({
cmd: [bunExe(), "dist/bundle.js"],
env: bunEnv,
cwd: dir,
});

const [stdout, stderr, exitCode] = await Promise.all([
new Response(runProc.stdout).text(),
new Response(runProc.stderr).text(),
runProc.exited,
]);

expect(exitCode).toBe(0);
expect(stderr).toBe("");
const lines = stdout.trim().split("\n");
expect(lines[0]).toBe("COUNT: 2");
expect(lines[1]).toBe("FIRST_TYPE: function");
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Apply consistent resource cleanup in bundler tests.

Similar to runtime tests, bundler tests at lines 458, 496, 527, 567, 634, 681, and 830 use tempDirWithFiles without automatic cleanup, while tests at lines 736 and 783 correctly use using dir = tempDir(...).

Apply the same resource cleanup pattern:

-    const dir = tempDirWithFiles("import-glob-bundle", {
+    using dir = tempDir("import-glob-bundle", {

And update cwd references:

-      cwd: dir,
+      cwd: String(dir),

This ensures temp directories are properly cleaned up even if tests fail, following the coding guidelines for resource management with using/await using.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test("preserves import.meta.glob functionality after bundling", async () => {
const dir = tempDirWithFiles("import-glob-bundle", {
"index.js": `
const modules = import.meta.glob('./src/*.js');
console.log('COUNT:', Object.keys(modules).length);
console.log('FIRST_TYPE:', typeof Object.values(modules)[0]);
`,
"src/a.js": `export default "a";`,
"src/b.js": `export default "b";`,
});
await using buildProc = Bun.spawn({
cmd: [bunExe(), "build", "index.js", "--outfile", "dist/bundle.js"],
env: bunEnv,
cwd: dir,
});
await buildProc.exited;
await using runProc = Bun.spawn({
cmd: [bunExe(), "dist/bundle.js"],
env: bunEnv,
cwd: dir,
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(runProc.stdout).text(),
new Response(runProc.stderr).text(),
runProc.exited,
]);
expect(exitCode).toBe(0);
expect(stderr).toBe("");
const lines = stdout.trim().split("\n");
expect(lines[0]).toBe("COUNT: 2");
expect(lines[1]).toBe("FIRST_TYPE: function");
});
test("preserves import.meta.glob functionality after bundling", async () => {
using dir = tempDir("import-glob-bundle", {
"index.js": `
const modules = import.meta.glob('./src/*.js');
console.log('COUNT:', Object.keys(modules).length);
console.log('FIRST_TYPE:', typeof Object.values(modules)[0]);
`,
"src/a.js": `export default "a";`,
"src/b.js": `export default "b";`,
});
await using buildProc = Bun.spawn({
cmd: [bunExe(), "build", "index.js", "--outfile", "dist/bundle.js"],
env: bunEnv,
cwd: String(dir),
});
await buildProc.exited;
await using runProc = Bun.spawn({
cmd: [bunExe(), "dist/bundle.js"],
env: bunEnv,
cwd: String(dir),
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(runProc.stdout).text(),
new Response(runProc.stderr).text(),
runProc.exited,
]);
expect(exitCode).toBe(0);
expect(stderr).toBe("");
const lines = stdout.trim().split("\n");
expect(lines[0]).toBe("COUNT: 2");
expect(lines[1]).toBe("FIRST_TYPE: function");
});
🤖 Prompt for AI Agents
In test/js/bun/glob/import-meta-glob.test.ts around lines 458 to 493, replace
the plain tempDirWithFiles(...) call with the resource-managed pattern used
elsewhere so the temp directory is always cleaned up: declare the directory with
the using/await using pattern (e.g., await using dir =
tempDirWithFiles("import-glob-bundle", { ... });) and update subsequent cwd
references to use that dir variable (cwd: dir or cwd: dir.path consistent with
other tests). Repeat this change for the other bundler test sites called out in
the review to ensure deterministic cleanup.

@carlosmintfan

Copy link
Copy Markdown

Awesome that this feature gets implemented! Seems not to support tsconfig path aliases at the moment?

@carlosmintfan

carlosmintfan commented Nov 8, 2025 •

Copy link
Copy Markdown

Also, --hot on the command line plus development: { hmr: true, console: true } in the server file still don't make the glob updated automatically when new files that match it are created - could that be implemented? (Even a full reload on the client side is currently not enough, I have to restart the server manually.)

@robobun

robobun commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Closing as stale: this PR predates the Rust rewrite. Every src/ file it modifies has since been removed or relocated on main (Zig sources deleted; src/bun.js/ reorganized into src/jsc/), so it can no longer merge.

If the underlying change is still wanted, it will need to be redone against the current Rust/C++ tree. Apologies for the churn, and thank you for the contribution.

@robobun robobun closed this Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement import.meta.glob

7 participants