import { describe, expect } from "bun:test"; import { isBroken, isWindows } from "harness"; import { join } from "node:path"; import { itBundled } from "./expectBundled"; 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 {"ns":2,"default":1,"def2":3}', }, }); 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 `, }, }); 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/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: "qYACA,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", }, }, }); 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 `, }, }); 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 `, }, }); 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, }); // TODO(@paperclover): test every case of this. I had already tested it manually, but it may break later const requireTranspilationListESM = [ // input, output:bun, output:node ["require", "import.meta.require", "__require"], ["typeof require", "import.meta.require", "typeof __require"], ["typeof require", "import.meta.require", "typeof __require"], ]; // // itBundled('edgecase/RequireTranspilation') 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/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]; " `); }, }); }); 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!", }, }); }); }