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`); });