Writing Bundler Tests
Bundler tests use itBundled() from test/bundler/expectBundled.ts to test Bun's bundler.
Basic Usage
import { describe } from "bun:test"; import { itBundled, dedent } from "./expectBundled";
describe("bundler", () => {
itBundled("category/TestName", {
files: {
"index.js": console.log("hello");,
},
run: {
stdout: "hello",
},
});
});
Test ID format: category/TestName (e.g., banner/CommentBanner , minify/Empty )
File Setup
{
files: {
"index.js": console.log("test");,
"lib.ts": export const foo = 123;,
"nested/file.js": export default {};,
},
entryPoints: ["index.js"], // defaults to first file
runtimeFiles: { // written AFTER bundling
"extra.js": console.log("added later");,
},
}
Bundler Options
{ outfile: "/out.js", outdir: "/out", format: "esm" | "cjs" | "iife", target: "bun" | "browser" | "node",
// Minification minifyWhitespace: true, minifyIdentifiers: true, minifySyntax: true,
// Code manipulation banner: "// copyright", footer: "// end", define: { "PROD": "true" }, external: ["lodash"],
// Advanced sourceMap: "inline" | "external", splitting: true, treeShaking: true, drop: ["console"], }
Runtime Verification
{ run: { stdout: "expected output", // exact match stdout: /regex/, // pattern match partialStdout: "contains this", // substring stderr: "error output", exitCode: 1, env: { NODE_ENV: "production" }, runtime: "bun" | "node",
// Runtime errors
error: "ReferenceError: x is not defined",
}, }
Bundle Errors/Warnings
{ bundleErrors: { "/file.js": ["error message 1", "error message 2"], }, bundleWarnings: { "/file.js": ["warning message"], }, }
Dead Code Elimination (DCE)
Add markers in source code:
// KEEP - this should survive const used = 1;
// REMOVE - this should be eliminated const unused = 2;
{ dce: true, dceKeepMarkerCount: 5, // expected KEEP markers }
Capture Pattern
Verify exact transpilation with capture() :
itBundled("string/Folding", {
files: {
"index.ts": capture(\${1 + 1}`);`,
},
capture: ['"2"'], // expected captured value
minifySyntax: true,
});
Post-Bundle Assertions
{ onAfterBundle(api) { api.expectFile("out.js").toContain("console.log"); api.assertFileExists("out.js");
const content = api.readFile("out.js");
expect(content).toMatchSnapshot();
const values = api.captureFile("out.js");
expect(values).toEqual(["2"]);
}, }
Common Patterns
Simple output verification:
itBundled("banner/Comment", {
banner: "// copyright",
files: { "a.js": console.log("Hello") },
onAfterBundle(api) {
api.expectFile("out.js").toContain("// copyright");
},
});
Multi-file CJS/ESM interop:
itBundled("cjs/ImportSyntax", {
files: {
"entry.js": import lib from './lib.cjs'; console.log(lib);,
"lib.cjs": exports.foo = 'bar';,
},
run: { stdout: '{"foo":"bar"}' },
});
Error handling:
itBundled("edgecase/InvalidLoader", {
files: { "index.js": ... },
bundleErrors: {
"index.js": ["Unsupported loader type"],
},
});
Test Organization
test/bundler/ ├── bundler_banner.test.ts ├── bundler_string.test.ts ├── bundler_minify.test.ts ├── bundler_cjs.test.ts ├── bundler_edgecase.test.ts ├── bundler_splitting.test.ts ├── css/ ├── transpiler/ └── expectBundled.ts
Running Tests
bun bd test test/bundler/bundler_banner.test.ts BUN_BUNDLER_TEST_FILTER="banner/Comment" bun bd test bundler_banner.test.ts BUN_BUNDLER_TEST_DEBUG=1 bun bd test bundler_minify.test.ts
Key Points
-
Use dedent for readable multi-line code
-
File paths are relative (e.g., /index.js )
-
Use capture() to verify exact transpilation results
-
Use .toMatchSnapshot() for complex outputs
-
Pass array to run for multiple test scenarios