diff --git a/src/bundler/transpiler.rs b/src/bundler/transpiler.rs
index 3729996fa70e..08182e5cc2db 100644
--- a/src/bundler/transpiler.rs
+++ b/src/bundler/transpiler.rs
@@ -2057,38 +2057,44 @@ fn parse_data_loader<'a>(
}
decls.truncate(count);
- let stmt0 = bun_ast::Stmt::alloc(
- bun_ast::S::Local {
- decls: bun_ast::G::DeclList::move_from_list(decls),
- kind: bun_ast::S::Kind::KVar,
- ..Default::default()
- },
- bun_ast::Loc { start: 0 },
- );
- let stmt1 = bun_ast::Stmt::alloc(
- bun_ast::S::ExportClause {
- items: bun_ast::StoreSlice::new_mut(&mut export_clauses[..count]),
- is_single_line: false,
- },
- bun_ast::Loc { start: 0 },
- );
- let stmt2 = bun_ast::Stmt::alloc(
- bun_ast::S::ExportDefault {
- value: bun_ast::StmtOrExpr::Expr(expr),
- default_name: bun_ast::LocRef {
- loc: bun_ast::Loc::default(),
- ref_: bun_ast::Ref::NONE,
+ symbols.truncate(count);
+
+ // `print_decls` aborts on a `var` with no declarations (`{"default": 1}`).
+ if count > 0 {
+ let stmt0 = bun_ast::Stmt::alloc(
+ bun_ast::S::Local {
+ decls: bun_ast::G::DeclList::move_from_list(decls),
+ kind: bun_ast::S::Kind::KVar,
+ ..Default::default()
},
- },
- bun_ast::Loc { start: 0 },
- );
+ bun_ast::Loc { start: 0 },
+ );
+ let stmt1 = bun_ast::Stmt::alloc(
+ bun_ast::S::ExportClause {
+ items: bun_ast::StoreSlice::new_mut(&mut export_clauses[..count]),
+ is_single_line: false,
+ },
+ bun_ast::Loc { start: 0 },
+ );
+ let stmt2 = bun_ast::Stmt::alloc(
+ bun_ast::S::ExportDefault {
+ value: bun_ast::StmtOrExpr::Expr(expr),
+ default_name: bun_ast::LocRef {
+ loc: bun_ast::Loc::default(),
+ ref_: bun_ast::Ref::NONE,
+ },
+ },
+ bun_ast::Loc { start: 0 },
+ );
- let stmts =
- bun_ast::StoreSlice::new_mut(arena.alloc_slice_copy(&[stmt0, stmt1, stmt2]));
- break 'parts Box::new([bun_ast::Part {
- stmts,
- ..Default::default()
- }]);
+ let stmts = bun_ast::StoreSlice::new_mut(
+ arena.alloc_slice_copy(&[stmt0, stmt1, stmt2]),
+ );
+ break 'parts Box::new([bun_ast::Part {
+ stmts,
+ ..Default::default()
+ }]);
+ }
}
}
diff --git a/test/bundler/bundler_loader.test.ts b/test/bundler/bundler_loader.test.ts
index bab25c3a0726..7439c4d7efd1 100644
--- a/test/bundler/bundler_loader.test.ts
+++ b/test/bundler/bundler_loader.test.ts
@@ -370,6 +370,28 @@ describe("bundler", async () => {
},
});
+ // A "default" key gets no named export. When no key gets one, the module
+ // has no `var` statement: it is only `export default`.
+ const onlyDefaultKey = (ext: string, contents: string, value: unknown, loader?: Record<`.${string}`, Loader>) =>
+ itBundled(`bun/loader-${ext}-only-default-key-no-bundle`, {
+ target: "bun",
+ bundling: false,
+ entryPoints: [`/data.${ext}`],
+ loader,
+ files: {
+ [`/data.${ext}`]: contents,
+ "/check.js": `import * as data from "./out.js"; console.write(JSON.stringify(data));`,
+ },
+ run: { file: "/check.js", stdout: JSON.stringify({ default: { default: value } }) },
+ });
+ onlyDefaultKey("json", `{"default": 1}`, 1);
+ onlyDefaultKey("jsonc", `{"default": 1} // comment`, 1);
+ onlyDefaultKey("json5", `{default: 1}`, 1);
+ onlyDefaultKey("toml", `default = 1`, 1);
+ onlyDefaultKey("yaml", `default: 1`, 1);
+ // `bun build --no-bundle` gives ".xml" the file loader unless --loader names it.
+ onlyDefaultKey("xml", `1`, "1", { ".xml": "xml" });
+
// The CSS-modules lazy export builds its object through `E::Object::put`.
itBundled("bun/loader-css-module-proto-class-is-own-property", {
target: "bun",
diff --git a/test/bundler/expectBundled.ts b/test/bundler/expectBundled.ts
index 9c0e3d4d341d..bc2f6e4af6be 100644
--- a/test/bundler/expectBundled.ts
+++ b/test/bundler/expectBundled.ts
@@ -663,7 +663,7 @@ function expectBundled(
}
if (!ESBUILD && loader) {
const loaderValues = [...new Set(Object.values(loader))];
- const supportedLoaderTypes = ["js", "jsx", "ts", "tsx", "css", "json", "text", "file", "wtf", "toml"];
+ const supportedLoaderTypes = ["js", "jsx", "ts", "tsx", "css", "json", "text", "file", "wtf", "toml", "xml"];
const unsupportedLoaderTypes = loaderValues.filter(x => !supportedLoaderTypes.includes(x));
if (unsupportedLoaderTypes.length > 0) {
throw new UnsupportedOptionError(`loader '${unsupportedLoaderTypes.join("', '")}' not implemented in bun build`);
diff --git a/test/bundler/transpiler/transpiler.test.js b/test/bundler/transpiler/transpiler.test.js
index a4b6cd48696c..dd323f50906b 100644
--- a/test/bundler/transpiler/transpiler.test.js
+++ b/test/bundler/transpiler/transpiler.test.js
@@ -5256,6 +5256,31 @@ describe.concurrent("minify.identifiers on an empty source or a data loader", ()
});
});
+// The printer aborted the process on this input, so it runs in a subprocess.
+it.concurrent('a data loader source whose only key is "default" is only `export default`', async () => {
+ const result = await bunRun([
+ "-e",
+ `const transpiler = new Bun.Transpiler({ minify: { whitespace: true } });
+ const output = [];
+ for (const [loader, source] of [
+ ["json", '{"default": 1}'],
+ ["jsonc", '{"default": 1} // comment'],
+ ["json5", "{default: 1}"],
+ ["toml", "default = 1"],
+ ["yaml", "default: 1"],
+ ["xml", "1"],
+ ]) {
+ output.push([loader, transpiler.transformSync(source, loader), await transpiler.transform(source, loader)]);
+ }
+ console.log(JSON.stringify(output));`,
+ ]);
+ const number = "export default {default:1};";
+ const string = 'export default {default:"1"};';
+ const expected = ["json", "jsonc", "json5", "toml", "yaml"].map(loader => [loader, number, number]);
+ expected.push(["xml", string, string]);
+ expect(result).toEqual({ stdout: JSON.stringify(expected), stderr: "", exitCode: 0, signalCode: null });
+});
+
it("runtime transpiler stack overflows", async () => {
expect(async () => await import("./fixtures/lots-of-for-loop.js")).toThrow(`Maximum call stack size exceeded`);
});