import { describe, expect, test } from "bun:test"; import { bunEnv, bunExe, isBroken, isWindows, tempDir } from "harness"; import { readdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { decodeSourceMappingsLine, itBundled } from "./expectBundled"; // A public path composes with the referenced file's path relative to the output // directory, never relative to the importing chunk (esbuild's semantics). const CDN_PUBLIC_PATH = "https://cdn.example/app/"; const cdnUrls = (source: string) => [...source.matchAll(/"(https:\/\/cdn\.example\/[^"]+)"/g)].map(match => match[1]); describe("bundler", () => { itBundled("edgecase/EmptyFile", { files: { "/entry.js": "", }, }); itBundled("edgecase/EmptyCommonJSModule", { files: { "/entry.js": /* js */ ` import * as module from './module.cjs'; console.log(typeof module) `, "/module.cjs": /* js */ ``, }, run: { stdout: "object", }, }); itBundled("edgecase/NestedRedirectToABuiltin", { files: { "/entry.js": /* js */ ` import * as path from './module.cjs'; console.log(path.join('a', 'b')) `, "/module.cjs": /* js */ ` module.exports = require('./2nd') `, "/2nd.js": /* js */ ` module.exports = require('path') `, }, target: "bun", run: { stdout: join("a", "b"), }, }); itBundled("edgecase/ImportStarFunction", { files: { "/entry.js": /* js */ ` import * as foo from "./foo.js"; console.log(foo.fn()); `, "/foo.js": /* js */ ` export function fn() { return "foo"; } `, }, run: { stdout: "foo" }, }); itBundled("edgecase/ImportStarSyntaxErrorBug", { // bug: 'import {ns}, * as import_x from "x";' files: { "/entry.js": /* js */ ` export {ns} from 'x' export * as ns2 from 'x' `, }, external: ["x"], runtimeFiles: { "/node_modules/x/index.js": `export const ns = 1`, }, run: true, }); itBundled("edgecase/BunPluginTreeShakeImport", { todo: true, // This only appears at runtime and not with bun build, even with --no-bundle files: { "/entry.ts": /* js */ ` import { A, B } from "./somewhere-else"; import { plugin } from "bun"; plugin(B()); new A().chainedMethods(); `, "/somewhere-else.ts": /* js */ ` export class A { chainedMethods() { console.log("hey"); } } export function B() { return { name: 'hey' } } `, }, minifySyntax: true, target: "bun", run: { file: "/entry.ts" }, }); itBundled("edgecase/TemplateStringIssue622", { files: { "/entry.ts": /* js */ ` capture(\`\\?\`); capture(hello\`\\?\`); `, }, capture: ["`?`", "hello`\\?`"], target: "bun", }); // https://github.com/oven-sh/bun/issues/2699 itBundled("edgecase/ImportNamedFromExportStarCJS", { files: { "/entry.js": /* js */ ` import { foo } from './foo'; console.log(foo); `, "/foo.js": /* js */ ` export * from './bar.cjs'; `, "/bar.cjs": /* js */ ` module.exports = { foo: 'bar' }; `, }, run: { stdout: "bar", }, }); itBundled("edgecase/NodeEnvDefaultUnset", { files: { "/entry.js": /* js */ ` capture(process.env.NODE_ENV); capture(process.env.NODE_ENV === 'production'); capture(process.env.NODE_ENV === 'development'); `, }, target: "browser", capture: ['"development"', "false", "true"], env: { // undefined will ensure this variable is not passed to the bundler NODE_ENV: undefined, }, }); itBundled("edgecase/NodeEnvDefaultDevelopment", { files: { "/entry.js": /* js */ ` capture(process.env.NODE_ENV); capture(process.env.NODE_ENV === 'production'); capture(process.env.NODE_ENV === 'development'); `, }, target: "browser", capture: ['"development"', "false", "true"], env: { NODE_ENV: "development", }, }); itBundled("edgecase/NodeEnvDefaultProduction", { files: { "/entry.js": /* js */ ` capture(process.env.NODE_ENV); capture(process.env.NODE_ENV === 'production'); capture(process.env.NODE_ENV === 'development'); `, }, target: "browser", capture: ['"production"', "true", "false"], env: { NODE_ENV: "production", }, }); itBundled("edgecase/NodeEnvOptionalChaining", { todo: true, files: { "/entry.js": /* js */ ` capture(process?.env?.NODE_ENV); capture(process?.env?.NODE_ENV === 'production'); capture(process?.env?.NODE_ENV === 'development'); capture(process.env?.NODE_ENV); capture(process.env?.NODE_ENV === 'production'); capture(process.env?.NODE_ENV === 'development'); capture(process?.env.NODE_ENV); capture(process?.env.NODE_ENV === 'production'); capture(process?.env.NODE_ENV === 'development'); `, }, target: "browser", capture: ['"development"', "false", "true", '"development"', "false", "true", '"development"', "false", "true"], env: { NODE_ENV: "development", }, }); itBundled("edgecase/StarExternal", { files: { "/entry.js": /* js */ ` import { foo } from './foo'; import { bar } from './bar'; console.log(foo); `, }, external: ["*"], }); itBundled("edgecase/ImportNamespaceAndDefault", { files: { "/entry.js": /* js */ ` import def2, * as ns2 from './c' console.log(def2, JSON.stringify(ns2)) `, "/c.js": /* js */ ` export const ns = 2 export const def2 = 3 export default 1 `, }, runtimeFiles: {}, run: { stdout: '1 {"def2":3,"default":1,"ns":2}', }, }); itBundled("edgecase/ExternalES6ConvertedToCommonJSSimplified", { todo: true, files: { "/entry.js": /* js */ ` console.log(JSON.stringify(require('./e'))); `, "/e.js": `export * from 'x'`, }, external: ["x"], runtimeFiles: { "/node_modules/x/index.js": /* js */ ` export const ns = 123 export const ns2 = 456 `, }, run: { stdout: ` {"ns":123,"ns2":456} `, }, }); itBundled("edgecase/ImportTrailingSlash", { files: { "/entry.js": /* js */ ` import "slash/" `, "/node_modules/slash/index.js": /* js */ `console.log(1)`, }, run: { stdout: "1", }, }); itBundled("edgecase/ValidLoaderSeenAsInvalid", { files: { "/entry.js": /* js */ `console.log(1)`, }, outdir: "/out", loader: { ".a": "file", ".b": "text", ".c": "toml", ".d": "json", ".e": "js", ".f": "ts", ".g": "jsx", ".h": "tsx", // ".i": "wasm", // ".j": "napi", // ".k": "base64", // ".l": "dataurl", // ".m": "binary", // ".n": "empty", // ".o": "copy", }, }); itBundled("edgecase/InvalidLoaderSegfault", { files: { "/entry.js": /* js */ `console.log(1)`, }, outdir: "/out", backend: "cli", loader: { ".cool": "wtf", }, bundleErrors: { "": ['invalid loader "wtf", expected one of:'], }, }); itBundled("edgecase/ScriptTagEscape", { todo: true, files: { "/entry.js": /* js */ ` console.log(''); console.log(await import('./text-file.txt')) `, "/text-file.txt": /* txt */ ` `, }, outdir: "/out", onAfterBundle(api) { try { expect(api.readFile("/out/entry.js")).not.toContain(""); } catch (error) { console.error("Bundle contains which will break if this bundle is placed in a script tag."); throw error; } }, }); itBundled("edgecase/JSONDefaultImport", { files: { "/entry.js": /* js */ ` import def from './test.json' console.log(JSON.stringify(def)) `, "/test.json": `{ "hello": 234, "world": 123 }`, }, run: { stdout: '{"hello":234,"world":123}', }, }); itBundled("edgecase/JSONDefaultKeyImport", { files: { "/entry.js": /* js */ ` import def from './test.json' console.log(def.hello) `, "/test.json": `{ "hello": 234, "world": "REMOVE" }`, }, run: { stdout: "234", }, }); itBundled("edgecase/JSONDefaultAndNamedImport", { todo: true, files: { "/entry.js": /* js */ ` import def from './test.json' import { hello } from './test.json' console.log(def.hello, hello) `, "/test.json": `{ "hello": 234, "world": "REMOVE" }`, }, dce: true, run: { stdout: "234 234", }, }); itBundled("edgecase/JSONWithDefaultKey", { files: { "/entry.js": /* js */ ` import def from './test.json' console.log(JSON.stringify(def)) `, "/test.json": `{ "default": 234 }`, }, dce: true, run: { stdout: '{"default":234}', }, }); itBundled("edgecase/JSONWithDefaultKeyNamespace", { todo: true, files: { "/entry.js": /* js */ ` import * as ns from './test.json' console.log(JSON.stringify(ns)) `, "/test.json": `{ "default": 234 }`, }, dce: true, run: { stdout: '{"default":234}', }, }); itBundled("edgecase/RequireUnknownExtension", { files: { "/entry.js": /* js */ ` require('./x.aaaa') `, "/x.aaaa": `x`, }, outdir: "/out", }); itBundled("edgecase/PackageJSONDefaultConditionRequire", { files: { "/entry.js": /* js */ ` const boop = require('boop') console.log(boop) `, "/node_modules/boop/package.json": /* json */ ` { "name": "boop", "exports": { ".": { "boop-server": "./ignore.js", "default": "./boop.js" } } } `, "/node_modules/boop/boop.js": /* js */ ` module.exports = 123 `, }, run: { stdout: "123", }, }); itBundled("edgecase/PackageJSONDefaultConditionImport", { files: { "/entry.js": /* js */ ` import React from 'boop' console.log(React) `, // NOTE: this test fails if the package name is "react" // most likely an issue with commonjs unwrapping. "/node_modules/boop/package.json": /* json */ ` { "name": "boop", "exports": { ".": { "react-server": "./ignore.js", "default": "./boop.js" } } } `, "/node_modules/boop/boop.js": /* js */ ` export default 123 `, }, run: { stdout: "123", }, }); itBundled("edgecase/TSConfigPathsStarOnlyInLeft", { files: { "/entry.ts": /* ts */ ` import test0 from 'test0/hello' console.log(test0) `, "/tsconfig.json": /* json */ ` { "compilerOptions": { "baseUrl": ".", "paths": { "test0/*": ["./test0-success.ts"] } } } `, "/test0-success.ts": `export default 'success'`, }, run: { stdout: "success", }, }); itBundled("edgecase/TSConfigPathStarAnywhere", { todo: true, files: { "/entry.ts": /* ts */ ` import test0 from 'test3/foo' console.log(test0) `, "/tsconfig.json": /* json */ ` { "compilerOptions": { "baseUrl": ".", "paths": { "t*t3/foo": ["./test3-succ*s.ts"], } } } `, "/test3-success.ts": `export default 'success'`, }, run: { stdout: "success", }, }); itBundled("edgecase/StaticClassNameIssue2806", { files: { "/entry.ts": /* ts */ ` new class C { set baz(x) { C.foo = x; C.bar; } static get bar() { console.log(C.foo); } }().baz = "PASS"; new class C { set baz(x) { C.foo = x; C.bar; } static get bar() { console.log(C.foo); } }().baz = "Hello World"; `, }, minifyIdentifiers: true, run: { stdout: "PASS\nHello World", }, }); itBundled("edgecase/DCEVarRedeclarationIssue2814A", { files: { "/entry.ts": /* ts */ ` var a = 1; if (false) { var a; } console.log(a); `, }, target: "bun", run: { stdout: `1`, }, }); itBundled("edgecase/DCEVarRedeclarationIssue2814B", { files: { "/entry.ts": /* ts */ ` var a = 1; switch ("foo") { case "foo": var a; } console.log(a); `, }, target: "bun", run: { stdout: `1`, }, }); itBundled("edgecase/DCEVarRedeclarationIssue2814C", { files: { "/entry.ts": /* ts */ ` "use strict"; var a = 1; { var a; } console.log(a); `, }, target: "bun", run: { stdout: `1`, }, }); itBundled("edgecase/DCEVarRedeclarationIssue2814", { files: { "/entry.ts": /* ts */ ` "use strict"; var a = 1, b = 2; switch (b++) { case b: var c = a; var a; break; } console.log(a); var x = 123, y = 45; switch (console) { case 456: var x = 789, y = 0; } var y = 67; console.log(x, y); var z = 123; switch (console) { default: var z = typeof z; } console.log(z); var A = 1, B = 2; switch (A) { case A: var B; break; case B: break; } console.log(B); `, }, target: "bun", run: { stdout: ` 1 123 67 number 2 `, }, }); itBundled("edgecase/DCEVarRedeclarationIssue2815", { todo: true, files: { "/entry.ts": /* ts */ ` var x = 1; try { console.blog; } catch (x) { var x = 2; } console.log(x); var e = 3; try { console.log("try2"); } catch (e) { var e = 4; } console.log(e); try { var z = 5; throw "try3"; } catch (w) { z += w; var w = 6; } console.log(z); var c = 8; try { "try4"; } catch (c) { var c = 9; } console.log(c); `, }, target: "bun", run: { stdout: ` 1 123 67 number 2 `, }, }); // https://github.com/oven-sh/bun/issues/30271 // // When dead-code elimination prunes an if/else body, the scaffolding // itself only collapses with --minify-syntax. Even so, the empty `else {}` // remnant produced by the prune is ugly — trim it so the output at least // says `if (true) { kept }` instead of `if (true) { kept } else {}`. itBundled("edgecase/DCEEmptyElseTrimmed#30271", { files: { "/entry.js": /* js */ ` if ("foo" === "foo") { console.log("success"); } else { console.log("fail"); } `, }, target: "bun", onAfterBundle(api) { const out = api.readFile("/out.js"); // The dead branch body is gone... expect(out).not.toContain("fail"); // ...and so is the empty `else {}` remnant. expect(out).not.toContain("else"); }, run: { stdout: "success", }, }); // Trimming the empty `else {}` from an inner labeled `if` used to drop the // dangling-else guard: `if (a) L: if (b) c(); else {} else d();` would print // as `if (a) L: if (b) c(); else d();`, re-binding `else d()` to the inner // `if (b)`. `wrapToAvoidAmbiguousElse` needs to traverse `.s_label`. itBundled("edgecase/DCEEmptyElseTrimmedLabeledDanglingElse#30271", { files: { "/entry.js": /* js */ ` var a = false, b = false; if (a) L: if (b) console.log("inner-then"); else {} else console.log("outer-else"); `, }, target: "bun", run: { stdout: "outer-else", }, }); itBundled("edgecase/AbsolutePathShouldNotResolveAsRelative", { files: { "/entry.js": /* js */ ` console.log(1); `, }, entryPointsRaw: ["/entry.js"], bundleErrors: { "": ['ModuleNotFound resolving "/entry.js" (entry point)'], }, }); itBundled("edgecase/AssetEntryPoint", { files: { "/entry.zig": ` const std = @import("std"); pub fn main() void { std.log.info("Hello, world!\\n", .{}); } `, }, outdir: "/out", entryPointsRaw: ["./entry.zig"], runtimeFiles: { "/exec.js": ` import assert from 'node:assert'; import the_path from './out/entry.js'; assert.strictEqual(the_path, './entry-z5artd5z.zig'); `, }, run: { file: "./exec.js", }, }); itBundled("edgecase/ExportDefaultUndefined", { files: { "/entry.ts": /* ts */ ` export const a = 1; `, }, target: "bun", }); itBundled("edgecase/RuntimeExternalRequire", { files: { "/entry.ts": /* ts */ ` console.log(require("hello-1").type); `, }, external: ["hello-1"], target: "bun", runtimeFiles: { "/node_modules/hello-1/require.js": `export const type = "require";`, "/node_modules/hello-1/package.json": /* json */ ` { "type": "module", "exports": { ".": { "require": "./require.js", } } } `, }, run: { stdout: ` require `, }, }); itBundled("edgecase/RuntimeExternalImport", { todo: true, files: { "/entry.ts": /* ts */ ` import { type as a1 } from 'hello-1'; import { type as a2 } from 'hello-2'; import { type as a3 } from 'hello-3'; console.log(a1, a2, a3); const b1 = require('hello-1').type; const b2 = require('hello-2').type; const b3 = require('hello-3').type; console.log(b1, b2, b3); `, }, external: ["hello-1", "hello-2", "hello-3"], target: "bun", runtimeFiles: { "/node_modules/hello-1/node.js": `export const type = "node";`, "/node_modules/hello-1/bun.js": `export const type = "bun";`, "/node_modules/hello-1/package.json": /* json */ ` { "type": "module", "exports": { ".": { "node": "./node.js", "bun": "./bun.js" } } } `, "/node_modules/hello-2/node.js": `export const type = "node";`, "/node_modules/hello-2/bun.js": `export const type = "bun";`, "/node_modules/hello-2/package.json": /* json */ ` { "type": "module", "exports": { ".": { "bun": "./bun.js", "node": "./node.js" } } } `, "/node_modules/hello-3/import.js": `export const type = "import";`, "/node_modules/hello-3/require.js": `exports.type = "require";`, "/node_modules/hello-3/package.json": /* json */ ` { "type": "module", "exports": { ".": { "require": "./require.js", "import": "./import.js", } } } `, }, run: { stdout: ` bun bun import bun bun import `, }, }); itBundled("edgecase/RuntimeExternalImport2", { todo: true, files: { "/entry.ts": /* ts */ ` import t from 'hello'; console.log(t); `, }, external: ["hello"], target: "bun", runtimeFiles: { "/node_modules/hello/index.js": /* js */ ` export const hello = "Hello World"; `, }, run: { stdout: "Hello World", }, }); itBundled("edgecase/AssetPublicPath", { files: { "/entry.ts": /* ts */ ` import hello from "./hello.file"; console.log(hello); `, "/hello.file": "Hello World", }, outdir: "/out", publicPath: "/www", run: {}, }); itBundled("edgecase/PublicPathNestedChunkReferences", { files: { "/src/pages/a/entry.ts": /* ts */ ` import { shared } from "../../shared"; import logo from "../../logo.svg"; console.log(shared(), logo); `, "/src/pages/b/entry.ts": /* ts */ ` import { shared } from "../../shared"; console.log(shared()); `, "/src/shared.ts": /* ts */ ` import icon from "./icon.svg"; export const shared = () => icon; `, "/src/icon.svg": ``, "/src/logo.svg": ``, }, entryPoints: ["/src/pages/a/entry.ts", "/src/pages/b/entry.ts"], outputPaths: ["/out/pages/a/entry.js", "/out/pages/b/entry.js"], root: "/src", outdir: "/out", splitting: true, publicPath: CDN_PUBLIC_PATH, chunkNaming: "chunk-[hash].[ext]", loader: { ".svg": "file" }, onAfterBundle(api) { // The nested entry references the shared chunk and its own asset. Both // must resolve under the public path, not above it. const nested = cdnUrls(api.readFile("out/pages/a/entry.js")).map(url => url.slice(CDN_PUBLIC_PATH.length)); expect(nested.sort()).toEqual([ expect.stringMatching(/^chunk-[a-z0-9]+\.js$/), expect.stringMatching(/^logo-[a-z0-9]+\.svg$/), ]); for (const rel of nested) { api.assertFileExists(join("out", rel)); } // The root-level chunk emits the same form in the same build. const chunks = readdirSync(api.outdir).filter(file => file.endsWith(".js")); expect(chunks).toHaveLength(1); const rootLevel = cdnUrls(api.readFile(join("out", chunks[0]))).map(url => url.slice(CDN_PUBLIC_PATH.length)); expect(rootLevel).toEqual([expect.stringMatching(/^icon-[a-z0-9]+\.svg$/)]); api.assertFileExists(join("out", rootLevel[0])); }, }); itBundled("edgecase/PublicPathNestedEntryAsset", { files: { "/src/pages/a/entry.ts": /* ts */ ` import logo from "../../logo.svg"; console.log(logo); `, "/src/logo.svg": ``, }, entryPoints: ["/src/pages/a/entry.ts"], outputPaths: ["/out/pages/a/entry.js"], root: "/src", outdir: "/out", publicPath: CDN_PUBLIC_PATH, loader: { ".svg": "file" }, onAfterBundle(api) { const urls = cdnUrls(api.readFile("out/pages/a/entry.js")).map(url => url.slice(CDN_PUBLIC_PATH.length)); expect(urls).toEqual([expect.stringMatching(/^logo-[a-z0-9]+\.svg$/)]); api.assertFileExists(join("out", urls[0])); }, }); itBundled("edgecase/NoPublicPathNestedChunkStaysRelative", { files: { "/src/pages/a/entry.ts": /* ts */ ` import { shared } from "../../shared"; console.log(shared()); `, "/src/pages/b/entry.ts": /* ts */ ` import { shared } from "../../shared"; console.log(shared()); `, "/src/shared.ts": `export const shared = () => "shared";`, }, entryPoints: ["/src/pages/a/entry.ts", "/src/pages/b/entry.ts"], outputPaths: ["/out/pages/a/entry.js", "/out/pages/b/entry.js"], root: "/src", outdir: "/out", splitting: true, chunkNaming: "chunk-[hash].[ext]", onAfterBundle(api) { api.expectFile("out/pages/a/entry.js").toMatch(/from "\.\.\/\.\.\/chunk-[a-z0-9]+\.js"/); }, }); itBundled("edgecase/ImportDefaultInDirectory", { files: { "/a/file.js": ` import def from './def' console.log(def) `, "/a/def.js": ` export default 1; console.log('inner'); `, }, run: { file: "/out.js", stdout: "inner\n1", }, }); itBundled("edgecase/RequireVarThenExport", { files: { "/entry.js": /* js */ ` import { version } from './react'; console.log(version); `, "/react.js": /* js */ ` const a = require('./library.js'); exports.version = a.version; `, "/library.js": /* js */ ` exports.version = '0.6.0'; `, }, target: "bun", run: { stdout: `0.6.0`, }, }); itBundled("edgecase/OverwriteInputWithOutdir", { todo: true, files: { "/entry.js": /* js */ ` import { version } from './library'; console.log(version); `, "/library.js": /* js */ ` exports.version = '0.6.0'; `, }, outdir: "/", bundleErrors: { "": ['Refusing to overwrite input file "/entry.js"'], }, }); itBundled("edgecase/OverwriteInputWithOutfile", { todo: true, files: { "/entry.js": /* js */ ` import { version } from './library'; console.log(version); `, "/library.js": /* js */ ` exports.version = '0.6.0'; `, }, outfile: "/entry.js", bundleErrors: { "": ['Refusing to overwrite input file "/entry.js"'], }, }); itBundled("edgecase/OverwriteInputNonEntrypoint", { todo: true, files: { "/entry.js": /* js */ ` import { version } from './library'; console.log(version); `, "/library.js": /* js */ ` exports.version = '0.6.0'; `, }, outfile: "/entry.js", bundleErrors: { "": ['Refusing to overwrite input file "/entry.js"'], }, }); itBundled("edgecase/ModuleExportsFunctionIssue2911", { files: { "/entry.js": /* js */ ` const fn = require('fresh'); console.log(fn()); const fn2 = require('./not_in_node_modules'); console.log(fn2()); import fn3 from 'fresh'; console.log(fn()); import fn4 from './not_in_node_modules'; console.log(fn2()); `, "/node_modules/fresh/index.js": /* js */ ` module.exports = function() { return 'it worked'; } `, "/not_in_node_modules.js": /* js */ ` module.exports = function() { return 'it worked'; } `, }, run: { stdout: "it worked\nit worked\nit worked\nit worked", }, }); itBundled("edgecase/IsBuffer1", { files: { "/entry.js": /* js */ ` import isBuffer from 'lodash-es/isBuffer'; if(isBuffer !== 1) throw 'fail'; console.log('pass'); `, "/node_modules/lodash-es/isBuffer.js": /* js */ ` var freeExports = typeof exports == 'object'; // this is using the 'freeExports' variable but giving a predictable outcome const isBuffer = freeExports ? 1 : 1; export default isBuffer; `, }, run: { stdout: "pass", }, }); itBundled("edgecase/TS_LessThanAmbiguity", { files: { "/entry.ts": ` function expectArrow(item) { if(typeof item !== 'function') { throw new Error('Expected arrow function'); } } function expectTypeCast(item) { if(typeof item !== 'number') { throw new Error('Expected arrow function'); } } const x = 1; expectTypeCast((x)); expectTypeCast(<[]>(x)); expectTypeCast((x)); expectArrow((x) => {}) expectArrow((x) => {}) expectArrow((x) => {}) expectArrow((x) => {}) expectArrow((x) => {}) console.log('pass'); `, }, run: { stdout: "pass", }, }); itBundled("edgecase/TSX_LessThanAmbiguity", { files: { "/entry.tsx": ` function expectJSX(item) { if(typeof item !== 'object') { throw new Error('Expected JSX'); } } function expectArrow(item) { if(typeof item !== 'function') { throw new Error('Expected arrow function'); } } const A = 1; expectJSX((x) ...); expectJSX((x) ... ); expectJSX((x) ... ); expectJSX((x) ...); expectJSX((x) ...); expectJSX((x) ...); expectJSX((x) ...); expectArrow((x) => {}); expectArrow((x) => {}); expectArrow((x) => {}); console.log('pass'); `, "/node_modules/react/jsx-dev-runtime.js": ` export function jsxDEV(type, props, key, isStaticChildren, source, self) { return {}; } `, }, run: { stdout: "pass", }, }); itBundled("edgecase/IsBuffer2", { files: { "/entry.js": /* js */ ` import isBuffer from 'lodash-es/isBuffer'; if(isBuffer !== 1) throw 'fail'; console.log('pass'); `, "/node_modules/lodash-es/package.json": /* json */ ` { "name": "lodash-es", "type": "module"} `, "/node_modules/lodash-es/isBuffer.js": /* js */ ` var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports; var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module; // this is using the 'freeExports' variable but giving a predictable outcome const isBuffer = [freeExports, freeModule] ? 1 : 1; export default isBuffer; `, }, run: { stdout: "pass", }, }); itBundled("edgecase/YieldKeyword", { files: { "/entry.js": /* js */ ` function* foo() { yield 1; [yield]; yield yield yield; [yield * 2]; [yield (yield)]; { x: yield }; (yield).hello yield+1 } `, }, }); itBundled("edgecase/UsingWithSixImports", { files: { "/entry.js": /* js */ ` import { Database } from 'bun:sqlite'; import 'bun'; import 'bun:ffi'; import 'bun:jsc'; import 'node:assert'; import 'bun:test'; using a = new Database(); export { a }; `, }, target: "bun", }); itBundled("edgecase/EmitInvalidSourceMap1", { files: { "/src/index.ts": /* ts */ ` const y = await import("./second.mts"); import * as z from "./third.mts"; const v = await import("./third.mts"); console.log(z, v, y); `, "/src/second.mts": /* ts */ ` export default "swag"; `, "/src/third.mts": /* ts */ ` export default "bun"; `, }, outdir: "/out", target: "bun", sourceMap: "external", minifySyntax: true, minifyIdentifiers: true, minifyWhitespace: true, splitting: true, }); // chunk-concat weaved mappings together incorrectly causing the `console` // token to be -2, thus breaking the rest of the mappings in the file itBundled("edgecase/EmitInvalidSourceMap2", { files: { "/entry.js": ` import * as react from "react"; console.log(react); `, "/node_modules/react/index.js": ` var _ = module; sideEffect(() => {}); `, }, outdir: "/out", sourceMap: "external", minifySyntax: true, minifyIdentifiers: true, minifyWhitespace: true, snapshotSourceMap: { "entry.js.map": { files: ["../node_modules/react/index.js", "../entry.js"], mappingsExactMatch: "2lBACA,WAAW,IAAQ,EAAE,ICDrB,eACA,QAAQ,IAAI,CAAK", }, }, }); // chunk-concat forgets to de-duplicate source indicies // chunk-concat ignores all but the first instance of a chunk itBundled("edgecase/EmitInvalidSourceMap2", { files: { "/entry.js": ` const a = new TextEncoder(); console.log('hey!') const d = new TextEncoder(); const b = { hello: 'world' }; const c = new Set([ ]); console.log('hey!') console.log('hey!') console.log('hey!') console.log('hey!') `, }, outdir: "/out", sourceMap: "external", minifySyntax: true, minifyIdentifiers: true, minifyWhitespace: true, snapshotSourceMap: { "entry.js.map": { files: ["../entry.js"], mappingsExactMatch: "AACQ,QAAQ,IAAI,MAAM,EAOlB,QAAQ,IAAI,MAAM,EAClB,QAAQ,IAAI,MAAM,EAClB,QAAQ,IAAI,MAAM,EAClB,QAAQ,IAAI,MAAM", }, }, }); // SourceMapPieces.finalize advanced the shift cursor at most once per // mapping, so a mapping crossing >=2 placeholder substitutions on one // minified line was re-encoded against a stale shift and landed out of order. itBundled("edgecase/EmitInvalidSourceMapMultipleShifts", { files: { "/entry.ts": /* ts */ ` import a from "./a.bin"; import b from "./b.bin"; import c from "./c.bin"; const keep: string[] = [a, b, c]; console.log(keep); `, "/a.bin": "AAAA", "/b.bin": "BBBB", "/c.bin": "CCCC", }, outdir: "/out", loader: { ".bin": "file" }, sourceMap: "external", minifyWhitespace: true, onAfterBundle(api) { const js = api.readFile("/out/entry.js"); const map = JSON.parse(api.readFile("/out/entry.js.map")); expect(map.sources).toEqual(["../entry.ts"]); const line1 = decodeSourceMappingsLine(map.mappings.split(";")[0]); for (let i = 1; i < line1.length; i++) { if (line1[i].gen < line1[i - 1].gen) { throw new Error( `out-of-order mappings on line 1: generated column ` + `${line1[i - 1].gen} -> ${line1[i].gen}\n` + line1.map(s => ` col ${s.gen} -> entry.ts:${s.ol + 1}:${s.oc}`).join("\n"), ); } } // The first mapping after all three substituted asset paths is for // `keep` in `const keep`. It must point at the `keep` identifier in // the generated output, not at a stale pre-shift column. const keepCol = js.split("\n")[0].indexOf("keep=["); expect(keepCol).toBeGreaterThan(0); expect(line1).toContainEqual({ gen: keepCol, src: 0, ol: 3, oc: 6 }); }, }); itBundled("edgecase/NoUselessConstructorTS", { files: { "/entry.ts": ` class A { constructor(...args) { console.log(JSON.stringify({ args, self: this })); } field = 1; } class B extends A {} class C extends A { field = 2 } class D extends A { public field = 3 } class E extends A { constructor(public y: number, a) { super(a); }; public field = 4 } new A("arg1", "arg2"); new B("arg1", "arg2"); new C("arg1", "arg2"); new D("arg1", "arg2"); new E("arg1", "arg2"); `, }, run: { stdout: ` {"args":["arg1","arg2"],"self":{"field":1}} {"args":["arg1","arg2"],"self":{"field":1}} {"args":["arg1","arg2"],"self":{"field":1}} {"args":["arg1","arg2"],"self":{"field":1}} {"args":["arg2"],"self":{"field":1}} `, }, onAfterBundle(api) { const content = api.readFile("out.js"); const count = content.split("constructor").length - 1; expect(count, "should only emit two constructors: " + content).toBe(2); }, }); itBundled("edgecase/EnumInliningRopeStringPoison", { files: { "/entry.ts": ` const enum A1 { B = "1" + "2", C = "3" + B, }; console.log(A1.B, A1.C); const enum A2 { B = "1" + "2", C = ("3" + B) + "4", }; console.log(A2.B, A2.C); `, }, run: { stdout: "12 312\n12 3124", }, }); itBundled("edgecase/ProtoNullProtoInlining", { files: { "/entry.ts": ` console.log({ __proto__: null }.__proto__ !== void 0) `, }, run: { stdout: "false", }, }); itBundled("edgecase/ImportOptionsArgument", { files: { "/entry.js": ` import('ext', { with: { get ''() { KEEP } } }) .then(function (error) { console.log(error); }); `, }, dce: true, external: ["ext"], target: "bun", }); itBundled("edgecase/ConstantFoldingShiftOperations", { files: { "/entry.ts": ` capture(421 >> -542) capture(421 >>> -542) capture(1 << 32) capture(1 >> 32) capture(1 >>> 32) capture(47849312 << 34) capture(-9 >> 1) capture(-5 >> 1) `, }, minifySyntax: true, capture: ["105", "105", "1", "1", "1", "191397248", "-5", "-3"], }); itBundled("edgecase/ConstantFoldingBitwiseCoersion", { files: { "/entry.ts": ` capture(0 | 0) capture(12582912 | 0) capture(0xc00000 | 0) capture(Infinity | 0) capture(-Infinity | 0) capture(NaN | 0) // u32 limits capture(-4294967295 | 0) capture(-4294967296 | 0) capture(-4294967297 | 0) capture(4294967295 | 0) capture(4294967296 | 0) capture(4294967297 | 0) // i32 limits capture(-2147483647 | 0) capture(-2147483648 | 0) capture(-2147483649 | 0) capture(2147483647 | 0) capture(2147483648 | 0) capture(2147483649 | 0) capture(0.5 | 0) `, }, minifySyntax: true, capture: [ "0", "12582912", "12582912", "0", "0", "0", "1", "0", "-1", "-1", "0", "1", "-2147483647", "-2147483648", "2147483647", "2147483647", "-2147483648", "-2147483647", "0", ], }); itBundled("edgecase/EnumInliningNanBoxedEncoding", { files: { "/main.ts": ` import { Enum } from './other.ts'; capture(Enum.a); capture(Enum.b); capture(Enum.c); capture(Enum.d); capture(Enum.e); capture(Enum.f); capture(Enum.g); `, "/other.ts": ` export const enum Enum { a = 0, b = NaN, c = (0 / 0) + 1, d = Infinity, e = -Infinity, f = 3e450, // https://float.exposed/0xffefffffffffffff g = -1.79769313486231570815e+308, } `, }, minifySyntax: true, capture: [ "0 /* a */", "NaN /* b */", "NaN /* c */", "1 / 0 /* d */", "-1 / 0 /* e */", "1 / 0 /* f */", // should probably fix this "-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 /* g */", ], }); // Stack overflow possibility itBundled("edgecase/AwsCdkLib", { files: { "entry.js": `import * as aws from ${JSON.stringify(require.resolve("aws-cdk-lib"))}; aws;`, }, target: "bun", run: true, todo: isBroken && isWindows, timeoutScale: 5, }); itBundled("edgecase/PackageExternalDoNotBundleNodeModules", { files: { "/entry.ts": /* ts */ ` import { a } from "foo"; console.log(a); `, }, packages: "external", target: "bun", runtimeFiles: { "/node_modules/foo/index.js": `export const a = "Hello World";`, "/node_modules/foo/package.json": /* json */ ` { "name": "foo", "version": "2.0.0", "main": "index.js" } `, }, run: { stdout: ` Hello World `, }, }); // #29590: a catch-all `paths` entry (common for ambient .d.ts stubs) // whose target doesn't exist must not defeat `packages=external`. itBundled("edgecase/PackageExternalStarPathsDoesNotBundleNodeModules#29590", { files: { "/entry.ts": /* ts */ ` import { a } from "foo"; console.log(a); `, "/tsconfig.json": /* json */ ` { "compilerOptions": { "baseUrl": ".", "paths": { "*": ["./types/*"] } } } `, }, packages: "external", target: "bun", runtimeFiles: { "/node_modules/foo/index.js": `export const a = "Hello World";`, "/node_modules/foo/package.json": /* json */ ` { "name": "foo", "version": "2.0.0", "main": "index.js" } `, }, onAfterBundle(api) { // If this regresses, `foo` gets inlined via __commonJS(...) instead. api.expectFile("/out.js").toContain(`from "foo"`); }, run: { stdout: ` Hello World `, }, }); // #29590: an explicit --external wildcard must win over a tsconfig `paths` // alias, even when the alias resolves to a real local file. itBundled("edgecase/ExternalWildcardBeatsTSConfigPaths#29590", { files: { "/entry.ts": /* ts */ ` import { add } from "@/src/adder"; console.log(add(1, 2)); `, "/src/adder.ts": /* ts */ ` export const add = (a: number, b: number) => a + b; `, "/tsconfig.json": /* json */ ` { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["./*"] } } } `, }, packages: "external", external: ["@/src/*"], target: "bun", onAfterBundle(api) { // If this regresses, `add` gets inlined from src/adder.ts. api.expectFile("/out.js").toContain(`from "@/src/adder"`); api.expectFile("/out.js").not.toContain(`= (a, b) => a + b`); }, }); itBundled("edgecase/EntrypointWithoutPrefixSlashOrDotIsNotConsideredExternal#12734", { files: { "/src/entry.ts": /* ts */ ` import { helloWorld } from "./second.ts"; console.log(helloWorld); `, "/src/second.ts": /* ts */ ` export const helloWorld = "Hello World"; `, }, root: "/src", entryPointsRaw: ["src/entry.ts"], packages: "external", target: "bun", run: { file: "/src/entry.ts", stdout: ` Hello World `, }, }); itBundled("edgecase/IntegerUnderflow#12547", { files: { "/entry.js": ` import { a } from 'external'; function func() { const b = 1 + a.c; return b; } `, }, minifySyntax: true, minifyWhitespace: true, minifyIdentifiers: true, external: ["external"], onAfterBundle(api) { // DCE is not yet able to eliminate the `a` or even the `as c`. Equivalent to esbuild as of 2024-07-15 api.expectFile("/out.js").toBe(`import{a as c}from"external";\n`); }, }); itBundled("edgecase/TypeScriptNamespaceSiblingFunction", { files: { "/entry.ts": ` namespace X { export function Y() { return 2; } export namespace Y { export const Z = 1; } } console.log(X, X.Y(), X.Y.Z); `, }, run: { stdout: "{\n Y: [Function: Y],\n} 2 1", }, }); itBundled("edgecase/TypeScriptNamespaceSiblingClass", { files: { "/entry.ts": ` namespace X { export class Y { constructor(v) { this.value = v; } toJSON() { return this.value; } } export namespace Y { export const Z = 1; } } console.log(X, new X.Y(2).toJSON(), X.Y.Z); `, }, run: { stdout: "{\n Y: [class Y],\n} 2 1", }, }); itBundled("edgecase/TypeScriptNamespaceSiblingEnum", { files: { "/entry.ts": ` namespace X { export enum Y { A, B, } export namespace Y { export const Z = 1; } } console.log(JSON.stringify([X, X.Y.A, X.Y.Z])); `, }, run: { stdout: '[{"Y":{"0":"A","1":"B","A":0,"B":1,"Z":1}},0,1]', }, }); itBundled("edgecase/TypeScriptNamespaceSiblingVariable", { files: { "/entry.ts": ` namespace X { export let Y = {}; export namespace Y { export const Z = 1; } } `, }, bundleErrors: { "/entry.ts": [`"Y" has already been declared`], }, }); // This specifically only happens with 'export { ... } from ...' syntax itBundled("edgecase/EsmSideEffectsFalseWithSideEffectsExportFrom", { files: { "/file1.js": ` import("./file2.js"); `, "/file2.js": ` export { a } from './file3.js'; `, "/file3.js": ` export function a(input) { return 42; } console.log('side effect'); `, "/package.json": ` { "name": "my-package", "sideEffects": false } `, }, run: { stdout: "side effect", }, }); itBundled("edgecase/EsmSideEffectsFalseWithSideEffectsExportFromCodeSplitting", { files: { "/file1.js": ` import("./file2.js"); console.log('file1'); `, "/file1b.js": ` import("./file2.js"); console.log('file2'); `, "/file2.js": ` export { a } from './file3.js'; `, "/file3.js": ` export function a(input) { return 42; } console.log('side effect'); `, "/package.json": ` { "name": "my-package", "sideEffects": false } `, }, splitting: true, outdir: "out", entryPoints: ["./file1.js", "./file1b.js"], run: [ { file: "/out/file1.js", stdout: "file1\nside effect", }, { file: "/out/file1b.js", stdout: "file2\nside effect", }, ], }); itBundled("edgecase/RequireSideEffectsFalseWithSideEffectsExportFrom", { files: { "/file1.js": ` require("./file2.js"); `, "/file2.js": ` export { a } from './file3.js'; `, "/file3.js": ` export function a(input) { return 42; } console.log('side effect'); `, "/package.json": ` { "name": "my-package", "sideEffects": false } `, }, run: { stdout: "side effect", }, }); itBundled("edgecase/SideEffectsFalseWithSideEffectsExportFrom", { files: { "/file1.js": ` import("./file2.js"); `, "/file2.js": ` import * as foo from './file3.js'; export default foo; `, "/file3.js": ` export function a(input) { return 42; } console.log('side effect'); `, "/package.json": ` { "name": "my-package", "sideEffects": false } `, }, run: { stdout: "side effect", }, }); itBundled("edgecase/BuiltinWithTrailingSlash", { files: { "/entry.js": ` import * as process from 'process/'; console.log(JSON.stringify(process)); `, "/node_modules/process/index.js": ` export default { hello: 'world' }; `, }, run: { stdout: `{"default":{"hello":"world"}}`, }, }); itBundled("edgecase/EsmWrapperClassHoisting", { files: { "/entry.ts": ` async function hi() { const { default: MyInherited } = await import('./hello'); const myInstance = new MyInherited(); console.log(myInstance.greet()) } hi(); `, "/hello.ts": ` const MyReassignedSuper = class MySuper { greet() { return 'Hello, world!'; } }; class MyInherited extends MyReassignedSuper {}; export default MyInherited; `, }, run: { stdout: "Hello, world!", }, }); itBundled("edgecase/EsmWrapperElimination1", { files: { "/entry.ts": ` async function load() { return import('./hello'); } load().then(({ default: def }) => console.log(def())); `, "/hello.ts": ` export var x = 123; export var y = function() { return x; }; export function z() { return y(); } function a() { return z(); } export default function c() { return a(); } `, }, run: { stdout: "123", }, }); itBundled("edgecase/TsEnumTreeShakingUseAndInlineClass", { files: { "/entry.ts": ` import { TestEnum } from './enum'; class TestClass { constructor() { console.log(JSON.stringify(TestEnum)); } testMethod(name: TestEnum) { return name === TestEnum.A; } } // This must use wrapper class console.log(new TestClass()); // This must inline console.log(TestClass.prototype.testMethod.toString().includes('TestEnum')); `, "/enum.ts": ` export enum TestEnum { A, B, } `, }, dce: true, run: { stdout: ` {"0":"A","1":"B","A":0,"B":1} TestClass { testMethod: [Function: testMethod], } false `, }, }); // this test checks that visit order doesnt matter (inline then use, above is use then inline) itBundled("edgecase/TsEnumTreeShakingUseAndInlineClass2", { files: { "/entry.ts": ` import { TestEnum } from './enum'; class TestClass { testMethod(name: TestEnum) { return name === TestEnum.A; } constructor() { console.log(JSON.stringify(TestEnum)); } } // This must use wrapper class console.log(new TestClass()); // This must inline console.log(TestClass.prototype.testMethod.toString().includes('TestEnum')); `, "/enum.ts": ` export enum TestEnum { A, B, } `, }, dce: true, run: { stdout: ` {"0":"A","1":"B","A":0,"B":1} TestClass { testMethod: [Function: testMethod], } false `, }, }); itBundled("edgecase/TsEnumTreeShakingUseAndInlineNamespace", { files: { "/entry.ts": ` import { TestEnum } from './enum'; namespace TestClass { console.log(JSON.stringify(TestEnum)); console.log((() => TestEnum.A).toString().includes('TestEnum')); } `, "/enum.ts": ` export enum TestEnum { A, B, } `, }, dce: true, run: { stdout: ` {"0":"A","1":"B","A":0,"B":1} false `, }, }); // https://github.com/oven-sh/bun/issues/31755 // An object literal whose computed keys are inlined enum members (e.g. // `{ [A.FOO]: ... }`) has no side effects, so when the binding is unused it // must be tree-shaken along with everything it references. The enum-member // key is wrapped in an inlined-enum node; the side-effect check must look // through that wrapper just like it does for a bare numeric-literal key. itBundled("edgecase/TsEnumKeyedObjectTreeShaking#31755", { files: { "/entry.ts": ` import { A } from './lib'; console.log(JSON.stringify(A)); `, "/lib.ts": ` export enum A { FOO, BAR } export function fooFunctionREMOVE() {} export const fooArrowFunctionREMOVE = () => {}; export const unusedObjectREMOVE = { [A.FOO]: fooFunctionREMOVE, [A.BAR]: fooArrowFunctionREMOVE }; `, }, dce: true, dceKeepMarkerCount: false, assertNotPresent: { "/out.js": ["fooFunctionREMOVE", "fooArrowFunctionREMOVE", "unusedObjectREMOVE"], }, run: { stdout: `{"0":"FOO","1":"BAR","FOO":0,"BAR":1}`, }, }); // Same as above but with primitive-literal values, isolating the computed // enum-member key as the thing that previously blocked removal. itBundled("edgecase/TsEnumKeyedLiteralObjectTreeShaking#31755", { files: { "/entry.ts": ` import { A } from './lib'; console.log(JSON.stringify(A)); `, "/lib.ts": ` export enum A { FOO, BAR } export const unusedObjectREMOVE = { [A.FOO]: 1, [A.BAR]: 2 }; `, }, dce: true, dceKeepMarkerCount: false, assertNotPresent: { "/out.js": ["unusedObjectREMOVE"], }, run: { stdout: `{"0":"FOO","1":"BAR","FOO":0,"BAR":1}`, }, }); // Guard against over-eager removal: a computed key that actually has side // effects must keep the object alive even when the binding is unused. The // side effect is observed at runtime (the flag it sets is printed) so the // test fails if the computed-key call is tree-shaken away. itBundled("edgecase/ComputedKeyWithSideEffectsNotTreeShaken#31755", { files: { "/entry.ts": ` import './lib'; console.log(globalThis.hit === true ? 'side-effect-ran' : 'side-effect-missing'); `, "/lib.ts": ` function sideEffectKept() { globalThis.hit = true; return 'k'; } const unusedObject = { [sideEffectKept()]: 1 }; `, }, onAfterBundle(api) { // The side-effecting key call must survive tree-shaking. api.expectFile("/out.js").toContain("sideEffectKept"); }, run: { stdout: `side-effect-ran`, }, }); itBundled("edgecase/ImportMetaMain", { files: { "/entry.ts": /* js */ ` import {other} from './other'; console.log(capture(import.meta.main), capture(require.main === module), ...other); `, "/other.ts": ` globalThis['ca' + 'pture'] = x => x; export const other = [capture(require.main === module), capture(import.meta.main)]; `, }, capture: ["false", "false", "import.meta.main", "import.meta.main"], onAfterBundle(api) { // This should not be marked as a CommonJS module api.expectFile("/out.js").not.toContain("require"); api.expectFile("/out.js").not.toContain("module"); }, }); itBundled("edgecase/ImportMetaMainTargetNode", { files: { "/entry.ts": /* js */ ` import {other} from './other'; console.log(capture(import.meta.main), capture(require.main === module), ...other); `, "/other.ts": ` globalThis['ca' + 'pture'] = x => x; export const other = [capture(require.main === module), capture(import.meta.main)]; `, }, target: "node", capture: ["false", "false", "__require.main == __require.module", "__require.main == __require.module"], onAfterBundle(api) { // This should not be marked as a CommonJS module api.expectFile("/out.js").not.toMatch(/\brequire\b/); // __require is ok api.expectFile("/out.js").not.toMatch(/[^\.:]module/); // `.module` and `node:module` are ok. }, }); itBundled("edgecase/build-cjs-module#20308", { files: { "/entry.ts": /* js */ ` import {other} from './other'; console.log(capture(import.meta.main), capture(require.main === module), ...other); `, "/other.ts": /* js */ ` globalThis['ca' + 'pture'] = x => x; export const other = [capture(require.main === module), capture(import.meta.main)]; `, }, target: "node", format: "cjs", capture: ["false", "false", "require.main == module", "require.main == module"], onAfterBundle(api) { console.log(api.readFile("/out.js")); // This should be marked as a CommonJS module api.expectFile("/out.js").toMatch(/\brequire\b/); // __require is not ok api.expectFile("/out.js").toMatch(/[^\.:]module/); // `.module` and `node:module` are not ok. }, }); itBundled("edgecase/IdentifierInEnum#13081", { files: { "/entry.ts": ` let ZZZZZZZZZ = 1; enum B { C = ZZZZZZZZZ, } console.log(B.C); `, }, run: { stdout: "1" }, }); itBundled("edgecase/DoNotMoveTaggedTemplateLiterals", { files: { "/entry.ts": ` globalThis.z = () => console.log(2) const y = await import('./second.ts'); `, "/second.ts": ` console.log(1); export const y = z\`zyx\`; `, }, run: { stdout: "1\n2" }, }); itBundled("edgecase/Latin1StringInImportedJSON", { files: { "/entry.ts": ` import x from './second.json'; console.log(x + 'a'); `, "/second.json": ` "测试" `, }, target: "bun", run: { stdout: `测试a` }, }); itBundled("edgecase/Latin1StringInImportedJSONBrowser", { files: { "/entry.ts": ` import x from './second.json'; console.log(x + 'a'); `, "/second.json": ` "测试" `, }, target: "browser", run: { stdout: `测试a` }, }); itBundled("edgecase/Latin1StringKey", { files: { "/entry.ts": ` import x from './second.json'; console.log(x["测试" + "a"]); `, "/second.json": ` {"测试a" : 123} `, }, target: "bun", run: { stdout: `123` }, }); itBundled("edgecase/Latin1StringKeyBrowser", { files: { "/entry.ts": ` import x from './second.json'; console.log(x["测试" + "a"]); `, "/second.json": ` {"测试a" : 123} `, }, target: "browser", run: { stdout: `123` }, }); itBundled("edgecase/UninitializedVariablesMoved", { files: { "/entry.ts": ` await import('./b.js'); `, "/b.js": ` export var a = 32; export var b; (function (c) { c.d = 1; })(b ?? {}); +a; `, }, minifySyntax: true, run: true, // pass if no thrown error }); itBundled("edgecase/UsingExportDefault", { files: { "/entry.ts": ` import module from "./module.ts"; console.log(module.x); `, "/module.ts": ` using a = { [Symbol.dispose]: () => { console.log("Disposing"); } }; export default {x: 1}; `, }, run: { stdout: "Disposing\n1", }, }); itBundled("edgecase/UsingExportClass", { files: { "/entry.ts": ` export class A { [Symbol.dispose](){ console.info("Disposing"); } } using a = new A(); `, }, run: { stdout: "Disposing", }, }); itBundled("edgecase/UsingExportDefaultThrows", { files: { "/entry.ts": ` import("./module.ts").catch(error => { console.log("Caught error:", error.message); }); `, "/module.ts": ` function somethingThatThrows() { throw new Error("This function throws"); } using disposable = { [Symbol.dispose]: () => { console.log("Disposing"); } }; export default somethingThatThrows(); `, }, run: { stdout: "Disposing\nCaught error: This function throws", }, }); itBundled("edgecase/UsingExportDefaultAsync", { files: { "/entry.ts": ` const result = await import("./importer.ts"); console.log(await result.default); `, "/importer.ts": ` async function main() { using disposable = { [Symbol.dispose]: () => { console.log("Disposing"); } }; return "Success"; } export default main(); `, }, run: { stdout: "Disposing\nSuccess", }, }); itBundled("edgecase/UsingDisposeThrowDoesntMask", { files: { "/entry.ts": ` using a = { [Symbol.dispose]: () => { throw new Error("Error"); } }; using b = { [Symbol.dispose]: () => { console.log("Disposing"); } } `, }, run: { error: "error: Error", stdout: "Disposing", }, }); itBundled("edgecase/UsingExportFails", { files: { "/entry.ts": ` import a from "./import.ts"; console.log(a.ok); `, "/import.ts": ` using a = { [Symbol.dispose]: () => { console.log("Disposing"); }, ok: true, }; export default a; `, }, run: { stdout: "Disposing\ntrue", }, }); itBundled("edgecase/NoOutWithTwoFiles", { files: { "/entry.ts": ` import index from './index.html' with { type: 'file' } console.log(index); `, "/index.html": ` `, }, generateOutput: false, backend: "api", onAfterApiBundle: async build => { expect(build.success).toEqual(true); expect(build.outputs).toBeArrayOfSize(2); expect(build.outputs[0].path).toEqual("./entry.js"); expect(build.outputs[0].loader).toEqual("ts"); expect(build.outputs[0].kind).toEqual("entry-point"); expect(build.outputs[1].loader).toEqual("file"); expect(build.outputs[1].kind).toEqual("asset"); expect(await build.outputs[1].text()).toEqual(""); }, }); itBundled("edgecase/OutWithTwoFiles", { backend: "cli", files: { "/entry.ts": ` import index from './index.html' with { type: 'file' } console.log(index); `, "/index.html": ` `, }, generateOutput: true, bundleErrors: { "": ["cannot write multiple output files without an output directory"], }, run: true, }); itBundled("edgecase/TSConfigPathsConfigDir", { files: { "/src/entry.ts": /* ts */ ` import { value } from "alias/foo"; import { other } from "@scope/bar"; import { nested } from "deep/path"; import { absolute } from "abs/path"; console.log(value, other, nested, absolute); `, "/src/actual/foo.ts": `export const value = "foo";`, "/src/lib/bar.ts": `export const other = "bar";`, "/src/nested/deep/file.ts": `export const nested = "nested";`, "/src/absolute.ts": `export const absolute = "absolute";`, "/src/tsconfig.json": /* json */ `{ "compilerOptions": { "baseUrl": "\${configDir}", "paths": { "alias/*": ["actual/*"], "@scope/*": ["lib/*"], "deep/path": ["nested/deep/file.ts"], "abs/*": ["\${configDir}/absolute.ts"] } } }`, }, run: { stdout: "foo bar nested absolute", }, }); itBundled("edgecase/TSConfigBaseUrlConfigDir", { files: { "/entry.ts": /* ts */ ` import { value } from "./src/subdir/module"; console.log(value); `, "/src/lib/module.ts": `export const value = "found";`, "/src/subdir/module.ts": ` import { value } from "absolute"; export { value }; `, "tsconfig.json": /* json */ `{ "compilerOptions": { "baseUrl": "\${configDir}/src/lib", "paths": { "absolute": ["./module.ts"] } } }`, }, run: { stdout: "found", }, }); itBundled("edgecase/TSConfigPathsConfigDirWildcard", { files: { "/src/entry.ts": /* ts */ ` import { one } from "prefix/one"; import { two } from "prefix/two"; import { three } from "other/three"; console.log(one, two, three); `, "/src/modules/one.ts": `export const one = "one";`, "/src/modules/two.ts": `export const two = "two";`, "/src/alternate/three.ts": `export const three = "three";`, "/src/tsconfig.json": /* json */ `{ "compilerOptions": { "baseUrl": "\${configDir}", "paths": { "prefix/*": ["modules/*"], "other/*": ["\${configDir}/alternate/*"] } } }`, }, run: { stdout: "one two three", }, }); itBundled("edgecase/TSConfigPathsConfigDirNested", { files: { "/deeply/nested/src/entry.ts": /* ts */ ` import { value } from "alias/module"; console.log(value); `, "/deeply/nested/src/actual/module.ts": `export const value = "nested";`, "/deeply/nested/src/tsconfig.json": /* json */ `{ "compilerOptions": { "baseUrl": "\${configDir}", "paths": { "alias/*": ["actual/*"] } } }`, }, run: { stdout: "nested", }, }); itBundled("edgecase/TSConfigPathsConfigDirMultiple", { files: { "/src/entry.ts": /* ts */ ` import { value } from "multi/module"; console.log(value); `, "/src/fallback/module.ts": `export const value = "fallback";`, "/src/primary/module.ts": `export const value = "primary";`, "/src/tsconfig.json": /* json */ `{ "compilerOptions": { "baseUrl": "\${configDir}", "paths": { "multi/*": [ "\${configDir}/primary/*", "\${configDir}/fallback/*" ] } } }`, }, run: { stdout: "primary", }, }); itBundled("edgecase/TSConfigPathsConfigDirInvalid", { files: { "/entry.ts": /* ts */ ` import { value } from "invalid/module"; console.log(value); `, "/tsconfig.json": /* json */ `{ "compilerOptions": { "baseUrl": "\${configDir}", "paths": { "invalid/*": ["\${configDir}/\${configDir}/*"] } } }`, }, bundleErrors: { "/entry.ts": ['Could not resolve: "invalid/module". Maybe you need to "bun install"?'], }, }); itBundled("edgecase/TSConfigPathsConfigDirBackslash", { files: { "/entry.ts": /* ts */ ` import { value } from "windows/style"; console.log(value); `, "/win/style.ts": `export const value = "windows";`, "/tsconfig.json": /* json */ `{ "compilerOptions": { "baseUrl": "\${configDir}", "paths": { "windows/*": ["win\\\\*"] } } }`, }, run: { stdout: "windows", }, }); itBundled("edgecase/TSPublicFieldMinification", { files: { "/entry.ts": /* ts */ ` export class Foo { constructor(public name: string) {} } const keys = Object.keys(new Foo('test')) if (keys.length !== 1) throw new Error('Keys length is not 1') if (keys[0] !== 'name') throw new Error('keys[0] is not "name"') console.log('success') `, }, minifySyntax: true, minifyIdentifiers: true, target: "bun", run: { stdout: "success", }, }); // https://github.com/oven-sh/bun/issues/14585 itBundled("identifiers/SameNameDifferentModulesWithMinifyIdentifiersDisabled", { files: { "/foo.js": ` { var d = 0; } export const foo = () => {} `, "/bar.js": ` // bar.js - The collision happens with this function declaration function d() {} export function bar() {d.length;} `, "/index.js": ` import { foo } from "./foo.js"; import { bar } from "./bar.js"; // Execute in order foo(); bar(); `, }, entryPoints: ["/index.js"], outfile: "/out.js", minifyIdentifiers: false, run: { stdout: "", }, }); itBundled("edgecase/MacroProtoKeyIsOwnProperty", { files: { "/entry.ts": /* js */ ` import { getData } from "./macro.ts" with { type: "macro" }; const data = getData(); console.write(JSON.stringify([ Object.getPrototypeOf(data) === Object.prototype, Object.hasOwn(data, "__proto__"), data.x, JSON.stringify(data), ])); `, "/macro.ts": /* js */ ` export function getData() { return JSON.parse('{"__proto__": {"x": 1}, "a": 2}'); } `, }, target: "bun", run: { stdout: '[true,true,null,"{\\"__proto__\\":{\\"x\\":1},\\"a\\":2}"]' }, }); itBundled("edgecase/NodeBuiltinWithoutPrefix", { files: { "/entry.ts": ` import * as hello from "node:test"; import * as world from "node:fs"; import * as etc from "console"; import * as blah from "bun:jsc"; +[hello,world,etc,blah]; `, }, target: "bun", onAfterBundle(api) { api.expectFile("out.js").toMatchInlineSnapshot(` "// @bun // entry.ts import * as hello from "node:test"; import * as world from "fs"; import * as etc from "console"; import * as blah from "bun:jsc"; +[hello, world, etc, blah]; " `); }, }); itBundled("edgecase/NodeBuiltinWithoutPrefix2", { files: { "/entry.ts": ` import * as hello from "node:test"; import * as world from "node:fs"; import * as etc from "console"; +[hello,world,etc]; `, }, target: "node", onAfterBundle(api) { api.expectFile("out.js").toMatchInlineSnapshot(` "// entry.ts import * as hello from "node:test"; import * as world from "node:fs"; import * as etc from "console"; +[hello, world, etc]; " `); }, }); itBundled("edgecase/NonAsciiIdentifierPreserved", { files: { "/entry.js": /* js */ ` class Café {} function naïve(x) { return x } class Cafá {} class 模块 {} const aπ = 1; const a𝒜 = 2; const élan = 3; console.log(JSON.stringify([Café.name, naïve.name, Cafá.name, 模块.name, aπ, a𝒜, élan])); `, }, target: "node", run: { stdout: '["Café","naïve","Cafá","模块",1,2,3]' }, onAfterBundle(api) { const out = api.readFile("/out.js"); expect(out).toContain("class Café"); expect(out).toContain("function naïve"); expect(out).toContain("class Cafá"); expect(out).toContain("class 模块"); expect(out).toContain("var aπ"); expect(out).toContain("var a𝒜"); expect(out).toContain("var élan"); expect(out).not.toContain("Caf_"); expect(out).not.toContain("na_ve"); expect(out).not.toContain("模_"); expect(out).not.toContain("var a_"); }, }); itBundled("edgecase/NonAsciiIdentifierPreservedBunTarget", { files: { "/entry.js": /* js */ ` class Café {} function naïve(x) { return x } console.log(JSON.stringify([Café.name, naïve.name])); `, }, target: "bun", run: { stdout: '["Café","naïve"]' }, onAfterBundle(api) { const out = api.readFile("/out.js"); expect(out).not.toContain("Caf_"); expect(out).not.toContain("na_ve"); }, }); // The bundler's per-edge graph walks (reachable files, tree-shaking / // code-splitting liveness, chunk part ordering, CSS discovery, TLA // validation, async propagation, dependency wrapping) used to recurse once // per import-graph edge, overflowing the stack on long linear chains. 7000 // reliably crashed the old recursive form under debug+ASAN. const deepChainDepth = 7000; const deepChainFiles = { ...Object.fromEntries( Array.from({ length: deepChainDepth - 1 }, (_, i) => [ `/m${i}.js`, `import { v${i + 1} } from "./m${i + 1}.js"; export const v${i} = v${i + 1} + 1;`, ]), ), [`/m${deepChainDepth - 1}.js`]: `export const v${deepChainDepth - 1} = 1;`, }; itBundled("edgecase/DeepImportChain", { files: { "/entry.js": `import { v0 } from "./m0.js"; console.log(v0);`, ...deepChainFiles, }, backend: "cli", run: { stdout: String(deepChainDepth) }, }); // Top-level await in the entry makes `validate_tla` / `propagate_async` walk // the chain; `await import()` of an ESM head without splitting wraps the // whole chain, driving `DependencyWrapper::wrap` through it. The wrapped // output initializes module N by calling module N+1's init, so running it // would recurse at runtime; checking for the deepest wrapper is enough. itBundled("edgecase/DeepImportChainWrappedTLA", { files: { "/entry.js": `await 0; const { v0 } = await import("./m0.js"); console.log(v0);`, ...deepChainFiles, }, backend: "cli", onAfterBundle(api) { const out = api.readFile("out.js"); expect(out).toContain(`init_m${deepChainDepth - 2}`); }, }); // Diamond-shaped DAG (half the modules have two importers). The code- // splitting reachability pass tracks min distance-from-entry for each file; // a LIFO walk with distance relaxation does O(V*E) re-visits here, so this // guards that the pass stays O(V+E). Plain fs writes because itBundled's // fixture pipeline is too slow at this scale under debug+ASAN. test.concurrent( "edgecase/DeepImportDiamondDAG", async () => { const N = 20000; using dir = tempDir("deep-import-dag", {}); const root = String(dir); for (let i = 0; i < N; i++) { const deps: number[] = []; if (i + 1 < N) deps.push(i + 1); if (2 * i + 3 < N) deps.push(2 * i + 3); writeFileSync( join(root, `m${i}.js`), deps.map(d => `import { v as v${d} } from "./m${d}.js";`).join("\n") + `\nexport const v = ${i}${deps.map(d => ` + v${d}`).join("")};\n`, ); } writeFileSync(join(root, "entry.js"), `import { v } from "./m0.js"; console.log(typeof v);\n`); await using build = Bun.spawn({ cmd: [bunExe(), "build", "entry.js", "--outfile=out.js"], cwd: root, env: bunEnv, stdio: ["ignore", "pipe", "pipe"], timeout: 60_000, }); const [, stderr, exitCode] = await Promise.all([build.stdout.text(), build.stderr.text(), build.exited]); expect({ stderr, exitCode, signalCode: build.signalCode }).toEqual({ stderr: "", exitCode: 0, signalCode: null }); await using run = Bun.spawn({ cmd: [bunExe(), "out.js"], cwd: root, env: bunEnv, stdio: ["ignore", "pipe", "pipe"], }); const [stdout, runStderr, runExit] = await Promise.all([run.stdout.text(), run.stderr.text(), run.exited]); expect({ stdout, stderr: runStderr, exitCode: runExit }).toEqual({ stdout: "number\n", stderr: "", exitCode: 0, }); }, 120_000, ); itBundled("edgecase/NonAsciiPathDerivedWrapperName", { files: { "/entry.ts": /* js */ ` const a = require("./模块.cjs"); const b = require("./foo\u2014bar.cjs"); console.log(a.x, b.y); `, "/模块.cjs": /* js */ ` module.exports = { x: 42 }; `, "/foo\u2014bar.cjs": /* js */ ` module.exports = { y: 7 }; `, }, target: "node", run: { stdout: "42 7" }, onAfterBundle(api) { const out = api.readFile("/out.js"); // ID_Continue code points in the path basename are preserved. expect(out).toContain("require_模块"); expect(out).not.toContain("require_模_"); expect(out).not.toContain("require___"); // Non-ID_Continue code points (U+2014 em dash) are still replaced with _. expect(out).toContain("require_foo_bar"); expect(out).not.toContain("require_foo\u2014bar"); }, }); // `delete (null ?? ns.x)` evaluates its operand to a value, so the result is // `true` with no side effect. When bundling rewrites `ns.x` to the local // binding (EImportIdentifier) after folding `??`, the printer must re-wrap // the operand so `delete` still sees a value instead of the binding itself. // Without the wrap the output is `delete x`, a strict-mode SyntaxError. itBundled("edgecase/DeleteFoldedNamespacePropertyRef", { files: { "/entry.js": /* js */ ` import * as ns from "./m.js"; console.log(delete (null ?? ns.x), ns.x); console.log(delete (0, ns.x), ns.x); console.log(delete (true ? ns.x : 0), ns.x); `, "/m.js": /* js */ ` export let x = 1; `, }, onAfterBundle: api => { const code = api.readFile("out.js"); expect(code).not.toMatch(/delete\s+x\b/); expect(code).not.toMatch(/delete\s+ns\.x\b/); }, run: { stdout: "true 1\ntrue 1\ntrue 1" }, }); itBundled("edgecase/DeleteFoldedNamespacePropertyRefMinify", { files: { "/entry.js": /* js */ ` import * as ns from "./m.js"; console.log(delete (null ?? ns.x), ns.x); `, "/m.js": /* js */ ` export let x = 1; `, }, minifySyntax: true, minifyWhitespace: true, run: { stdout: "true 1" }, }); // Same path via a direct named import: the identifier becomes an // EImportIdentifier during the visit pass. itBundled("edgecase/DeleteFoldedImportedBindingRef", { files: { "/entry.js": /* js */ ` import { x } from "./m.js"; console.log(delete (null ?? x), x); `, "/m.js": /* js */ ` export let x = 1; `, }, onAfterBundle: api => { expect(api.readFile("out.js")).not.toMatch(/delete\s+x\b/); }, run: { stdout: "true 1" }, }); // The bundler rewrites bare `require`/`require.main`/`require.resolve` to an // ERequireCallTarget / ERequireMain / ERequireResolveCallTarget that prints // as `__require` / `__require.main` / `__require.resolve`. itBundled("edgecase/DeleteFoldedRequireRefs", { files: { "/entry.js": /* js */ ` console.log(delete (null ?? require)); console.log(delete (null ?? require.main)); console.log(delete (null ?? require.resolve)); `, }, onAfterBundle: api => { const code = api.readFile("out.js"); expect(code).not.toMatch(/delete\s+__require\b/); expect(code).not.toMatch(/delete\s+require\b/); }, run: { stdout: "true\ntrue\ntrue" }, }); // The visit pass substitutes unbound `undefined` to EUndefined, which // `print_undefined` emits as the bare identifier when not minifying. itBundled("edgecase/DeleteFoldedUndefinedRef", { files: { "/entry.js": /* js */ ` console.log(delete (null ?? undefined)); `, }, onAfterBundle: api => { expect(api.readFile("out.js")).not.toMatch(/delete\s+undefined\b/); }, run: { stdout: "true" }, }); // `import.meta.main` is rewritten to EImportMetaMain; under `target: node` // that prints as `__require.main == __require.module` without its own paren // wrap, so an unwrapped `delete` would bind to `__require.main`. itBundled("edgecase/DeleteFoldedImportMetaMainRef", { files: { "/entry.js": /* js */ ` console.log(delete (null ?? import.meta.main)); `, }, onAfterBundle: api => { expect(api.readFile("out.js")).not.toMatch(/delete\s+import\.meta\.main\b/); }, run: { stdout: "true" }, }); itBundled("edgecase/DeleteFoldedImportMetaMainRefNode", { files: { "/entry.js": /* js */ ` console.log(delete (null ?? import.meta.main)); `, }, target: "node", onAfterBundle: api => { expect(api.readFile("out.js")).not.toMatch(/delete\s+__require\.main\b/); }, run: { runtime: "node", stdout: "true" }, }); // A same-file `const enum` member is inlined to an EInlinedEnum wrapping an // ENumber during the visit pass, so the NaN/Infinity check has to look // through the wrapper. itBundled("edgecase/DeleteFoldedInlinedConstEnumNaN", { files: { "/entry.ts": /* ts */ ` const enum E { N = 0/0, I = 1/0, V = 1 } console.log(delete (null ?? E.N), delete (null ?? E.I), delete (null ?? E.V)); `, }, onAfterBundle: api => { const code = api.readFile("out.js"); expect(code).not.toMatch(/delete\s+NaN\b/); expect(code).not.toMatch(/delete\s+Infinity\b/); }, run: { stdout: "true true true" }, }); // https://github.com/oven-sh/bun/issues/14509 // A require() in the catch handler of a try/catch is the common "fallback // require" pattern and should not fail the build when unresolvable. itBundled("edgecase/RequireInCatchBody", { files: { "/entry.js": /* js */ ` let v; try { v = require('pkg'); } catch (e) { v = require('pkg/sub.cjs'); } console.log(v); `, "/node_modules/pkg/package.json": JSON.stringify({ name: "pkg", exports: { ".": "./index.js" }, }), "/node_modules/pkg/index.js": `module.exports = "main";`, }, target: "bun", run: { stdout: "main" }, }); itBundled("edgecase/RequireInCatchBodyFromNodeModules", { files: { "/entry.js": `console.log(require('lib'));`, "/node_modules/lib/package.json": JSON.stringify({ name: "lib", main: "index.js" }), "/node_modules/lib/index.js": /* js */ ` let v; try { v = require('pkg'); } catch (e) { v = require('pkg/dist/node/pkg.cjs'); } module.exports = v; `, "/node_modules/pkg/package.json": JSON.stringify({ name: "pkg", exports: { ".": "./index.js" }, }), "/node_modules/pkg/index.js": `module.exports = "pkg-main";`, }, target: "bun", run: { stdout: "pkg-main" }, }); itBundled("edgecase/RequireInCatchBodyBothUnresolved", { files: { "/entry.js": /* js */ ` exports.load = function () { try { return require('does-not-exist-a'); } catch (e) { return require('does-not-exist-b'); } }; `, }, target: "bun", runtimeFiles: { "/test.js": /* js */ ` const { load } = require('./out.js'); try { load(); console.log("no throw"); } catch (e) { console.log("threw: " + e.message.includes("does-not-exist-b")); } `, }, run: { file: "/test.js", stdout: "threw: true" }, }); itBundled("edgecase/RequireResolveInCatchBody", { files: { "/entry.js": /* js */ ` let v; try { v = require.resolve('does-not-exist-a'); } catch (e) { v = require.resolve('does-not-exist-b'); } console.log(typeof v); `, }, target: "bun", onAfterBundle(api) { api.expectFile("/out.js").toContain("does-not-exist-b"); }, }); itBundled("edgecase/AwaitImportInCatchBody", { files: { "/entry.js": /* js */ ` async function load() { try { return await import('does-not-exist-a'); } catch (e) { return await import('does-not-exist-b'); } } load().catch(e => console.log("caught")); `, }, target: "bun", run: { stdout: "caught" }, }); itBundled("edgecase/RequireInFinallyStillErrors", { files: { "/entry.js": /* js */ ` try { console.log("ok"); } catch (e) { } finally { require('does-not-exist'); } `, }, target: "bun", bundleErrors: { "/entry.js": [`Could not resolve: "does-not-exist". Maybe you need to "bun install"?`], }, }); itBundled("edgecase/RequireAfterCatchBodyStillErrors", { files: { "/entry.js": /* js */ ` try { require('does-not-exist-a'); } catch (e) { require('does-not-exist-b'); } require('does-not-exist-c'); `, }, target: "bun", bundleErrors: { "/entry.js": [`Could not resolve: "does-not-exist-c". Maybe you need to "bun install"?`], }, }); // A resolved-but-disabled path (node builtin under --target=browser, or a // `"browser": { "pkg": false }` remap) in a try/catch body must keep emitting // the empty-module stub, not a runtime throw. itBundled("edgecase/RequireDisabledInCatchBodyStaysEmpty", { files: { "/entry.js": /* js */ ` try { throw 0; } catch (e) { const a = require('fs'); const b = require('mapped-false'); if (a instanceof Error || b instanceof Error) throw new Error("unreachable"); console.log("ok"); } `, "/package.json": JSON.stringify({ name: "app", browser: { "mapped-false": false } }), "/node_modules/mapped-false/package.json": JSON.stringify({ name: "mapped-false", main: "index.js" }), "/node_modules/mapped-false/index.js": `module.exports = "real";`, }, target: "browser", onAfterBundle(api) { api.expectFile("/out.js").not.toContain("Cannot require module"); }, run: { stdout: "ok" }, }); itBundled("edgecase/RequireDisabledInTryBodyStaysEmpty", { files: { "/entry.js": /* js */ ` let hit = ""; try { const x = require('mapped-false'); hit = "try:" + (x instanceof Error); } catch (e) { hit = "catch:" + e.message; } console.log(hit); `, "/package.json": JSON.stringify({ name: "app", browser: { "mapped-false": false } }), "/node_modules/mapped-false/package.json": JSON.stringify({ name: "mapped-false", main: "index.js" }), "/node_modules/mapped-false/index.js": `module.exports = "real";`, }, target: "browser", onAfterBundle(api) { api.expectFile("/out.js").not.toContain("Cannot require module"); }, run: { stdout: "try:false" }, }); }); for (const backend of ["api", "cli"] as const) { describe(`bundler_edgecase/${backend}`, () => { itBundled("edgecase/ProcessEnvArbitrary", { files: { "/entry.js": /* js */ ` capture(process.env.ARBITRARY); `, }, target: "browser", backend, capture: ["process.env.ARBITRARY"], env: { ARBITRARY: "secret environment stuff!", }, }); }); }