mirror of
https://github.com/oven-sh/bun
synced 2026-02-26 03:27:23 +01:00
Compare commits
37 Commits
claude/fix
...
riskymh/im
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c82efd6cd | ||
|
|
abcfcd5bc4 | ||
|
|
bd3822348b | ||
|
|
8a6710f1a5 | ||
|
|
69d9f83319 | ||
|
|
533011d25f | ||
|
|
4c7e8f0649 | ||
|
|
490c551812 | ||
|
|
c431a726d6 | ||
|
|
7236daa029 | ||
|
|
72cb84ce17 | ||
|
|
b69193ce05 | ||
|
|
0e5c48bb8d | ||
|
|
f530a16f1d | ||
|
|
8ba8e96a33 | ||
|
|
fe188726dc | ||
|
|
ed3f2d6629 | ||
|
|
95bd24e6b0 | ||
|
|
ccaef4bc19 | ||
|
|
ce1756a2d4 | ||
|
|
ddb56e83db | ||
|
|
642fb9d181 | ||
|
|
e86f0c65f1 | ||
|
|
18bf62da3e | ||
|
|
5083f26b02 | ||
|
|
6cd626a4ce | ||
|
|
8150288fda | ||
|
|
65d491e5ec | ||
|
|
892a20d108 | ||
|
|
6894f8a14a | ||
|
|
61f2d9eedd | ||
|
|
5ddea5ce1d | ||
|
|
e0bd157f68 | ||
|
|
6a60b82fd2 | ||
|
|
2f9ff3f31d | ||
|
|
5812103e08 | ||
|
|
581d72ae60 |
@@ -12,6 +12,8 @@ import.meta.main; // `true` if this file is directly executed by `bun run`
|
||||
// `false` otherwise
|
||||
|
||||
import.meta.resolve("zod"); // => "file:///path/to/project/node_modules/zod/index.js"
|
||||
|
||||
import.meta.glob("./src/*.ts"); // => { "./src/a.ts": () => import("./src/a.ts"), ... }
|
||||
```
|
||||
|
||||
{% table %}
|
||||
@@ -66,4 +68,17 @@ import.meta.resolve("zod"); // => "file:///path/to/project/node_modules/zod/inde
|
||||
- `import.meta.url`
|
||||
- A `string` url to the current file, e.g. `file:///path/to/project/index.ts`. Equivalent to [`import.meta.url` in browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#url)
|
||||
|
||||
---
|
||||
|
||||
- `import.meta.glob`
|
||||
- Import multiple modules using glob patterns. Returns an object mapping 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 %}
|
||||
|
||||
113
packages/bun-types/globals.d.ts
vendored
113
packages/bun-types/globals.d.ts
vendored
@@ -1344,6 +1344,35 @@ interface ImportMeta {
|
||||
*/
|
||||
main: boolean;
|
||||
|
||||
/**
|
||||
* Import multiple modules using glob patterns.
|
||||
* Inspired by [Vite's `import.meta.glob`](https://vite.dev/guide/features.html#glob-import).
|
||||
*
|
||||
* @param pattern - A glob pattern or array of glob patterns to match files
|
||||
* @param options - Options for how imports are handled
|
||||
* @returns An object mapping file paths to dynamic import functions
|
||||
*
|
||||
* @example
|
||||
* const modules = import.meta.glob('./src/*.ts')
|
||||
* const module = await modules['./src/foo.ts']()
|
||||
*
|
||||
* // equivalent to
|
||||
* const modules = {
|
||||
* './src/foo.ts': () => import('./src/foo.ts'),
|
||||
* './src/bar.ts': () => import('./src/bar.ts'),
|
||||
* }
|
||||
* const module = await modules['./src/foo.ts']()
|
||||
*/
|
||||
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>;
|
||||
|
||||
/** Alias of `import.meta.dir`. Exists for Node.js compatibility */
|
||||
dirname: string;
|
||||
|
||||
@@ -1351,6 +1380,90 @@ interface ImportMeta {
|
||||
filename: string;
|
||||
}
|
||||
|
||||
interface ImportMetaGlobOptions<Eager extends boolean = false> {
|
||||
// todo:
|
||||
// /**
|
||||
// * If true, imports all modules eagerly as if they were top level imports.
|
||||
// * If false (default), returns functions that import modules lazily with dynamic imports.
|
||||
// *
|
||||
// * @example
|
||||
// * const eager = import.meta.glob('./src/*.ts', { eager: true })
|
||||
// * const normal = import.meta.glob('./src/*.ts', { eager: false })
|
||||
// *
|
||||
// * // equivalent to
|
||||
// * 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;
|
||||
/** Right now Bun doesn't support eager mode */
|
||||
eager?: false;
|
||||
/**
|
||||
* Specify a named export to import from matched modules.
|
||||
* If not specified, imports the entire module.
|
||||
*
|
||||
* @example
|
||||
* const modules = import.meta.glob('./src/*.ts', { import: 'default' })
|
||||
*
|
||||
* // equivalent to
|
||||
* const modules = {
|
||||
* './src/bar.ts': () => import('./src/bar.ts').then((m) => m.default),
|
||||
* './src/foo.ts': () => import('./src/foo.ts').then((m) => m.default),
|
||||
* }
|
||||
*/
|
||||
import?: "default" | (string & {});
|
||||
/**
|
||||
* Add a query string to the end of the "generated" dynamic import.
|
||||
*
|
||||
* @example
|
||||
* const modules = import.meta.glob('./assets/*.txt', { query: '?something' })
|
||||
*
|
||||
* // equivalent to
|
||||
* const modules = {
|
||||
* './assets/file.txt': () => import('./assets/file.txt?something'),
|
||||
* }
|
||||
*/
|
||||
query?: string;
|
||||
/**
|
||||
* Import attributes to pass to the import statement.
|
||||
* This is like the standard way to specify import options to a normal dynamic import.
|
||||
*
|
||||
* @example
|
||||
* const modules = import.meta.glob('./assets/*.txt', { with: { type: 'text' } })
|
||||
*
|
||||
* // equivalent to
|
||||
* const modules = {
|
||||
* './assets/file.txt': () => import('./assets/file.txt', { with: { type: 'text' } }),
|
||||
* }
|
||||
*/
|
||||
with?: ImportAttributes;
|
||||
/**
|
||||
* The base path to prepend to the imports.
|
||||
* Basically changing the "cwd" of the directory to scan.
|
||||
*
|
||||
* @example
|
||||
* const modules = import.meta.glob('./*.txt', { base: '../public' })
|
||||
*
|
||||
* // equivalent to
|
||||
* const modules = {
|
||||
* './file.txt': () => import('../public/file.txt'),
|
||||
* }
|
||||
*/
|
||||
base?: string;
|
||||
}
|
||||
|
||||
interface ImportAttributes {
|
||||
type: "css" | "file" | "json" | "jsonc" | "toml" | "yaml" | "txt" | "text" | "html" | (string & {});
|
||||
}
|
||||
|
||||
/**
|
||||
* NodeJS-style `require` function
|
||||
*
|
||||
|
||||
@@ -192,6 +192,8 @@ pub const Special = union(enum) {
|
||||
hot_accept_visited,
|
||||
/// Prints the resolved specifier string for an import record.
|
||||
resolved_specifier_string: ImportRecord.Index,
|
||||
/// `import.meta.glob`
|
||||
import_meta_glob,
|
||||
};
|
||||
|
||||
pub const Call = struct {
|
||||
|
||||
323
src/ast/P.zig
323
src/ast/P.zig
@@ -298,6 +298,10 @@ pub fn NewParser_(
|
||||
export_star_import_records: List(u32) = .{},
|
||||
import_symbol_property_uses: SymbolPropertyUseMap = .{},
|
||||
|
||||
// Track generated import statements from eager globs
|
||||
eager_glob_import_stmts: List(Stmt) = .{},
|
||||
glob_call_counter: u32 = 0,
|
||||
|
||||
// These are for handling ES6 imports and exports
|
||||
esm_import_keyword: logger.Range = logger.Range.None,
|
||||
esm_export_keyword: logger.Range = logger.Range.None,
|
||||
@@ -511,6 +515,10 @@ pub fn NewParser_(
|
||||
p.import_records.items[import_record_index].tag = tag;
|
||||
}
|
||||
|
||||
if (state.import_loader) |loader| {
|
||||
p.import_records.items[import_record_index].loader = loader;
|
||||
}
|
||||
|
||||
p.import_records.items[import_record_index].handles_import_errors = (state.is_await_target and p.fn_or_arrow_data_visit.try_body_count != 0) or state.is_then_catch_target;
|
||||
p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable;
|
||||
|
||||
@@ -6014,6 +6022,306 @@ pub fn NewParser_(
|
||||
} };
|
||||
}
|
||||
|
||||
pub fn handleImportMetaGlobCall(p: *P, call: *E.Call, loc: logger.Loc) Expr {
|
||||
if (call.args.len == 0) {
|
||||
bun.handleOom(p.log.addError(p.source, loc, "import.meta.glob() requires at least one argument"));
|
||||
return p.newExpr(E.Object{}, loc);
|
||||
}
|
||||
|
||||
// Parse patterns
|
||||
var patterns = std.ArrayList([]const u8).init(p.allocator);
|
||||
defer patterns.deinit();
|
||||
|
||||
switch (call.args.at(0).data) {
|
||||
.e_string => |str| bun.handleOom(patterns.append(str.slice(p.allocator))),
|
||||
.e_array => |arr| {
|
||||
for (arr.items.slice()) |item| {
|
||||
if (item.data == .e_string) {
|
||||
bun.handleOom(patterns.append(item.data.e_string.slice(p.allocator)));
|
||||
} else {
|
||||
bun.handleOom(p.log.addError(p.source, item.loc, "import.meta.glob() patterns must be string literals"));
|
||||
return p.newExpr(E.Object{}, loc);
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {
|
||||
bun.handleOom(p.log.addError(p.source, call.args.at(0).loc, "import.meta.glob() patterns must be string literals or an array of string literals"));
|
||||
return p.newExpr(E.Object{}, loc);
|
||||
},
|
||||
}
|
||||
|
||||
// Parse options
|
||||
var query: ?[]const u8 = null;
|
||||
var import_name: ?[]const u8 = null;
|
||||
var loader: ?options.Loader = null;
|
||||
var with_attrs: ?*const E.Object = null;
|
||||
var base_path: ?[]const u8 = null;
|
||||
var eager: bool = false;
|
||||
|
||||
if (call.args.len >= 2 and call.args.at(1).data == .e_object) {
|
||||
const obj = call.args.at(1).data.e_object;
|
||||
if (obj.get("query")) |query_value| {
|
||||
if (query_value.data == .e_string) {
|
||||
query = query_value.data.e_string.slice(p.allocator);
|
||||
}
|
||||
}
|
||||
if (obj.get("import")) |import_value| {
|
||||
if (import_value.data == .e_string) {
|
||||
import_name = import_value.data.e_string.slice(p.allocator);
|
||||
}
|
||||
}
|
||||
if (obj.get("eager")) |eager_value| {
|
||||
if (eager_value.data == .e_boolean) {
|
||||
eager = eager_value.data.e_boolean.value;
|
||||
}
|
||||
}
|
||||
if (obj.get("base")) |base_value| {
|
||||
if (base_value.data == .e_string) {
|
||||
const _base_path = base_value.data.e_string.slice(p.allocator);
|
||||
base_path = if (strings.hasPrefixComptime(_base_path, "./") or strings.hasPrefixComptime(_base_path, "../") or strings.hasPrefixComptime(_base_path, "/"))
|
||||
_base_path
|
||||
else
|
||||
bun.handleOom(std.fmt.allocPrint(p.allocator, "./{s}", .{_base_path}));
|
||||
}
|
||||
}
|
||||
if (obj.get("with")) |with_value| {
|
||||
if (with_value.data == .e_object) {
|
||||
with_attrs = with_value.data.e_object;
|
||||
if (with_attrs.?.get("type")) |type_value| {
|
||||
if (type_value.data == .e_string) {
|
||||
loader = options.Loader.fromString(type_value.data.e_string.slice(p.allocator));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find matching files
|
||||
const source_dir = p.source.path.sourceDir();
|
||||
const search_dir = if (base_path) |base| blk: {
|
||||
if (strings.hasPrefixComptime(base, "/")) {
|
||||
break :blk base;
|
||||
} else {
|
||||
var path_buf: bun.PathBuffer = undefined;
|
||||
const resolved = bun.path.joinAbsStringBuf(source_dir, &path_buf, &.{base}, .auto);
|
||||
break :blk bun.handleOom(p.allocator.dupe(u8, resolved));
|
||||
}
|
||||
} else source_dir;
|
||||
|
||||
var matched_files = std.ArrayList([]const u8).init(p.allocator);
|
||||
defer matched_files.deinit();
|
||||
|
||||
var glob_arena = bun.ArenaAllocator.init(p.allocator);
|
||||
defer glob_arena.deinit();
|
||||
|
||||
for (patterns.items) |pattern| {
|
||||
var walker = glob.BunGlobWalker{};
|
||||
defer walker.deinit(false);
|
||||
|
||||
switch (walker.initWithCwd(&glob_arena, pattern, search_dir, true, false, true, false, true) catch continue) {
|
||||
.err => continue,
|
||||
.result => {},
|
||||
}
|
||||
|
||||
var iter = glob.BunGlobWalker.Iterator{ .walker = &walker };
|
||||
defer iter.deinit();
|
||||
switch (iter.init() catch continue) {
|
||||
.err => continue,
|
||||
.result => {},
|
||||
}
|
||||
|
||||
while (switch (iter.next() catch continue) {
|
||||
.err => null,
|
||||
.result => |path| path,
|
||||
}) |path| brk: {
|
||||
if (patterns.items.len > 0) for (patterns.items) |patt| {
|
||||
if (patt.len < 1 or patt[0] != '!') continue;
|
||||
if (glob.match(patt[1..], path).matches()) {
|
||||
break :brk;
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
var properties: []G.Property = bun.handleOom(p.allocator.alloc(G.Property, matched_files.items.len));
|
||||
|
||||
for (matched_files.items, 0..) |file_path, i| {
|
||||
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;
|
||||
|
||||
// For eager mode, always use static import (synchronous)
|
||||
// For lazy mode, use dynamic import (returns Promise)
|
||||
const import_kind = if (eager)
|
||||
ImportKind.stmt
|
||||
else
|
||||
ImportKind.dynamic;
|
||||
const import_record_index = p.addImportRecord(import_kind, loc, import_path);
|
||||
bun.handleOom(p.import_records_for_current_part.append(p.allocator, import_record_index));
|
||||
|
||||
if (loader) |l| p.import_records.items[import_record_index].loader = l;
|
||||
|
||||
if (eager) {
|
||||
// Mark file as ESM since we're adding imports
|
||||
if (p.esm_import_keyword.len == 0) {
|
||||
p.esm_import_keyword = logger.Range{ .loc = loc, .len = 0 };
|
||||
}
|
||||
|
||||
const namespace_name = bun.handleOom(std.fmt.allocPrint(p.allocator, "__glob_{d}_{d}", .{ p.glob_call_counter, i }));
|
||||
|
||||
if (import_name) |name| {
|
||||
p.import_records.items[import_record_index].contains_default_alias = strings.eqlComptime(name, "default");
|
||||
} else {
|
||||
p.import_records.items[import_record_index].contains_import_star = true;
|
||||
}
|
||||
|
||||
// Create symbol in module scope since imports must be at top level
|
||||
const saved_scope = p.current_scope;
|
||||
p.current_scope = p.module_scope;
|
||||
const namespace_ref = bun.handleOom(p.newSymbol(.import, namespace_name));
|
||||
p.current_scope = saved_scope;
|
||||
|
||||
bun.handleOom(p.is_import_item.put(p.allocator, namespace_ref, {}));
|
||||
|
||||
if (import_name) |name| {
|
||||
bun.handleOom(p.named_imports.put(p.allocator, namespace_ref, js_ast.NamedImport{
|
||||
.alias_is_star = false,
|
||||
.alias = name,
|
||||
.alias_loc = loc,
|
||||
.namespace_ref = Ref.None,
|
||||
.import_record_index = import_record_index,
|
||||
}));
|
||||
} else {
|
||||
bun.handleOom(p.named_imports.put(p.allocator, namespace_ref, js_ast.NamedImport{
|
||||
.alias_is_star = true,
|
||||
.alias = "",
|
||||
.alias_loc = loc,
|
||||
.namespace_ref = Ref.None,
|
||||
.import_record_index = import_record_index,
|
||||
}));
|
||||
bun.handleOom(p.import_items_for_namespace.put(p.allocator, namespace_ref, ImportItemForNamespaceMap.init(p.allocator)));
|
||||
}
|
||||
|
||||
const import_stmt = if (import_name) |name| blk: {
|
||||
var items = bun.handleOom(p.allocator.alloc(js_ast.ClauseItem, 1));
|
||||
items[0] = js_ast.ClauseItem{
|
||||
.alias = name,
|
||||
.alias_loc = loc,
|
||||
.name = LocRef{
|
||||
.loc = loc,
|
||||
.ref = namespace_ref,
|
||||
},
|
||||
.original_name = namespace_name,
|
||||
};
|
||||
|
||||
break :blk p.s(S.Import{
|
||||
.namespace_ref = Ref.None,
|
||||
.import_record_index = import_record_index,
|
||||
.items = items,
|
||||
.is_single_line = true,
|
||||
}, loc);
|
||||
} else p.s(S.Import{
|
||||
.namespace_ref = namespace_ref,
|
||||
.import_record_index = import_record_index,
|
||||
.star_name_loc = null,
|
||||
.is_single_line = true,
|
||||
}, loc);
|
||||
|
||||
bun.handleOom(p.eager_glob_import_stmts.append(p.allocator, import_stmt));
|
||||
|
||||
const namespace_expr = p.newExpr(E.Identifier{ .ref = namespace_ref }, loc);
|
||||
p.recordUsage(namespace_ref);
|
||||
const value_expr = namespace_expr;
|
||||
|
||||
properties[i] = .{
|
||||
.key = p.newExpr(E.String{ .data = file_path }, loc),
|
||||
.value = value_expr,
|
||||
};
|
||||
} else {
|
||||
const import_expr = p.newExpr(E.Import{
|
||||
.expr = p.newExpr(E.String{ .data = import_path }, loc),
|
||||
.options = if (with_attrs) |attrs| blk: {
|
||||
var with_props: []G.Property = bun.handleOom(p.allocator.alloc(G.Property, 1));
|
||||
with_props[0] = .{
|
||||
.key = p.newExpr(E.String{ .data = "with" }, loc),
|
||||
.value = p.newExpr(E.Object{ .properties = attrs.properties }, loc),
|
||||
};
|
||||
break :blk p.newExpr(E.Object{ .properties = G.Property.List.fromOwnedSlice(with_props) }, loc);
|
||||
} else Expr.empty,
|
||||
.import_record_index = import_record_index,
|
||||
}, loc);
|
||||
|
||||
const return_expr = if (import_name) |name| blk: {
|
||||
const m_ref: Ref = bun.handleOom(p.newSymbol(.other, "m"));
|
||||
|
||||
var arrow_stmts: []Stmt = bun.handleOom(p.allocator.alloc(Stmt, 1));
|
||||
arrow_stmts[0] = p.s(S.Return{ .value = p.newExpr(E.Dot{
|
||||
.target = p.newExpr(E.Identifier{ .ref = m_ref }, loc),
|
||||
.name = name,
|
||||
.name_loc = loc,
|
||||
}, loc) }, loc);
|
||||
|
||||
var arrow_args: []G.Arg = bun.handleOom(p.allocator.alloc(G.Arg, 1));
|
||||
arrow_args[0] = .{
|
||||
.binding = p.b(B.Identifier{ .ref = m_ref }, logger.Loc.Empty),
|
||||
};
|
||||
|
||||
const arrow_fn = p.newExpr(E.Arrow{
|
||||
.args = arrow_args,
|
||||
.body = .{ .loc = loc, .stmts = arrow_stmts },
|
||||
.prefer_expr = true,
|
||||
}, loc);
|
||||
|
||||
break :blk p.newExpr(E.Call{
|
||||
.target = p.newExpr(E.Dot{
|
||||
.target = import_expr,
|
||||
.name = "then",
|
||||
.name_loc = loc,
|
||||
}, loc),
|
||||
.args = bun.handleOom(ExprNodeList.fromSlice(p.allocator, &.{arrow_fn})),
|
||||
}, loc);
|
||||
} else import_expr;
|
||||
|
||||
var outer_stmts: []Stmt = bun.handleOom(p.allocator.alloc(Stmt, 1));
|
||||
outer_stmts[0] = p.s(S.Return{ .value = return_expr }, loc);
|
||||
|
||||
properties[i] = .{
|
||||
.key = p.newExpr(E.String{ .data = file_path }, loc),
|
||||
.value = p.newExpr(E.Arrow{
|
||||
.args = &.{},
|
||||
.body = .{ .loc = loc, .stmts = outer_stmts },
|
||||
.prefer_expr = true,
|
||||
}, loc),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return p.newExpr(E.Object{
|
||||
.properties = G.Property.List.fromOwnedSlice(properties),
|
||||
}, loc);
|
||||
}
|
||||
|
||||
const ReactRefreshExportKind = enum { named, default };
|
||||
|
||||
pub fn handleReactRefreshRegister(p: *P, stmts: *ListManaged(Stmt), original_name: []const u8, ref: Ref, export_kind: ReactRefreshExportKind) !void {
|
||||
@@ -6239,6 +6547,19 @@ pub fn NewParser_(
|
||||
) !js_ast.Ast {
|
||||
const allocator = p.allocator;
|
||||
|
||||
// Inject eager glob imports as real import statements
|
||||
if (p.eager_glob_import_stmts.items.len > 0 and parts.items.len > 0) {
|
||||
// Mark as ESM since we're adding import statements
|
||||
p.has_es_module_syntax = true;
|
||||
|
||||
// Prepend import statements to the first part
|
||||
const old_stmts = parts.items[0].stmts;
|
||||
const new_stmts = try allocator.alloc(Stmt, p.eager_glob_import_stmts.items.len + old_stmts.len);
|
||||
@memcpy(new_stmts[0..p.eager_glob_import_stmts.items.len], p.eager_glob_import_stmts.items);
|
||||
@memcpy(new_stmts[p.eager_glob_import_stmts.items.len..], old_stmts);
|
||||
parts.items[0].stmts = new_stmts;
|
||||
}
|
||||
|
||||
// if (p.options.tree_shaking and p.options.features.trim_unused_imports) {
|
||||
// p.treeShake(&parts, false);
|
||||
// }
|
||||
@@ -6723,6 +7044,8 @@ var falseValueExpr = Expr.Data{ .e_boolean = E.Boolean{ .value = false } };
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const glob = @import("../glob.zig");
|
||||
|
||||
const Define = @import("../defines.zig").Define;
|
||||
const DefineData = @import("../defines.zig").DefineData;
|
||||
|
||||
|
||||
@@ -869,6 +869,7 @@ pub const SideEffects = enum(u1) {
|
||||
.module_exports,
|
||||
.resolved_specifier_string,
|
||||
.hot_data,
|
||||
.import_meta_glob,
|
||||
=> {},
|
||||
.hot_accept,
|
||||
.hot_accept_visited,
|
||||
|
||||
@@ -410,6 +410,12 @@ pub fn AstMaybe(
|
||||
}, .loc = loc };
|
||||
}
|
||||
|
||||
if (strings.eqlComptime(name, "glob")) {
|
||||
return .{ .data = .{
|
||||
.e_special = .import_meta_glob,
|
||||
}, .loc = loc };
|
||||
}
|
||||
|
||||
// Inline import.meta properties for Bake
|
||||
if (p.options.framework != null) {
|
||||
if (strings.eqlComptime(name, "dir") or strings.eqlComptime(name, "dirname")) {
|
||||
|
||||
@@ -1358,6 +1358,9 @@ pub fn VisitExpr(
|
||||
if (!p.options.features.hot_module_reloading)
|
||||
return .{ .data = .e_undefined, .loc = expr.loc };
|
||||
},
|
||||
.import_meta_glob => {
|
||||
return p.handleImportMetaGlobCall(e_, expr.loc);
|
||||
},
|
||||
else => {},
|
||||
};
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
/// Version 14: Updated global defines table list.
|
||||
/// Version 15: Updated global defines table list.
|
||||
/// Version 16: Added typeof undefined minification optimization.
|
||||
const expected_version = 16;
|
||||
/// Version 17: Added import.meta.glob support.
|
||||
const expected_version = 17;
|
||||
|
||||
const debug = Output.scoped(.cache, .visible);
|
||||
const MINIMUM_CACHE_SIZE = 50 * 1024;
|
||||
|
||||
@@ -2462,7 +2462,16 @@ pub const BundleV2 = struct {
|
||||
existing.value_ptr.* = source_index.get();
|
||||
out_source_index = source_index;
|
||||
this.graph.ast.append(this.allocator(), JSAst.empty) catch unreachable;
|
||||
const loader = path.loader(&this.transpiler.options.loaders) orelse options.Loader.file;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
this.graph.input_files.append(this.allocator(), .{
|
||||
.source = .{
|
||||
|
||||
@@ -1797,9 +1797,14 @@ fn NewPrinter(
|
||||
p.printStringLiteralUTF8(path.pretty, false);
|
||||
}
|
||||
|
||||
// Only print import options if the loader hasn't already been applied
|
||||
// When bundling with a loader applied, the transformation has already happened
|
||||
// so we don't need the import options anymore
|
||||
if (!import_options.isMissing()) {
|
||||
p.printWhitespacer(ws(", "));
|
||||
p.printExpr(import_options, .comma, .{});
|
||||
if (!p.options.bundling or record.loader == null) {
|
||||
p.printWhitespacer(ws(", "));
|
||||
p.printExpr(import_options, .comma, .{});
|
||||
}
|
||||
}
|
||||
|
||||
p.print(")");
|
||||
@@ -2091,6 +2096,10 @@ fn NewPrinter(
|
||||
bun.debugAssert(p.options.module_type == .internal_bake_dev);
|
||||
p.printStringLiteralUTF8(p.importRecord(index.get()).path.pretty, true);
|
||||
},
|
||||
.import_meta_glob => {
|
||||
// This should not reach the printer - it should be transformed in the parser
|
||||
p.print("(function() { throw new Error('import.meta.glob was not transformed at build time'); })");
|
||||
},
|
||||
},
|
||||
|
||||
.e_commonjs_export_identifier => |id| {
|
||||
|
||||
@@ -65,6 +65,64 @@ describe("bundler", async () => {
|
||||
},
|
||||
run: { stdout: '{"hello":"world"}' },
|
||||
});
|
||||
|
||||
itBundled("bun/loader-text-js-file", {
|
||||
target,
|
||||
files: {
|
||||
"/entry.ts": /* js */ `
|
||||
import hello from './hello.js' with {type: "text"};
|
||||
console.write(hello);
|
||||
`,
|
||||
"/hello.js": `console.log("This should not be executed!");
|
||||
export default "Hello from JS";`,
|
||||
},
|
||||
run: { stdout: `console.log("This should not be executed!");\nexport default "Hello from JS";` },
|
||||
});
|
||||
|
||||
itBundled("bun/loader-text-dynamic-import", {
|
||||
target,
|
||||
files: {
|
||||
"/entry.ts": /* js */ `
|
||||
const mod = await import('./script.js', { with: { type: 'text' } });
|
||||
console.write(mod.default);
|
||||
`,
|
||||
"/script.js": `console.log("This should not run!");
|
||||
export const data = "test";`,
|
||||
},
|
||||
run: { stdout: `console.log("This should not run!");\nexport const data = "test";` },
|
||||
});
|
||||
|
||||
// Verify that without type: "text", JS files are executed normally
|
||||
itBundled("bun/loader-js-normal-execution", {
|
||||
target,
|
||||
files: {
|
||||
"/entry.ts": /* js */ `
|
||||
import { message } from './module.js';
|
||||
console.write(message);
|
||||
`,
|
||||
"/module.js": `export const message = "executed";`,
|
||||
},
|
||||
run: { stdout: "executed" },
|
||||
});
|
||||
|
||||
itBundled("bun/loader-text-splitting-strips-options", {
|
||||
target,
|
||||
splitting: true,
|
||||
outdir: "/out",
|
||||
files: {
|
||||
"/entry.ts": /* js */ `
|
||||
const mod = await import('./data.js', { with: { type: 'text' } });
|
||||
console.write(mod.default);
|
||||
`,
|
||||
"/data.js": `export default "test data";`,
|
||||
},
|
||||
run: { stdout: `export default "test data";` },
|
||||
onAfterBundle(api) {
|
||||
const entryFile = api.readFile("/out/entry.js");
|
||||
expect(entryFile).not.toContain("with:");
|
||||
expect(entryFile).not.toContain("{ type:");
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
34
test/integration/bun-types/fixture/import-meta.ts
Normal file
34
test/integration/bun-types/fixture/import-meta.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { expectType } from "./utilities";
|
||||
|
||||
const fixtures = import.meta.glob("./**/*.ts");
|
||||
for (const [file, importFn] of Object.entries(fixtures)) {
|
||||
console.log(file, await importFn());
|
||||
}
|
||||
expectType<Record<string, () => Promise<any>>>(fixtures);
|
||||
|
||||
const http = import.meta.glob(["*.html", "**/*.html"], { with: { type: "text" } });
|
||||
expectType<Record<string, () => Promise<any>>>(http);
|
||||
|
||||
const tests = import.meta.glob("*.test.ts", { base: "../", eager: false });
|
||||
expectType<Record<string, () => Promise<any>>>(tests);
|
||||
|
||||
const jsons = import.meta.glob<false, Record<string, number>>("*.json");
|
||||
expectType<Record<string, () => Promise<Record<string, number>>>>(jsons);
|
||||
|
||||
const jsons2 = import.meta.glob<Record<string, number>>("*.json");
|
||||
expectType<Record<string, () => Promise<Record<string, number>>>>(jsons2);
|
||||
|
||||
// @ts-expect-error: right now bun doesn't support eager
|
||||
const eagerJsons = import.meta.glob<Record<string, number>>("*.json", { eager: true });
|
||||
// @ts-expect-error: right now bun doesn't support eager
|
||||
expectType<Record<string, Record<string, number>>>(eagerJsons);
|
||||
|
||||
expectType<string>(import.meta.dir);
|
||||
expectType<string>(import.meta.dirname);
|
||||
expectType<string>(import.meta.file);
|
||||
expectType<string>(import.meta.path);
|
||||
expectType<string>(import.meta.url);
|
||||
expectType<boolean>(import.meta.main);
|
||||
expectType<string>(import.meta.resolve("zod"));
|
||||
expectType<Record<string, () => Promise<any>>>(import.meta.glob("*"));
|
||||
expectType<Object>(import.meta.hot);
|
||||
@@ -392,6 +392,7 @@ exports[`fast-glob e2e tests patterns regular cwd **/{nested,file.md}/*: **/{nes
|
||||
|
||||
exports[`fast-glob e2e tests patterns regular relative cwd ./*: ./* 1`] = `
|
||||
[
|
||||
"./import-meta-glob.test.ts",
|
||||
"./leak.test.ts",
|
||||
"./match.test.ts",
|
||||
"./proto.test.ts",
|
||||
|
||||
903
test/js/bun/glob/import-meta-glob.test.ts
Normal file
903
test/js/bun/glob/import-meta-glob.test.ts
Normal file
@@ -0,0 +1,903 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDir, tempDirWithFiles } from "harness";
|
||||
import path from "node:path";
|
||||
|
||||
describe("import.meta.glob", () => {
|
||||
describe("runtime behavior", () => {
|
||||
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("import option extracts specific named export", async () => {
|
||||
const dir = tempDirWithFiles("import-glob-named", {
|
||||
"index.js": `
|
||||
const defaultModules = import.meta.glob('./routes/*.js', { import: 'default' });
|
||||
const namedModules = import.meta.glob('./routes/*.js', { import: 'handler' });
|
||||
|
||||
console.log('DEFAULT_MODULES:');
|
||||
for (const [path, loader] of Object.entries(defaultModules)) {
|
||||
const result = await loader();
|
||||
console.log(path + ':', result);
|
||||
}
|
||||
|
||||
console.log('NAMED_MODULES:');
|
||||
for (const [path, loader] of Object.entries(namedModules)) {
|
||||
const result = await loader();
|
||||
console.log(path + ':', result);
|
||||
}
|
||||
`,
|
||||
"routes/home.js": `
|
||||
export default "home-route";
|
||||
export const handler = "home-handler";
|
||||
export const unused = "should-not-see-this";
|
||||
`,
|
||||
"routes/about.js": `
|
||||
export default "about-route";
|
||||
export const handler = "about-handler";
|
||||
export const unused = "should-not-see-this";
|
||||
`,
|
||||
});
|
||||
|
||||
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(lines).toContain("./routes/about.js: about-route");
|
||||
expect(lines).toContain("./routes/home.js: home-route");
|
||||
expect(lines).toContain("./routes/about.js: about-handler");
|
||||
expect(lines).toContain("./routes/home.js: home-handler");
|
||||
expect(stdout).not.toContain("should-not-see-this");
|
||||
});
|
||||
|
||||
test("options are passed through (query and with)", async () => {
|
||||
const dir = tempDirWithFiles("import-glob-options", {
|
||||
"index.js": `
|
||||
const withType = import.meta.glob('./src/*.ts', { with: { type: 'text' } });
|
||||
const withQuery = import.meta.glob('./data/*.js', { query: '?inline' });
|
||||
console.log('WITH_TYPE:', Object.keys(withType).length);
|
||||
console.log('WITH_QUERY:', Object.keys(withQuery).length);
|
||||
`,
|
||||
"src/helper.ts": `export function helper() { return "typescript"; }`,
|
||||
"data/config.js": `export const config = { version: "1.0" };`,
|
||||
});
|
||||
|
||||
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(lines[0]).toBe("WITH_TYPE: 1");
|
||||
expect(lines[1]).toBe("WITH_QUERY: 1");
|
||||
});
|
||||
|
||||
test("eager mode loads modules synchronously", async () => {
|
||||
const dir = tempDirWithFiles("import-glob-eager", {
|
||||
"index.js": `
|
||||
const modules = import.meta.glob('./modules/*.js', { eager: true });
|
||||
console.log('TYPE:', typeof modules['./modules/a.js']);
|
||||
console.log('NAME:', modules['./modules/a.js'].name);
|
||||
console.log('KEYS:', JSON.stringify(Object.keys(modules).sort()));
|
||||
`,
|
||||
"modules/a.js": `export const name = "module-a";`,
|
||||
"modules/b.js": `export const name = "module-b";`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "index.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
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(lines[0]).toBe("TYPE: object");
|
||||
expect(lines[1]).toBe("NAME: module-a");
|
||||
expect(lines[2]).toBe('KEYS: ["./modules/a.js","./modules/b.js"]');
|
||||
});
|
||||
|
||||
test("supports recursive ** and multiple patterns", async () => {
|
||||
const dir = tempDirWithFiles("import-glob-patterns", {
|
||||
"index.js": `
|
||||
const recursive = import.meta.glob('./src/**/*.js');
|
||||
const multiple = import.meta.glob(['./lib/*.js', './config/*.js']);
|
||||
const negativeTest = import.meta.glob('./src/**/*.ts');
|
||||
const complexPattern = import.meta.glob('./src/**/[a-m]*.js');
|
||||
|
||||
console.log('RECURSIVE:', JSON.stringify(Object.keys(recursive).sort()));
|
||||
console.log('MULTIPLE:', JSON.stringify(Object.keys(multiple).sort()));
|
||||
console.log('NEGATIVE_TEST:', JSON.stringify(Object.keys(negativeTest)));
|
||||
console.log('COMPLEX_PATTERN:', JSON.stringify(Object.keys(complexPattern).sort()));
|
||||
`,
|
||||
"src/main.js": `export default "main";`,
|
||||
"src/lib/util.js": `export default "util";`,
|
||||
"src/components/button.js": `export default "button";`,
|
||||
"lib/helper.js": `export default "helper";`,
|
||||
"config/app.js": `export default "app";`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "index.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
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].split(": ")[1])).toEqual([
|
||||
"./src/components/button.js",
|
||||
"./src/lib/util.js",
|
||||
"./src/main.js",
|
||||
]);
|
||||
expect(JSON.parse(lines[1].split(": ")[1])).toEqual(["./config/app.js", "./lib/helper.js"]);
|
||||
expect(JSON.parse(lines[2].split(": ")[1])).toEqual([]);
|
||||
expect(JSON.parse(lines[3].split(": ")[1])).toEqual(["./src/components/button.js", "./src/main.js"]);
|
||||
});
|
||||
|
||||
test("negative patterns exclude files from results", async () => {
|
||||
const dir = tempDirWithFiles("import-glob-negative", {
|
||||
"index.js": `
|
||||
const all = import.meta.glob('./dir/*.js');
|
||||
const filtered = import.meta.glob(['./dir/*.js', '!**/bar.js']);
|
||||
|
||||
console.log('ALL:', JSON.stringify(Object.keys(all).sort()));
|
||||
console.log('FILTERED:', JSON.stringify(Object.keys(filtered).sort()));
|
||||
`,
|
||||
"dir/foo.js": `export default "foo";`,
|
||||
"dir/bar.js": `export default "bar";`,
|
||||
"dir/baz.js": `export default "baz";`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "index.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
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].split(": ")[1])).toEqual(["./dir/bar.js", "./dir/baz.js", "./dir/foo.js"]);
|
||||
expect(JSON.parse(lines[1].split(": ")[1])).toEqual(["./dir/baz.js", "./dir/foo.js"]);
|
||||
});
|
||||
|
||||
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("dynamic imports work when functions are called", async () => {
|
||||
const dir = tempDirWithFiles("import-glob-dynamic", {
|
||||
"index.js": `
|
||||
const modules = import.meta.glob('./modules/*.js');
|
||||
const loader = modules['./modules/test.js'];
|
||||
|
||||
if (loader) {
|
||||
const mod = await loader();
|
||||
console.log('SUCCESS:', mod.message);
|
||||
}
|
||||
`,
|
||||
"modules/test.js": `export const message = "Hello from module!";`,
|
||||
});
|
||||
|
||||
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("");
|
||||
expect(stdout.trim()).toBe("SUCCESS: Hello from module!");
|
||||
});
|
||||
|
||||
test("dynamic patterns work at runtime", async () => {
|
||||
const dir = tempDirWithFiles("import-glob-runtime", {
|
||||
"index.js": `
|
||||
const pattern = './modules/*.js';
|
||||
const modules = import.meta.glob(pattern);
|
||||
console.log('COUNT:', Object.keys(modules).length);
|
||||
`,
|
||||
"modules/test.js": `export const name = "test";`,
|
||||
});
|
||||
|
||||
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("");
|
||||
expect(stdout.trim()).toBe("COUNT: 1");
|
||||
});
|
||||
|
||||
test("error handling and edge cases", async () => {
|
||||
const dir = tempDirWithFiles("import-glob-errors", {
|
||||
"index.js": `
|
||||
const withQuery = import.meta.glob('./data/**/*.json', { query: '?raw' });
|
||||
console.log('WITH_QUERY_PATHS:', Object.keys(withQuery).sort());
|
||||
|
||||
const complex = import.meta.glob('./{data,config}/**/*.{js,json}');
|
||||
console.log('COMPLEX_COUNT:', Object.keys(complex).length);
|
||||
|
||||
const keys = Object.keys(withQuery);
|
||||
const key = keys.find(k => k.includes('config.json'));
|
||||
console.log('ACTUAL_KEY:', key);
|
||||
if (key && withQuery[key]) {
|
||||
const first = await withQuery[key]();
|
||||
const second = await withQuery[key]();
|
||||
console.log('SAME_INSTANCE:', first === second);
|
||||
}
|
||||
`,
|
||||
"data/config.json": `{"version": "1.0"}`,
|
||||
"data/nested/deep.json": `{"level": "deep"}`,
|
||||
"config/app.js": `export default { name: "app" };`,
|
||||
"config/settings.json": `{"theme": "dark"}`,
|
||||
});
|
||||
|
||||
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(lines[0]).toContain("./data/config.json");
|
||||
expect(lines[0]).toContain("./data/nested/deep.json");
|
||||
expect(lines[1]).toBe("COMPLEX_COUNT: 4");
|
||||
expect(lines[2]).toContain("ACTUAL_KEY:");
|
||||
expect(lines[3]).toBe("SAME_INSTANCE: true");
|
||||
});
|
||||
|
||||
test("base option prepends base path to imports but not keys", async () => {
|
||||
using dir = tempDir("import-glob-base", {
|
||||
"index.js": `
|
||||
const modules = import.meta.glob('./modules/*.js', { base: './src' });
|
||||
const moduleKeys = Object.keys(modules).sort();
|
||||
|
||||
console.log('KEYS:', JSON.stringify(moduleKeys));
|
||||
|
||||
for (const [key, loader] of Object.entries(modules)) {
|
||||
console.log('KEY:', key);
|
||||
const result = await loader();
|
||||
console.log('VALUE:', result.default);
|
||||
}
|
||||
`,
|
||||
"src/modules/foo.js": `export default "foo-value";`,
|
||||
"src/modules/bar.js": `export default "bar-value";`,
|
||||
"modules/baz.js": `export default "baz-should-not-match";`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "index.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
if (exitCode !== 0) {
|
||||
console.log("STDERR:", stderr);
|
||||
console.log("STDOUT:", stdout);
|
||||
}
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
const lines = stdout.trim().split("\n");
|
||||
|
||||
expect(lines[0]).toBe('KEYS: ["./modules/bar.js","./modules/foo.js"]');
|
||||
|
||||
expect(lines).toContain("KEY: ./modules/foo.js");
|
||||
expect(lines).toContain("VALUE: foo-value");
|
||||
expect(lines).toContain("KEY: ./modules/bar.js");
|
||||
expect(lines).toContain("VALUE: bar-value");
|
||||
});
|
||||
|
||||
test("base option with relative path upward", async () => {
|
||||
using dir = tempDir("import-glob-base-relative", {
|
||||
"src/index.js": `
|
||||
const modules = import.meta.glob('./lib/*.js', { base: '../base' });
|
||||
|
||||
console.log('KEYS:', JSON.stringify(Object.keys(modules).sort()));
|
||||
|
||||
for (const [key, loader] of Object.entries(modules)) {
|
||||
const result = await loader();
|
||||
console.log(key + ':', result.default);
|
||||
}
|
||||
`,
|
||||
"base/lib/util.js": `export default "util-module";`,
|
||||
"base/lib/helper.js": `export default "helper-module";`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "index.js"],
|
||||
env: bunEnv,
|
||||
cwd: path.join(String(dir), "src"),
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
if (exitCode !== 0 || !stdout.includes("KEYS:")) {
|
||||
console.log("STDERR:", stderr);
|
||||
console.log("STDOUT:", stdout);
|
||||
}
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
const lines = stdout.trim().split("\n");
|
||||
|
||||
expect(lines[0]).toBe('KEYS: ["./lib/helper.js","./lib/util.js"]');
|
||||
expect(lines).toContain("./lib/util.js: util-module");
|
||||
expect(lines).toContain("./lib/helper.js: helper-module");
|
||||
});
|
||||
|
||||
test("base option with parent directory", async () => {
|
||||
using dir = tempDir("import-glob-base-parent", {
|
||||
"src/index.js": `
|
||||
const modules = import.meta.glob('./components/*.js', { base: '../shared' });
|
||||
console.log('KEYS:', JSON.stringify(Object.keys(modules).sort()));
|
||||
|
||||
for (const [key, loader] of Object.entries(modules)) {
|
||||
const result = await loader();
|
||||
console.log(key + ':', result.name);
|
||||
}
|
||||
`,
|
||||
"shared/components/button.js": `export const name = "button-component";`,
|
||||
"shared/components/input.js": `export const name = "input-component";`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "index.js"],
|
||||
env: bunEnv,
|
||||
cwd: path.join(String(dir), "src"),
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
const lines = stdout.trim().split("\n");
|
||||
|
||||
expect(lines[0]).toBe('KEYS: ["./components/button.js","./components/input.js"]');
|
||||
expect(lines).toContain("./components/button.js: button-component");
|
||||
expect(lines).toContain("./components/input.js: input-component");
|
||||
});
|
||||
});
|
||||
|
||||
describe("bundler behavior", () => {
|
||||
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("bundled code works with different glob modes", async () => {
|
||||
const dir = tempDirWithFiles("import-glob-bundle-modes", {
|
||||
"index.js": `
|
||||
const regular = import.meta.glob('./lib/*.js');
|
||||
const eager = import.meta.glob('./lib/*.js', { eager: true });
|
||||
const withImport = import.meta.glob('./lib/*.js', { import: 'name' });
|
||||
|
||||
console.log('REGULAR:', typeof Object.values(regular)[0]);
|
||||
console.log('EAGER:', typeof Object.values(eager)[0]);
|
||||
console.log('IMPORT:', typeof Object.values(withImport)[0]);
|
||||
`,
|
||||
"lib/util.js": `export const name = "util";`,
|
||||
});
|
||||
|
||||
await using buildProc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "index.js", "--outfile", "dist/bundle.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [buildStdout, buildStderr, buildExitCode] = await Promise.all([
|
||||
new Response(buildProc.stdout).text(),
|
||||
new Response(buildProc.stderr).text(),
|
||||
buildProc.exited,
|
||||
]);
|
||||
|
||||
expect(buildExitCode).toBe(0);
|
||||
expect(buildStderr).toBe("");
|
||||
|
||||
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("REGULAR: function");
|
||||
expect(lines[1]).toBe("EAGER: object");
|
||||
expect(lines[2]).toBe("IMPORT: function");
|
||||
});
|
||||
|
||||
test("bundled code maintains correct file paths", async () => {
|
||||
const dir = tempDirWithFiles("import-glob-bundle-paths", {
|
||||
"index.js": `
|
||||
const modules = import.meta.glob('./src/**/*.js');
|
||||
const paths = Object.keys(modules).sort();
|
||||
console.log('PATHS:', JSON.stringify(paths));
|
||||
console.log('COUNT:', paths.length);
|
||||
`,
|
||||
"src/main.js": `export default "main";`,
|
||||
"src/lib/util.js": `export default "util";`,
|
||||
"src/lib/helper.js": `export default "helper";`,
|
||||
});
|
||||
|
||||
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('PATHS: ["./src/lib/helper.js","./src/lib/util.js","./src/main.js"]');
|
||||
expect(lines[1]).toBe("COUNT: 3");
|
||||
});
|
||||
|
||||
test("bundled code works with --splitting", async () => {
|
||||
const dir = tempDirWithFiles("import-glob-splitting", {
|
||||
"entry1.js": `
|
||||
const modules = import.meta.glob('./shared/*.js');
|
||||
export function getModules() {
|
||||
return modules;
|
||||
}
|
||||
console.log('ENTRY1_MODULES:', Object.keys(modules).length);
|
||||
`,
|
||||
"entry2.js": `
|
||||
const modules = import.meta.glob('./shared/*.js');
|
||||
export function getModules() {
|
||||
return modules;
|
||||
}
|
||||
console.log('ENTRY2_MODULES:', Object.keys(modules).length);
|
||||
`,
|
||||
"shared/util.js": `export default "util"; export const name = "util";`,
|
||||
"shared/helper.js": `export default "helper"; export const name = "helper";`,
|
||||
"test.js": `
|
||||
import { getModules as getModules1 } from './dist/entry1.js';
|
||||
import { getModules as getModules2 } from './dist/entry2.js';
|
||||
|
||||
const modules1 = getModules1();
|
||||
const modules2 = getModules2();
|
||||
|
||||
console.log('TEST_MODULES1:', Object.keys(modules1).length);
|
||||
console.log('TEST_MODULES2:', Object.keys(modules2).length);
|
||||
console.log('SAME_KEYS:', JSON.stringify(Object.keys(modules1).sort()) === JSON.stringify(Object.keys(modules2).sort()));
|
||||
`,
|
||||
});
|
||||
|
||||
await using buildProc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "entry1.js", "entry2.js", "--splitting", "--outdir", "dist", "--target=bun"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
const [buildStdout, buildStderr, buildExitCode] = await Promise.all([
|
||||
new Response(buildProc.stdout).text(),
|
||||
new Response(buildProc.stderr).text(),
|
||||
buildProc.exited,
|
||||
]);
|
||||
|
||||
expect(buildExitCode).toBe(0);
|
||||
expect(buildStderr).toBe("");
|
||||
|
||||
await using runProc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.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).toContain("ENTRY1_MODULES: 2");
|
||||
expect(lines).toContain("ENTRY2_MODULES: 2");
|
||||
expect(lines).toContain("TEST_MODULES1: 2");
|
||||
expect(lines).toContain("TEST_MODULES2: 2");
|
||||
expect(lines).toContain("SAME_KEYS: true");
|
||||
});
|
||||
|
||||
test("--splitting works with import option", async () => {
|
||||
const dir = tempDirWithFiles("import-glob-splitting-import", {
|
||||
"entry.js": `
|
||||
const modules = import.meta.glob('./lib/*.js', { import: 'name' });
|
||||
export async function loadNames() {
|
||||
const names = [];
|
||||
for (const [path, loader] of Object.entries(modules)) {
|
||||
const name = await loader();
|
||||
names.push(name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
`,
|
||||
"lib/foo.js": `export const name = "foo"; export default "default-foo";`,
|
||||
"lib/bar.js": `export const name = "bar"; export default "default-bar";`,
|
||||
"test.js": `
|
||||
import { loadNames } from './dist/entry.js';
|
||||
loadNames().then(names => {
|
||||
console.log('NAMES:', JSON.stringify(names.sort()));
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
await using buildProc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "entry.js", "--splitting", "--outdir", "dist", "--target=bun"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
await buildProc.exited;
|
||||
|
||||
await using runProc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.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("");
|
||||
expect(stdout.trim()).toContain('NAMES: ["bar","foo"]');
|
||||
});
|
||||
|
||||
test("--splitting works with 'with' option for JS files as text", async () => {
|
||||
const dir = tempDirWithFiles("import-glob-splitting-with", {
|
||||
"entry.js": `
|
||||
const modules = import.meta.glob('./assets/*', { with: { type: 'text' } });
|
||||
export async function loadTexts() {
|
||||
const texts = {};
|
||||
for (const [path, loader] of Object.entries(modules)) {
|
||||
const mod = await loader();
|
||||
texts[path] = mod.default || mod;
|
||||
}
|
||||
return texts;
|
||||
}
|
||||
`,
|
||||
"assets/hello.txt": `Hello World`,
|
||||
"assets/goodbye.txt": `Goodbye World`,
|
||||
"assets/script.js": `console.log("This should be text, not executed!"); export default "js-module";`,
|
||||
"test.js": `
|
||||
import { loadTexts } from './dist/entry.js';
|
||||
loadTexts().then(texts => {
|
||||
console.log('COUNT:', Object.keys(texts).length);
|
||||
console.log('SCRIPT_TEXT:', texts['./assets/script.js']);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
await using buildProc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "entry.js", "--splitting", "--outdir", "dist", "--target=bun"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
await buildProc.exited;
|
||||
|
||||
await using runProc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.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("");
|
||||
expect(stdout).toContain("COUNT: 3");
|
||||
expect(stdout).toContain("SCRIPT_TEXT:");
|
||||
expect(stdout).toContain('console.log("This should be text, not executed!");');
|
||||
expect(stdout).toContain('export default "js-module";');
|
||||
const lines = stdout.split("\n");
|
||||
const shouldNotExecuteLine = lines.findIndex(line => line === "This should be text, not executed!");
|
||||
expect(shouldNotExecuteLine).toBe(-1);
|
||||
});
|
||||
|
||||
test("base option works with bundler", async () => {
|
||||
using dir = tempDir("import-glob-bundler-base", {
|
||||
"index.js": `
|
||||
const modules = import.meta.glob('./modules/*.js', { base: './src' });
|
||||
const moduleKeys = Object.keys(modules).sort();
|
||||
|
||||
console.log('KEYS:', JSON.stringify(moduleKeys));
|
||||
|
||||
for (const [key, loader] of Object.entries(modules)) {
|
||||
const result = await loader();
|
||||
console.log(\`\${key}: \${result.default}\`);
|
||||
}
|
||||
`,
|
||||
"src/modules/foo.js": `export default "foo-bundled";`,
|
||||
"src/modules/bar.js": `export default "bar-bundled";`,
|
||||
"modules/baz.js": `export default "baz-should-not-match";`,
|
||||
});
|
||||
|
||||
await using buildProc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "index.js", "--outfile", "bundle.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const buildExitCode = await buildProc.exited;
|
||||
expect(buildExitCode).toBe(0);
|
||||
|
||||
await using runProc = Bun.spawn({
|
||||
cmd: [bunExe(), "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("");
|
||||
expect(stdout).toBe(`KEYS: ["./modules/bar.js","./modules/foo.js"]
|
||||
./modules/bar.js: bar-bundled
|
||||
./modules/foo.js: foo-bundled
|
||||
`);
|
||||
});
|
||||
|
||||
test("base option with parent directory and bundler", async () => {
|
||||
using dir = tempDir("import-glob-bundler-base-parent", {
|
||||
"project/index.js": `
|
||||
const modules = import.meta.glob('./*.js', { base: '../' });
|
||||
const moduleKeys = Object.keys(modules).sort();
|
||||
|
||||
console.log('KEYS:', JSON.stringify(moduleKeys));
|
||||
|
||||
for (const [key, loader] of Object.entries(modules)) {
|
||||
const result = await loader();
|
||||
console.log(\`\${key}: \${result.default}\`);
|
||||
}
|
||||
`,
|
||||
"module1.js": `export default "module1-value";`,
|
||||
"module2.js": `export default "module2-value";`,
|
||||
"project/local.js": `export default "should-not-match";`,
|
||||
});
|
||||
|
||||
await using buildProc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "project/index.js", "--outfile", "bundle.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const buildExitCode = await buildProc.exited;
|
||||
expect(buildExitCode).toBe(0);
|
||||
|
||||
await using runProc = Bun.spawn({
|
||||
cmd: [bunExe(), "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("");
|
||||
expect(stdout).toBe(`KEYS: ["./module1.js","./module2.js"]
|
||||
./module1.js: module1-value
|
||||
./module2.js: module2-value
|
||||
`);
|
||||
});
|
||||
|
||||
test("negative patterns work with bundler", async () => {
|
||||
const dir = tempDirWithFiles("import-glob-bundler-negative", {
|
||||
"index.js": `
|
||||
const all = import.meta.glob('./dir/*.js');
|
||||
const filtered = import.meta.glob(['./dir/*.js', '!**/bar.js']);
|
||||
|
||||
console.log('ALL:', JSON.stringify(Object.keys(all).sort()));
|
||||
console.log('FILTERED:', JSON.stringify(Object.keys(filtered).sort()));
|
||||
|
||||
for (const [key, loader] of Object.entries(filtered)) {
|
||||
const result = await loader();
|
||||
console.log(\`\${key}: \${result.default}\`);
|
||||
}
|
||||
`,
|
||||
"dir/foo.js": `export default "foo";`,
|
||||
"dir/bar.js": `export default "bar";`,
|
||||
"dir/baz.js": `export default "baz";`,
|
||||
});
|
||||
|
||||
await using buildProc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "index.js", "--outfile", "bundle.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
const buildExitCode = await buildProc.exited;
|
||||
expect(buildExitCode).toBe(0);
|
||||
|
||||
expect(await Bun.file(path.join(dir, "bundle.js")).text()).not.toContain("glob");
|
||||
|
||||
await using runProc = Bun.spawn({
|
||||
cmd: [bunExe(), "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(JSON.parse(lines[0].split(": ")[1])).toEqual(["./dir/bar.js", "./dir/baz.js", "./dir/foo.js"]);
|
||||
expect(JSON.parse(lines[1].split(": ")[1])).toEqual(["./dir/baz.js", "./dir/foo.js"]);
|
||||
expect(lines).toContain("./dir/foo.js: foo");
|
||||
expect(lines).toContain("./dir/baz.js: baz");
|
||||
expect(stdout).not.toContain("./dir/bar.js: bar");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -146,3 +146,5 @@ vendor/elysia/test/validator/body.test.ts
|
||||
vendor/elysia/test/ws/message.test.ts
|
||||
|
||||
test/js/node/test/parallel/test-worker-abort-on-uncaught-exception.js
|
||||
test/js/bun/glob/import-meta-glob.test.ts
|
||||
test/bundler/bundler_loader.test.ts
|
||||
|
||||
Reference in New Issue
Block a user