import assert from "assert";
import { itBundled, testForFile } from "../expectBundled";
var { describe, test, expect } = testForFile(import.meta.path);
// Tests ported from:
// https://github.com/evanw/esbuild/blob/main/internal/bundler_tests/bundler_ts_test.go
// For debug, all files are written to $TEMP/bun-bundle-tests/
describe("bundler", () => {
itBundled("ts/DeclareConst", {
files: {
"/entry.ts": /* ts */ `
declare const require: any
declare const exports: any;
declare const module: any
declare const foo: any
let foo = bar()
`,
},
onAfterBundle(api) {
assert(api.readFile("/out.js").includes("foo = bar()"), 'includes "foo = bar()"');
assert(!api.readFile("/out.js").includes("require"), 'does not include "require"');
assert(!api.readFile("/out.js").includes("exports"), 'does not include "exports"');
assert(!api.readFile("/out.js").includes("module"), 'does not include "module"');
assert(!api.readFile("/out.js").includes("const foo"), 'does not include "const foo"');
},
});
itBundled("ts/DeclareLet", {
files: {
"/entry.ts": /* ts */ `
declare let require: any
declare let exports: any;
declare let module: any
declare let foo: any
let foo = bar()
`,
},
onAfterBundle(api) {
assert(api.readFile("/out.js").includes("foo = bar()"), 'includes "foo = bar()"');
assert(!api.readFile("/out.js").includes("require"), 'does not include "require"');
assert(!api.readFile("/out.js").includes("exports"), 'does not include "exports"');
assert(!api.readFile("/out.js").includes("module"), 'does not include "module"');
},
});
itBundled("ts/DeclareVar", {
files: {
"/entry.ts": /* ts */ `
declare var require: any
declare var exports: any;
declare var module: any
declare var foo: any
let foo = bar()
`,
},
onAfterBundle(api) {
assert(api.readFile("/out.js").includes("foo = bar()"), 'includes "foo = bar()"');
assert(!api.readFile("/out.js").includes("require"), 'does not include "require"');
assert(!api.readFile("/out.js").includes("exports"), 'does not include "exports"');
assert(!api.readFile("/out.js").includes("module"), 'does not include "module"');
},
});
itBundled("ts/DeclareClass", {
files: {
"/entry.ts": /* ts */ `
declare class require {}
declare class exports {};
declare class module {}
declare class foo {}
let foo = bar()
`,
},
onAfterBundle(api) {
assert(api.readFile("/out.js").includes("foo = bar()"), 'includes "foo = bar()"');
assert(!api.readFile("/out.js").includes("require"), 'does not include "require"');
assert(!api.readFile("/out.js").includes("exports"), 'does not include "exports"');
assert(!api.readFile("/out.js").includes("module"), 'does not include "module"');
assert(!api.readFile("/out.js").includes("class"), 'does not include "class"');
},
});
itBundled("ts/DeclareClassFields", {
files: {
"/entry.ts": /* ts */ `
import './setup'
import './define-false'
import './define-true'
`,
"./setup.js": /* js */ `
globalThis.A = "global.A"
globalThis.a = "global.a"
globalThis.B = "global.B"
globalThis.b = "global.b"
globalThis.C = "global.C"
globalThis.c = "global.c"
globalThis.D = "global.D"
globalThis.d = "global.d"
`,
"/define-false/index.ts": /* ts */ `
class Foo {
a = 1
declare b: number
[(() => null, c)] = 3
declare [(() => null, d)]: number
static A = 5
static declare B: number
static [(() => null, C)] = 7
static declare [(() => null, D)]: number
}
const props = x => JSON.stringify({ ...Object.getOwnPropertyDescriptors(x), length: undefined, prototype: undefined })
console.log('Foo ', props(Foo))
console.log('new Foo', props(new Foo()))
`,
"/define-true/index.ts": /* ts */ `
class Bar {
a
declare b
[(() => null, c)]
declare [(() => null, d)]
static A
static declare B
static [(() => null, C)]
static declare [(() => null, D)]
}
const props = x => JSON.stringify({ ...Object.getOwnPropertyDescriptors(x), length: undefined, prototype: undefined })
console.log('Bar ', props(Bar))
console.log('new Bar', props(new Bar()))
`,
"/define-true/tsconfig.json": /* json */ `
{
"compilerOptions": {
"useDefineForClassFields": true
}
}
`,
},
run: {
stdout: `
Foo {"name":{"value":"Foo","writable":false,"enumerable":false,"configurable":true},"A":{"value":5,"writable":true,"enumerable":true,"configurable":true},"global.C":{"value":7,"writable":true,"enumerable":true,"configurable":true}}
new Foo {"a":{"value":1,"writable":true,"enumerable":true,"configurable":true},"global.c":{"value":3,"writable":true,"enumerable":true,"configurable":true}}
Bar {"name":{"value":"Bar","writable":false,"enumerable":false,"configurable":true},"A":{"writable":true,"enumerable":true,"configurable":true},"global.C":{"writable":true,"enumerable":true,"configurable":true}}
new Bar {"a":{"writable":true,"enumerable":true,"configurable":true},"global.c":{"writable":true,"enumerable":true,"configurable":true}}
`,
},
});
itBundled("ts/DeclareFunction", {
files: {
"/entry.ts": /* ts */ `
declare function require(): void
declare function exports(): void;
declare function module(): void
declare function foo() {}
let foo = bar()
`,
},
onAfterBundle(api) {
assert(api.readFile("/out.js").includes("foo = bar()"), 'includes "foo = bar()"');
assert(!api.readFile("/out.js").includes("require"), 'does not include "require"');
assert(!api.readFile("/out.js").includes("exports"), 'does not include "exports"');
assert(!api.readFile("/out.js").includes("module"), 'does not include "module"');
assert(!api.readFile("/out.js").includes("function"), 'does not include "function"');
},
});
itBundled("ts/DeclareNamespace", {
files: {
"/entry.ts": /* ts */ `
declare namespace require {}
declare namespace exports {};
declare namespace module {}
declare namespace foo {}
let foo = bar()
`,
},
onAfterBundle(api) {
assert(api.readFile("/out.js").includes("foo = bar()"), 'includes "foo = bar()"');
assert(!api.readFile("/out.js").includes("require"), 'does not include "require"');
assert(!api.readFile("/out.js").includes("exports"), 'does not include "exports"');
assert(!api.readFile("/out.js").includes("module"), 'does not include "module"');
assert(!api.readFile("/out.js").includes("namespace"), 'does not include "namespace"');
},
});
itBundled("ts/DeclareEnum", {
files: {
"/entry.ts": /* ts */ `
declare enum require {}
declare enum exports {};
declare enum module {}
declare enum foo {}
let foo = bar()
`,
},
onAfterBundle(api) {
assert(api.readFile("/out.js").includes("foo = bar()"), 'includes "foo = bar()"');
assert(!api.readFile("/out.js").includes("require"), 'does not include "require"');
assert(!api.readFile("/out.js").includes("exports"), 'does not include "exports"');
assert(!api.readFile("/out.js").includes("module"), 'does not include "module"');
assert(!api.readFile("/out.js").includes("enum"), 'does not include "enum"');
},
});
itBundled("ts/DeclareConstEnum", {
files: {
"/entry.ts": /* ts */ `
declare const enum require {}
declare const enum exports {};
declare const enum module {}
declare const enum foo {}
let foo = bar()
`,
},
onAfterBundle(api) {
assert(api.readFile("/out.js").includes("foo = bar()"), 'includes "foo = bar()"');
assert(!api.readFile("/out.js").includes("require"), 'does not include "require"');
assert(!api.readFile("/out.js").includes("exports"), 'does not include "exports"');
assert(!api.readFile("/out.js").includes("module"), 'does not include "module"');
assert(!api.readFile("/out.js").includes("enum"), 'does not include "enum"');
assert(!api.readFile("/out.js").includes("const"), 'does not include "const"');
},
});
itBundled("ts/ConstEnumComments", {
// When it comes time to implement this inlining, we may decide we do NOT
// want to insert helper comments.
todo: true,
files: {
"/bar.ts": /* ts */ `
export const enum Foo {
"%/*" = 1,
"*/%" = 2,
}
`,
"/foo.ts": /* ts */ `
import { Foo } from "./bar";
const enum Bar {
"%/*" = 1,
"*/%" = 2,
}
console.log(JSON.stringify({
'should have comments': [
Foo["%/*"],
Bar["%/*"],
],
'should not have comments': [
Foo["*/%"],
Bar["*/%"],
],
}));
`,
},
entryPoints: ["/foo.ts"],
onAfterBundle(api) {
assert(!api.readFile("/out.js").match(/var|let|const/), "should have inlined all enum constants");
assert(!api.readFile("/out.js").match(/\*\/%/), "should not include '*/%' anywhere");
assert(
[...api.readFile("/out.js").matchAll(/1\s*\/\* %\/\* \*\//g)].length === 2,
"should have 2 comments for '1'",
);
assert(
[...api.readFile("/out.js").matchAll(/2\s*\/\*/g)].length === 0,
"should have 0 comments for '2' since */ will break the comment syntax",
);
},
run: {
stdout: `{"should have comments":[1,1],"should not have comments":[2,2]}`,
},
});
itBundled("ts/ImportEmptyNamespace", {
files: {
"/entry.ts": /* ts */ `
import {REMOVE} from './ns.ts'
function foo(): REMOVE.type {}
foo();
`,
"/ns.ts": `export namespace REMOVE { type type = number }`,
},
dce: true,
run: true,
});
itBundled("ts/ImportMissingES6", {
files: {
"/entry.ts": /* ts */ `
import fn, {x as a, y as b} from './foo'
console.log(fn(a, b))
`,
"/foo.js": `export const x = 123;`,
},
bundleErrors: {
"/entry.ts": [
`No matching export in "foo.js" for import "default"`,
`No matching export in "foo.js" for import "y"`,
],
},
});
itBundled("ts/ImportMissingUnusedES6", {
files: {
"/entry.ts": `import fn, {x as a, y as b} from './foo'`,
"/foo.js": `export const x = 123`,
},
// goal for this test is there is no error. we dont really care about the output
});
itBundled("ts/ExportMissingES6", {
todo: true,
files: {
"/entry.js": /* js */ `
import * as ns from './foo'
console.log(JSON.stringify(ns))
`,
// the reason this doesnt error in TS is because `nope` can be a type
"/foo.ts": `export {nope} from './bar'`,
"/bar.js": `export const yep = 123`,
},
run: {
stdout: `{}`,
},
});
itBundled("ts/ImportMissingFile", {
files: {
"/entry.ts": /* ts */ `
import {Something} from './doesNotExist.ts'
let foo = new Something
`,
},
bundleErrors: {
"/entry.ts": [`Could not resolve: "./doesNotExist.ts"`],
},
});
itBundled("ts/ImportTypeOnlyFile", {
files: {
"/entry.ts": /* ts */ `
import {SomeType1} from './doesNotExist1.ts'
import {SomeType2} from './doesNotExist2.ts'
function bar() { return 2; }
let foo: SomeType1 = bar()
console.log(foo);
`,
},
run: {
stdout: "2",
},
});
itBundled("ts/ExportEquals", {
files: {
"/a.ts": /* ts */ `
import b from './b.ts'
console.log(JSON.stringify(b))
`,
"/b.ts": /* ts */ `
export = [123, foo]
function foo() {}
`,
},
run: {
stdout: `[123,null]`,
},
});
itBundled("ts/ExportNamespace", {
files: {
"/a.ts": /* ts */ `
import {Foo} from './b.ts'
console.log(JSON.stringify(new Foo))
console.log(Foo.foo)
console.log(Foo.bar)
`,
"/b.ts": /* ts */ `
export class Foo {}
export namespace Foo {
export let foo = 1
}
export namespace Foo {
export let bar = 2
}
`,
},
run: {
stdout: `{}\n1\n2`,
},
});
itBundled("ts/MinifyEnum", {
todo: true,
files: {
"/a.ts": `enum Foo { A, B, C = Foo }\ncapture(Foo)`,
// "/b.ts": `export enum Foo { X, Y, Z = Foo }`,
},
entryPoints: ["/a.ts"],
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
bundling: false,
onAfterBundle(api) {
const a = api.readFile("/out.js");
api.writeFile("/out.edited.js", a.replace(/capture\((.*?)\)/, `export const Foo = $1`));
// const b = api.readFile("/out/b.js");
// make sure the minification trick "enum[enum.K=V]=K" is used, but `enum`
assert(a.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.A=0]=["']A["']/), "should be using enum minification trick (1)");
assert(a.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.B=1]=["']B["']/), "should be using enum minification trick (2)");
assert(a.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.C=[a-zA-Z$]]=["']C["']/), "should be using enum minification trick (3)");
// assert(b.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.X=0]=["']X["']/), "should be using enum minification trick (4)");
// assert(b.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.Y=1]=["']Y["']/), "should be using enum minification trick (5)");
// assert(b.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.Z=[a-zA-Z$]]=["']Z["']/), "should be using enum minification trick (6)");
},
runtimeFiles: {
"/test.js": /* js */ `
import {Foo as FooA} from './out/a.edited.js'
// import {Foo as FooB} from './out/b.js'
import assert from 'assert';
assert.strictEqual(FooA.A, 0, 'a.ts Foo.A')
assert.strictEqual(FooA.B, 1, 'a.ts Foo.B')
assert.strictEqual(FooA.C, Foo, 'a.ts Foo.C')
assert.strictEqual(FooA[0], 'A', 'a.ts Foo[0]')
assert.strictEqual(FooA[1], 'B', 'a.ts Foo[1]')
assert.strictEqual(FooA[FooA], 'C', 'a.ts Foo[Foo]')
// assert.strictEqual(FooB.X, 0, 'b.ts Foo.X')
// assert.strictEqual(FooB.Y, 1, 'b.ts Foo.Y')
// assert.strictEqual(FooB.Z, FooB, 'b.ts Foo.Z')
// assert.strictEqual(FooB[0], 'X', 'b.ts Foo[0]')
// assert.strictEqual(FooB[1], 'Y', 'b.ts Foo[1]')
// assert.strictEqual(FooB[FooB], 'Z', 'b.ts Foo[Foo]')
`,
},
});
itBundled("ts/MinifyEnumExported", {
todo: true,
files: {
"/b.ts": `export enum Foo { X, Y, Z = Foo }`,
},
entryPoints: ["/b.ts"],
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
bundling: false,
onAfterBundle(api) {
const b = api.readFile("/out.js");
// make sure the minification trick "enum[enum.K=V]=K" is used, but `enum`
assert(b.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.X=0]=["']X["']/), "should be using enum minification trick (4)");
assert(b.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.Y=1]=["']Y["']/), "should be using enum minification trick (5)");
assert(b.match(/\b[a-zA-Z$]\[[a-zA-Z$]\.Z=[a-zA-Z$]]=["']Z["']/), "should be using enum minification trick (6)");
},
runtimeFiles: {
"/test.js": /* js */ `
import {Foo as FooB} from './out.js'
import assert from 'assert';
assert.strictEqual(FooB.X, 0, 'b.ts Foo.X')
assert.strictEqual(FooB.Y, 1, 'b.ts Foo.Y')
assert.strictEqual(FooB.Z, FooB, 'b.ts Foo.Z')
assert.strictEqual(FooB[0], 'X', 'b.ts Foo[0]')
assert.strictEqual(FooB[1], 'Y', 'b.ts Foo[1]')
assert.strictEqual(FooB[FooB], 'Z', 'b.ts Foo[Foo]')
`,
},
});
itBundled("ts/MinifyNestedEnum", {
files: {
"/a.ts": `function foo(arg) { enum Foo { A, B, C = Foo, D = arg } return Foo }\ncapture(foo)`,
"/b.ts": `export function foo(arg) { enum Foo { X, Y, Z = Foo, W = arg } return Foo }`,
},
entryPoints: ["/a.ts", "/b.ts"],
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
onAfterBundle(api) {
const a = api.readFile("/out/a.js");
api.writeFile("/out/a.edited.js", a.replace(/capture\((.*?)\)/, `export const Foo = $1`));
},
runtimeFiles: {
"/test.js": /* js */ `
import {foo as fooA} from './out/a.edited.js'
import {foo as fooB} from './out/b.js'
import assert from 'assert';
const S = Symbol('S')
const FooA = fooA(S)
const FooB = fooB(S)
assert.strictEqual(FooA.A, 0, 'a.ts Foo.A')
assert.strictEqual(FooA.B, 1, 'a.ts Foo.B')
assert.strictEqual(FooA.C, Foo, 'a.ts Foo.C')
assert.strictEqual(FooA.D, S, 'a.ts Foo.D')
assert.strictEqual(FooA[0], 'A', 'a.ts Foo[0]')
assert.strictEqual(FooA[1], 'B', 'a.ts Foo[1]')
assert.strictEqual(FooA[FooA], 'C', 'a.ts Foo[Foo]')
assert.strictEqual(FooA[S], 'D', 'a.ts Foo[S]')
assert.strictEqual(FooB.X, 0, 'b.ts Foo.X')
assert.strictEqual(FooB.Y, 1, 'b.ts Foo.Y')
assert.strictEqual(FooB.Z, FooB, 'b.ts Foo.Z')
assert.strictEqual(FooB.W, S, 'b.ts Foo.W')
assert.strictEqual(FooB[0], 'X', 'b.ts Foo[0]')
assert.strictEqual(FooB[1], 'Y', 'b.ts Foo[1]')
assert.strictEqual(FooB[FooB], 'Z', 'b.ts Foo[Foo]')
assert.strictEqual(FooB[S], 'W', 'b.ts Foo[S]')
`,
},
});
itBundled("ts/MinifyNestedEnumNoLogicalAssignment", {
files: {
"/a.ts": `function foo(arg) { enum Foo { A, B, C = Foo, D = arg } return Foo }\ncapture(foo)`,
"/b.ts": `export function foo(arg) { enum Foo { X, Y, Z = Foo, W = arg } return Foo }`,
},
entryPoints: ["/a.ts", "/b.ts"],
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
bundling: false,
unsupportedJSFeatures: ["logical-assignment"],
onAfterBundle(api) {
const a = api.readFile("/out/a.js");
assert(a.includes("A"), "a should not be empty");
assert(!a.includes("||="), "a should not use logical assignment");
const b = api.readFile("/out/b.js");
assert(b.includes("X"), "b should not be empty");
assert(!b.includes("||="), "b should not use logical assignment");
},
});
itBundled("ts/MinifyNestedEnumNoArrow", {
files: {
"/a.ts": `function foo() { enum Foo { A, B, C = Foo } return Foo }`,
"/b.ts": `export function foo() { enum Foo { X, Y, Z = Foo } return Foo }`,
},
entryPoints: ["/a.ts", "/b.ts"],
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
outdir: "/",
bundling: false,
unsupportedJSFeatures: ["arrow"],
onAfterBundle(api) {
const a = api.readFile("/a.js");
assert(a.includes("A"), "a should not be empty");
assert(!a.includes("=>"), "a should not use arrow");
const b = api.readFile("/b.js");
assert(b.includes("X"), "b should not be empty");
assert(!b.includes("=>"), "b should not use arrow");
},
});
itBundled("ts/MinifyNamespace", {
files: {
"/a.ts": /* ts */ `
namespace Foo {
export namespace Bar {
foo(Foo, Bar)
}
}
capture(Foo)
`,
"/b.ts": /* ts */ `
export namespace Foo {
export namespace Bar {
foo(Foo, Bar)
}
}
`,
},
entryPoints: ["/a.ts", "/b.ts"],
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
onAfterBundle(api) {
api.writeFile("/out/a.edited.js", api.readFile("/out/a.js").replace(/capture\((.*?)\)/, `export const Foo = $1`));
},
runtimeFiles: {
"/test.js": /* js */ `
let called = false;
globalThis.foo = (a, b) => called = true;
await import('./out/a.edited.js');
assert(called, 'foo should be called from a.ts');
called = false;
await import('./out/b.js');
assert(called, 'foo should be called from b.ts');
`,
},
});
itBundled("ts/MinifyNamespaceNoLogicalAssignment", {
files: {
"/a.ts": /* ts */ `
namespace Foo {
export namespace Bar {
foo(Foo, Bar)
}
}
`,
"/b.ts": /* ts */ `
export namespace Foo {
export namespace Bar {
foo(Foo, Bar)
}
}
`,
},
entryPoints: ["/a.ts", "/b.ts"],
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
outdir: "/",
bundling: false,
unsupportedJSFeatures: ["logical-assignment"],
onAfterBundle(api) {
const a = api.readFile("/a.js");
assert(a.includes("Bar"), "a should not be empty");
assert(!a.includes("||="), "a should not use logical assignment");
const b = api.readFile("/b.js");
assert(b.includes("Bar"), "b should not be empty");
assert(!b.includes("||="), "b should not use logical assignment");
},
});
itBundled("ts/MinifyNamespaceNoArrow", {
files: {
"/a.ts": /* ts */ `
namespace Foo {
export namespace Bar {
foo(Foo, Bar)
}
}
`,
"/b.ts": /* ts */ `
export namespace Foo {
export namespace Bar {
foo(Foo, Bar)
}
}
`,
},
entryPoints: ["/a.ts", "/b.ts"],
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
outdir: "/",
bundling: false,
unsupportedJSFeatures: ["arrow"],
onAfterBundle(api) {
const a = api.readFile("/a.js");
assert(a.includes("foo"), "a should not be empty");
assert(!a.includes("=>"), "a should not use arrow");
const b = api.readFile("/b.js");
assert(b.includes("foo"), "b should not be empty");
assert(!b.includes("=>"), "b should not use arrow");
},
});
itBundled("ts/MinifyDerivedClass", {
files: {
"/entry.ts": /* ts */ `
class Foo extends Bar {
foo = 1;
bar = 2;
constructor() {
super();
foo();
bar();
}
}
export {Foo}
`,
},
minifySyntax: true,
runtimeFiles: {
"/test.js": /* js */ `
let calledFoo = false;
let calledBar = false;
globalThis.foo = () => calledFoo = true;
globalThis.bar = () => calledBar = true;
globalThis.Bar = class Bar {
constructor() {
console.log('super')
this.hello = 3;
}
};
const {Foo} = await import('./entry.js');
import assert from 'assert';
const instance = new Foo();
console.log(instance.foo, instance.bar, instance.hello);
assert(calledFoo, 'foo should be called');
assert(calledBar, 'bar should be called');
`,
},
run: {
file: "/test.js",
stdout: "super\n1 2 3",
},
});
itBundled("ts/ImportVsLocalCollisionAllTypes", {
files: {
"/entry.ts": /* ts */ `
import {a, b, c, d, e} from './other.ts'
let a
const b = 0
var c
function d() { return 5; }
class e { constructor() { this.prop = 2; }}
console.log(JSON.stringify([a, b, c, d(), new e]))
`,
"/other.ts": ``,
},
run: {
stdout: '[null,0,null,5,{"prop":2}]',
},
});
itBundled("ts/ImportVsLocalCollisionMixed", {
files: {
"/entry.ts": /* ts */ `
import {a, b, c, d, e, real} from './other.ts'
let a
const b = 0
var c
function d() { return 5; }
class e { constructor() { this.prop = 2; }}
console.log(JSON.stringify([a, b, c, d(), new e, real]))
`,
"/other.ts": `export let real = 123`,
},
run: {
stdout: '[null,0,null,5,{"prop":2},123]',
},
});
itBundled("ts/ImportEqualsEliminationTest", {
todo: true,
files: {
"/entry.ts": /* ts */ `
import a = foo.a
import b = a.b
import c = b.c
import x = foo.x
import y = x.y
import z = y.z
export let bar = c
`,
},
runtimeFiles: {
"/test.js": /* js */ `
globalThis.foo = {
a: { b: { c: 123 } },
get x() {
throw new Error('should not be called')
}
};
const {bar} = await import('./out.js');
console.log(bar);
`,
},
run: {
file: "/test.js",
stdout: "123",
},
});
itBundled("ts/ImportEqualsTreeShakingFalse", {
files: {
"/entry.ts": /* ts */ `
import { foo } from 'pkg'
import used = foo.used
import unused_keep = foo.unused
export { used }
`,
},
treeShaking: false,
dce: true,
bundling: false,
external: ["pkg"],
});
itBundled("ts/ImportEqualsTreeShakingTrue", {
todo: true,
files: {
"/entry.ts": /* ts */ `
import { foo } from 'pkg'
import used = foo.used
import unused_drop = foo.unused
export { used }
`,
},
dce: true,
treeShaking: true,
external: ["pkg"],
bundling: false,
});
itBundled("ts/ImportEqualsBundle", {
todo: true,
files: {
"/entry.ts": /* ts */ `
import { foo } from 'pkg'
import used = foo.used
import unused_drop = foo.unused
export { used }
`,
},
dce: true,
treeShaking: true,
external: ["pkg"],
});
itBundled("ts/MinifiedBundleES6", {
files: {
"/entry.ts": /* ts */ `
import {foo} from './a'
console.log(foo())
`,
"/a.ts": /* ts */ `
export function foo() {
return 123
}
`,
},
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
run: {
stdout: "123",
},
});
itBundled("ts/MinifiedBundleCommonJS", {
files: {
"/entry.ts": /* ts */ `
const {foo} = require('./a')
console.log(JSON.stringify([foo(), require('./j.json')]))
`,
"/a.ts": /* ts */ `
exports.foo = function() {
return 123
}
`,
"/j.json": `{"test": true}`,
},
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
run: {
stdout: '[123,{"test":true}]',
},
});
// TODO: all situations with decorators are currently not runtime-checked. as of writing bun crashes when hitting them at all.
itBundled("ts/TypeScriptDecoratorsSimpleCase", {
files: {
"/entry.ts": /* ts */ `
function decorator(...args) {
console.log('decorator called', JSON.stringify(args))
}
@decorator
class Foo {
@decorator
bar() {
console.log('bar called')
}
}
new Foo().bar()
`,
},
run: {
stdout: `
decorator called [{},"bar",{"writable":true,"enumerable":false,"configurable":true}]
decorator called [null]
bar called
`,
},
});
itBundled("ts/TypeScriptDecorators", {
// We still need to handle decorators with computed properties in method names
todo: true,
files: {
"/entry.js": /* js */ `
import all from './all'
import all_computed from './all_computed'
import {a} from './a'
import {b} from './b'
import {c} from './c'
import {d} from './d'
import e from './e'
import f from './f'
import g from './g'
import h from './h'
import {i} from './i'
import {j} from './j'
import k from './k'
import {fn} from './arguments'
console.log(all, all_computed, a, b, c, d, e, f, g, h, i, j, k, fn)
`,
"/all.ts": /* ts */ `
@x.y()
@new y.x()
export default class Foo {
@x @y mUndef
@x @y mDef = 1
@x @y method(@x0 @y0 arg0, @x1 @y1 arg1) { return new Foo }
@x @y declare mDecl
constructor(@x0 @y0 arg0, @x1 @y1 arg1) {}
@x @y static sUndef
@x @y static sDef = new Foo
@x @y static sMethod(@x0 @y0 arg0, @x1 @y1 arg1) { return new Foo }
@x @y static declare mDecl
}
`,
"/all_computed.ts": /* ts */ `
@x?.[_ + 'y']()
@new y?.[_ + 'x']()
export default class Foo {
@x @y [mUndef()]
@x @y [mDef()] = 1
@x @y [method()](@x0 @y0 arg0, @x1 @y1 arg1) { return new Foo }
@x @y declare [mDecl()]
// Side effect order must be preserved even for fields without decorators
[xUndef()]
[xDef()] = 2
static [yUndef()]
static [yDef()] = 3
@x @y static [sUndef()]
@x @y static [sDef()] = new Foo
@x @y static [sMethod()](@x0 @y0 arg0, @x1 @y1 arg1) { return new Foo }
@x @y static declare [mDecl()]
}
`,
"/a.ts": /* ts */ `
@x(() => 0) @y(() => 1)
class a_class {
fn() { return new a_class }
static z = new a_class
}
export let a = a_class
`,
"/b.ts": /* ts */ `
@x(() => 0) @y(() => 1)
abstract class b_class {
fn() { return new b_class }
static z = new b_class
}
export let b = b_class
`,
"/c.ts": /* ts */ `
@x(() => 0) @y(() => 1)
export class c {
fn() { return new c }
static z = new c
}
`,
"/d.ts": /* ts */ `
@x(() => 0) @y(() => 1)
export abstract class d {
fn() { return new d }
static z = new d
}
`,
"/e.ts": /* ts */ `
@x(() => 0) @y(() => 1)
export default class {}
`,
"/f.ts": /* ts */ `
@x(() => 0) @y(() => 1)
export default class f {
fn() { return new f }
static z = new f
}
`,
"/g.ts": /* ts */ `
@x(() => 0) @y(() => 1)
export default abstract class {}
`,
"/h.ts": /* ts */ `
@x(() => 0) @y(() => 1)
export default abstract class h {
fn() { return new h }
static z = new h
}
`,
"/i.ts": /* ts */ `
class i_class {
@x(() => 0) @y(() => 1)
foo
}
export let i = i_class
`,
"/j.ts": /* ts */ `
export class j {
@x(() => 0) @y(() => 1)
foo() {}
}
`,
"/k.ts": /* ts */ `
export default class {
foo(@x(() => 0) @y(() => 1) x) {}
}
`,
"/arguments.ts": /* ts */ `
function dec(x: any): any {}
export function fn(x: string): any {
class Foo {
@dec(arguments[0])
[arguments[0]]() {}
}
return Foo;
}
`,
},
});
itBundled("ts/TypeScriptDecoratorsKeepNames", {
files: {
"/entry.ts": /* ts */ `
@decoratorMustComeAfterName
class Foo {}
`,
},
keepNames: true,
});
itBundled("ts/TypeScriptDecoratorScopeESBuildIssue2147", {
files: {
"/entry.ts": /* ts */ `
class Foo {
method1(@dec(foo) foo = 2) {}
method2(@dec(() => foo) foo = 3) {}
}
class Bar {
static x = class {
static y = () => {
@dec(bar)
@dec(() => bar)
class Baz {
@dec(bar) method1() {}
@dec(() => bar) method2() {}
method3(@dec(() => bar) bar) {}
method4(@dec(() => bar) bar) {}
}
return Baz
}
}
}
console.log(Foo, Bar)
`,
},
bundling: false,
onAfterBundle(api) {
const capturedCalls = api.captureFile("/out.js", "dec");
expect(capturedCalls).toEqual([
"foo",
"() => foo",
"bar",
"() => bar",
"() => bar",
"() => bar",
"bar",
"() => bar",
]);
},
});
itBundled("ts/ExportType*", {
files: {
"/entry.ts": /* ts */ `
export type * as Foo from "foo";
export type * from "foo";
console.log("hi");
`,
},
run: {
stdout: "hi\n",
},
});
itBundled("ts/ExportDefaultTypeESBuildIssue316", {
files: {
"/entry.ts": /* ts */ `
import dc_def, { bar as dc } from './keep/declare-class'
import dl_def, { bar as dl } from './keep/declare-let'
import im_def, { bar as im } from './keep/interface-merged'
import in_def, { bar as _in } from './keep/interface-nested'
import tn_def, { bar as tn } from './keep/type-nested'
import vn_def, { bar as vn } from './keep/value-namespace'
import vnm_def, { bar as vnm } from './keep/value-namespace-merged'
import i_def, { bar as i } from './remove/interface'
import ie_def, { bar as ie } from './remove/interface-exported'
import t_def, { bar as t } from './remove/type'
import te_def, { bar as te } from './remove/type-exported'
import ton_def, { bar as ton } from './remove/type-only-namespace'
import tone_def, { bar as tone } from './remove/type-only-namespace-exported'
export default [
dc_def, dc,
dl_def, dl,
im_def, im,
in_def, _in,
tn_def, tn,
vn_def, vn,
vnm_def, vnm,
i,
ie,
t,
te,
ton,
tone,
]
`,
"/keep/declare-class.ts": /* ts */ `
declare class foo {}
export default foo
export let bar = 123
`,
"/keep/declare-let.ts": /* ts */ `
declare let foo: number
export default foo
export let bar = 123
`,
"/keep/interface-merged.ts": /* ts */ `
class foo {
static x = new foo
}
interface foo {}
export default foo
export let bar = 123
`,
"/keep/interface-nested.ts": /* ts */ `
if (true) {
interface foo {}
}
export default foo
export let bar = 123
`,
"/keep/type-nested.ts": /* ts */ `
if (true) {
type foo = number
}
export default foo
export let bar = 123
`,
"/keep/value-namespace.ts": /* ts */ `
namespace foo {
export let num = 0
}
export default foo
export let bar = 123
`,
"/keep/value-namespace-merged.ts": /* ts */ `
namespace foo {
export type num = number
}
namespace foo {
export let num = 0
}
export default foo
export let bar = 123
`,
"/remove/interface.ts": /* ts */ `
interface foo { }
export default foo
export let bar = 123
`,
"/remove/interface-exported.ts": /* ts */ `
export interface foo { }
export default foo
export let bar = 123
`,
"/remove/type.ts": /* ts */ `
type foo = number
export default foo
export let bar = 123
`,
"/remove/type-exported.ts": /* ts */ `
export type foo = number
export default foo
export let bar = 123
`,
"/remove/type-only-namespace.ts": /* ts */ `
namespace foo {
export type num = number
}
export default foo
export let bar = 123
`,
"/remove/type-only-namespace-exported.ts": /* ts */ `
export namespace foo {
export type num = number
}
export default foo
export let bar = 123
`,
},
runtimeFiles: {
"/test.js": /* js */ `
globalThis.foo = 123456;
const mod = (await import('./out.js')).default;
console.log(JSON.stringify(mod))
console.log(JSON.stringify(mod.map(x => typeof x)))
`,
},
run: {
file: "/test.js",
stdout: `
[123456,123,123456,123,null,123,123456,123,123456,123,{"num":0},123,{"num":0},123,123,123,123,123,123,123]
["number","number","number","number","function","number","number","number","number","number","object","number","object","number","number","number","number","number","number","number"]
`,
},
});
itBundled("ts/ImplicitExtensions", {
files: {
"/entry.ts": /* ts */ `
import './pick-js.js'
import './pick-ts.js'
import './pick-jsx.jsx'
import './pick-tsx.jsx'
import './order-js.js'
import './order-jsx.jsx'
`,
"/pick-js.js": `console.log("correct")`,
"/pick-js.ts": `console.log("FAILED")`,
"/pick-ts.jsx": `console.log("FAILED")`,
"/pick-ts.ts": `console.log("correct")`,
"/pick-jsx.jsx": `console.log("correct")`,
"/pick-jsx.tsx": `console.log("FAILED")`,
"/pick-tsx.js": `console.log("FAILED")`,
"/pick-tsx.tsx": `console.log("correct")`,
"/order-js.ts": `console.log("correct")`,
"/order-js.tsx": `console.log("FAILED")`,
"/order-jsx.ts": `console.log("correct")`,
"/order-jsx.tsx": `console.log("FAILED")`,
},
run: {
stdout: "correct\n".repeat(6),
},
});
itBundled("ts/ImplicitExtensionsMissing", {
files: {
"/entry.ts": /* ts */ `
import './mjs.mjs'
import './cjs.cjs'
import './js.js'
import './jsx.jsx'
`,
"/mjs.ts": ``,
"/mjs.tsx": ``,
"/cjs.ts": ``,
"/cjs.tsx": ``,
"/js.ts.js": ``,
"/jsx.tsx.jsx": ``,
},
bundleErrors: {
"/entry.ts": [
`Could not resolve: "./mjs.mjs"`,
`Could not resolve: "./cjs.cjs"`,
`Could not resolve: "./js.js"`,
`Could not resolve: "./jsx.jsx"`,
],
},
});
itBundled("ts/ExportTypeESBuildIssue379", {
files: {
"/entry.ts": /* ts */ `
import * as A from './a'
import * as B from './b'
import * as C from './c'
import * as D from './d'
console.log(JSON.stringify([A, B, C, D]))
`,
"/a.ts": /* ts */ `
type Test = Element
let foo = 123
export { Test, foo }
`,
"/b.ts": /* ts */ `
export type Test = Element
export let foo = 123
`,
"/c.ts": /* ts */ `
import { Test } from './test'
let foo = 123
export { Test }
export { foo }
`,
"/d.ts": /* ts */ `
export { Test }
export { foo }
import { Test } from './test'
let foo = 123
`,
"/test.ts": `export type Test = Element`,
},
run: {
stdout: '[{"foo":123},{"foo":123},{"foo":123},{"foo":123}]',
},
useDefineForClassFields: false,
});
itBundled("ts/ThisInsideFunctionTS", {
files: {
"/entry.ts": /* ts */ `
function foo(x = this) { return [x, this]; }
const objFoo = {
foo(x = this) { return [x, this]; }
}
class Foo {
x = this
static z = 456;
static y = this.z;
foo(x = this) { return [x, this]; }
static bar(x = this) { return [x, this]; }
}
assert.deepEqual(foo('bun'), ['bun', undefined]);
assert.deepEqual(foo.call('this'), ['this', 'this']);
assert.deepEqual(foo.call('this', 'bun'), ['bun', 'this']);
assert.deepEqual(objFoo.foo('bun'), ['bun', objFoo]);
assert.deepEqual(objFoo.foo(), [objFoo, objFoo]);
const fooInstance = new Foo();
assert(fooInstance.x === fooInstance, 'Foo#x');
assert(Foo.y === 456, 'Foo.y');
assert.deepEqual(Foo.bar('bun'), ['bun', Foo]);
assert.deepEqual(Foo.bar(), [Foo, Foo]);
assert.deepEqual(fooInstance.foo(), [fooInstance, fooInstance]);
assert.deepEqual(fooInstance.foo('bun'), ['bun', fooInstance]);
if (nested) {
function bar(x = this) { return [x, this]; }
const objBar = {
foo(x = this) { return [x, this]; }
}
class Bar {
x = this
static z = 456;
static y = this.z
foo(x = this) { return [x, this]; }
static bar(x = this) { return [x, this]; }
}
assert.deepEqual(bar('bun'), ['bun', undefined]);
assert.deepEqual(bar.call('this'), ['this', 'this']);
assert.deepEqual(bar.call('this', 'bun'), ['bun', 'this']);
assert.deepEqual(objBar.foo('bun'), ['bun', objBar]);
assert.deepEqual(objBar.foo(), [objBar, objBar]);
const barInstance = new Bar();
assert(barInstance.x === barInstance, 'Bar#x');
assert(Bar.y === 456, 'Bar.y');
assert.deepEqual(Bar.bar('bun'), ['bun', Bar]);
assert.deepEqual(Bar.bar(), [Bar, Bar]);
assert.deepEqual(barInstance.foo(), [barInstance, barInstance]);
assert.deepEqual(barInstance.foo('bun'), ['bun', barInstance]);
}
`,
},
runtimeFiles: {
"/test.js": /* js */ `
globalThis.nested = true;
globalThis.assert = (await import('assert')).default;
import('./out')
`,
},
run: {
file: "/test.js",
},
});
itBundled("ts/ThisInsideFunctionTSUseDefineForClassFields", {
files: {
"/entry.ts": /* ts */ `
function foo(x = this) { return [x, this]; }
const objFoo = {
foo(x = this) { return [x, this]; }
}
class Foo {
x = this
static z = 456;
static y = this.z;
foo(x = this) { return [x, this]; }
static bar(x = this) { return [x, this]; }
}
assert.deepEqual(foo('bun'), ['bun', undefined]);
assert.deepEqual(foo.call('this'), ['this', 'this']);
assert.deepEqual(foo.call('this', 'bun'), ['bun', 'this']);
assert.deepEqual(objFoo.foo('bun'), ['bun', objFoo]);
assert.deepEqual(objFoo.foo(), [objFoo, objFoo]);
const fooInstance = new Foo();
assert(fooInstance.x === fooInstance, 'Foo#x');
assert(Foo.y === 456, 'Foo.y');
assert.deepEqual(Foo.bar('bun'), ['bun', Foo]);
assert.deepEqual(Foo.bar(), [Foo, Foo]);
assert.deepEqual(fooInstance.foo(), [fooInstance, fooInstance]);
assert.deepEqual(fooInstance.foo('bun'), ['bun', fooInstance]);
if (nested) {
function bar(x = this) { return [x, this]; }
const objBar = {
foo(x = this) { return [x, this]; }
}
class Bar {
x = this
static z = 456;
static y = this.z
foo(x = this) { return [x, this]; }
static bar(x = this) { return [x, this]; }
}
assert.deepEqual(bar('bun'), ['bun', undefined]);
assert.deepEqual(bar.call('this'), ['this', 'this']);
assert.deepEqual(bar.call('this', 'bun'), ['bun', 'this']);
assert.deepEqual(objBar.foo('bun'), ['bun', objBar]);
assert.deepEqual(objBar.foo(), [objBar, objBar]);
const barInstance = new Bar();
assert(barInstance.x === barInstance, 'Bar#x');
assert(Bar.y === 456, 'Bar.y');
assert.deepEqual(Bar.bar('bun'), ['bun', Bar]);
assert.deepEqual(Bar.bar(), [Bar, Bar]);
assert.deepEqual(barInstance.foo(), [barInstance, barInstance]);
assert.deepEqual(barInstance.foo('bun'), ['bun', barInstance]);
}
`,
},
runtimeFiles: {
"/test.js": /* js */ `
globalThis.nested = true;
globalThis.assert = (await import('assert')).default;
import('./out')
`,
},
run: {
file: "/test.js",
},
useDefineForClassFields: true,
});
itBundled("ts/ThisInsideFunctionTSNoBundle", {
files: {
"/entry.ts": /* ts */ `
function foo(x = this) { return [x, this]; }
const objFoo = {
foo(x = this) { return [x, this]; }
}
class Foo {
x = this
static z = 456;
static y = this.z;
foo(x = this) { return [x, this]; }
static bar(x = this) { return [x, this]; }
}
assert.deepEqual(foo('bun'), ['bun', undefined]);
assert.deepEqual(foo.call('this'), ['this', 'this']);
assert.deepEqual(foo.call('this', 'bun'), ['bun', 'this']);
assert.deepEqual(objFoo.foo('bun'), ['bun', objFoo]);
assert.deepEqual(objFoo.foo(), [objFoo, objFoo]);
const fooInstance = new Foo();
assert(fooInstance.x === fooInstance, 'Foo#x');
assert(Foo.y === 456, 'Foo.y');
assert.deepEqual(Foo.bar('bun'), ['bun', Foo]);
assert.deepEqual(Foo.bar(), [Foo, Foo]);
assert.deepEqual(fooInstance.foo(), [fooInstance, fooInstance]);
assert.deepEqual(fooInstance.foo('bun'), ['bun', fooInstance]);
if (nested) {
function bar(x = this) { return [x, this]; }
const objBar = {
foo(x = this) { return [x, this]; }
}
class Bar {
x = this
static z = 456;
static y = this.z
foo(x = this) { return [x, this]; }
static bar(x = this) { return [x, this]; }
}
assert.deepEqual(bar('bun'), ['bun', undefined]);
assert.deepEqual(bar.call('this'), ['this', 'this']);
assert.deepEqual(bar.call('this', 'bun'), ['bun', 'this']);
assert.deepEqual(objBar.foo('bun'), ['bun', objBar]);
assert.deepEqual(objBar.foo(), [objBar, objBar]);
const barInstance = new Bar();
assert(barInstance.x === barInstance, 'Bar#x');
assert(Bar.y === 456, 'Bar.y');
assert.deepEqual(Bar.bar('bun'), ['bun', Bar]);
assert.deepEqual(Bar.bar(), [Bar, Bar]);
assert.deepEqual(barInstance.foo(), [barInstance, barInstance]);
assert.deepEqual(barInstance.foo('bun'), ['bun', barInstance]);
}
`,
},
bundling: false,
runtimeFiles: {
"/test.js": /* js */ `
globalThis.nested = true;
globalThis.assert = (await import('assert')).default;
import('./out')
`,
},
run: {
file: "/test.js",
},
});
itBundled("ts/ThisInsideFunctionTSNoBundleUseDefineForClassFields", {
files: {
"/entry.ts": /* ts */ `
function foo(x = this) { return [x, this]; }
const objFoo = {
foo(x = this) { return [x, this]; }
}
class Foo {
x = this
static z = 456;
static y = this.z;
foo(x = this) { return [x, this]; }
static bar(x = this) { return [x, this]; }
}
assert.deepEqual(foo('bun'), ['bun', undefined]);
assert.deepEqual(foo.call('this'), ['this', 'this']);
assert.deepEqual(foo.call('this', 'bun'), ['bun', 'this']);
assert.deepEqual(objFoo.foo('bun'), ['bun', objFoo]);
assert.deepEqual(objFoo.foo(), [objFoo, objFoo]);
const fooInstance = new Foo();
assert(fooInstance.x === fooInstance, 'Foo#x');
assert(Foo.y === 456, 'Foo.y');
assert.deepEqual(Foo.bar('bun'), ['bun', Foo]);
assert.deepEqual(Foo.bar(), [Foo, Foo]);
assert.deepEqual(fooInstance.foo(), [fooInstance, fooInstance]);
assert.deepEqual(fooInstance.foo('bun'), ['bun', fooInstance]);
if (nested) {
function bar(x = this) { return [x, this]; }
const objBar = {
foo(x = this) { return [x, this]; }
}
class Bar {
x = this
static z = 456;
static y = this.z
foo(x = this) { return [x, this]; }
static bar(x = this) { return [x, this]; }
}
assert.deepEqual(bar('bun'), ['bun', undefined]);
assert.deepEqual(bar.call('this'), ['this', 'this']);
assert.deepEqual(bar.call('this', 'bun'), ['bun', 'this']);
assert.deepEqual(objBar.foo('bun'), ['bun', objBar]);
assert.deepEqual(objBar.foo(), [objBar, objBar]);
const barInstance = new Bar();
assert(barInstance.x === barInstance, 'Bar#x');
assert(Bar.y === 456, 'Bar.y');
assert.deepEqual(Bar.bar('bun'), ['bun', Bar]);
assert.deepEqual(Bar.bar(), [Bar, Bar]);
assert.deepEqual(barInstance.foo(), [barInstance, barInstance]);
assert.deepEqual(barInstance.foo('bun'), ['bun', barInstance]);
}
`,
},
bundling: false,
runtimeFiles: {
"/test.js": /* js */ `
globalThis.nested = true;
globalThis.assert = (await import('assert')).default;
import('./out')
`,
},
run: {
file: "/test.js",
},
});
itBundled("ts/ComputedClassFieldUseDefineFalse", {
todo: true,
files: {
"/entry.ts": /* ts */ `
class Foo {
[q];
[r] = s;
@dec
[x];
@dec
[y] = z;
}
export default Foo;
`,
},
runtimeFiles: {
"/test.js": /* js */ `
globalThis.q = 'q1';
globalThis.r = 'r1';
globalThis.s = 's1';
globalThis.x = 'x1';
globalThis.y = 'y1';
globalThis.z = 'z1';
globalThis.dec = function(...args) {
console.log(JSON.stringify([this, ...args]));
};
const Foo = (await import('./out')).default;
globalThis.q = 'q2';
globalThis.r = 'r2';
globalThis.s = 's2';
globalThis.x = 'x2';
globalThis.y = 'y2';
globalThis.z = 'z2';
const y = new Foo();
console.log(JSON.stringify(y));
`,
},
useDefineForClassFields: false,
bundling: false,
run: {
stdout: `
[null,{},"x1",null]
[null,{},"y1",null]
{"r1":"s2","y1":"z2"}
`,
file: "/test.js",
},
});
itBundled("ts/ComputedClassFieldUseDefineTrue", {
todo: true,
files: {
"/entry.ts": /* ts */ `
class Foo {
[q];
[r] = s;
@dec
[x];
@dec
[y] = z;
}
export default Foo;
`,
},
runtimeFiles: {
"/test.js": /* js */ `
globalThis.q = 'q1';
globalThis.r = 'r1';
globalThis.s = 's1';
globalThis.x = 'x1';
globalThis.y = 'y1';
globalThis.z = 'z1';
globalThis.dec = function(...args) {
console.log(JSON.stringify([this, ...args]));
};
const Foo = (await import('./out')).default;
globalThis.q = 'q2';
globalThis.r = 'r2';
globalThis.s = 's2';
globalThis.x = 'x2';
globalThis.y = 'y2';
globalThis.z = 'z2';
const y = new Foo();
console.log(JSON.stringify(y));
`,
},
useDefineForClassFields: true,
bundling: false,
run: {
stdout: `
[null,{},"x1",null]
[null,{},"y1",null]
{"r1":"s2","y1":"z2"}
`,
file: "/test.js",
},
});
itBundled("ts/ComputedClassFieldUseDefineTrueLower", {
files: {
"/entry.ts": /* ts */ `
class Foo {
[q];
[r] = s;
@dec
[x];
@dec
[y] = z;
}
export default Foo;
`,
},
runtimeFiles: {
"/test.js": /* js */ `
globalThis.q = 'q1';
globalThis.r = 'r1';
globalThis.s = 's1';
globalThis.x = 'x1';
globalThis.y = 'y1';
globalThis.z = 'z1';
globalThis.dec = function(...args) {
console.log(JSON.stringify([this, ...args]));
};
const Foo = (await import('./out')).default;
globalThis.q = 'q2';
globalThis.r = 'r2';
globalThis.s = 's2';
globalThis.x = 'x2';
globalThis.y = 'y2';
globalThis.z = 'z2';
const y = new Foo();
console.log(JSON.stringify(y));
`,
},
useDefineForClassFields: true,
bundling: false,
run: {
stdout: `
[null,{},"x1",null]
[null,{},"y1",null]
{"r1":"s2","y1":"z2"}
`,
file: "/test.js",
},
unsupportedJSFeatures: ["class-field"],
});
itBundled("ts/AbstractClassFieldUseAssign", {
todo: true,
files: {
"/entry.ts": /* ts */ `
const keepThis = Symbol('keepThis')
declare const AND_REMOVE_THIS: unique symbol
abstract class Foo {
REMOVE_THIS: any
[keepThis]: any
abstract REMOVE_THIS_TOO: any
abstract [AND_REMOVE_THIS]: any
abstract [(x => y => x + y)('nested')('scopes_REMOVE')]: any
}
(() => new Foo())()
`,
},
dce: true,
useDefineForClassFields: false,
});
itBundled("ts/AbstractClassFieldUseDefine", {
files: {
"/entry.ts": /* ts */ `
const keepThisToo = Symbol('keepThisToo')
declare const REMOVE_THIS_TOO: unique symbol
abstract class Foo {
keepThis: any
[keepThisToo]: any
abstract REMOVE_THIS: any
abstract [REMOVE_THIS_TOO]: any
abstract [(x => y => x + y)('nested')('scopes')]: any
}
(() => new Foo())()
`,
},
bundling: false,
useDefineForClassFields: true,
});
itBundled("ts/ImportMTS", {
todo: true,
files: {
"/entry.ts": `import './imported.mjs'`,
"/imported.mts": `console.log('works')`,
},
run: {
stdout: "works",
},
});
itBundled("ts/ImportCTS", {
files: {
"/entry.ts": `require('./required.cjs')`,
"/required.cjs": `console.log('works')`,
},
run: {
stdout: "works",
},
});
itBundled("ts/SideEffectsFalseWarningTypeDeclarations", {
files: {
"/entry.ts": /* ts */ `
import "some-js"
import "some-ts"
import "empty-js"
import "empty-ts"
import "empty-dts"
`,
"/node_modules/some-js/package.json": `{ "main": "./foo.js", "sideEffects": false }`,
"/node_modules/some-js/foo.js": `console.log('foo')`,
"/node_modules/some-ts/package.json": `{ "main": "./foo.ts", "sideEffects": false }`,
"/node_modules/some-ts/foo.ts": `console.log('foo' as string)`,
"/node_modules/empty-js/package.json": `{ "main": "./foo.js", "sideEffects": false }`,
"/node_modules/empty-js/foo.js": ``,
"/node_modules/empty-ts/package.json": `{ "main": "./foo.ts", "sideEffects": false }`,
"/node_modules/empty-ts/foo.ts": `export type Foo = number`,
"/node_modules/empty-dts/package.json": `{ "main": "./foo.d.ts", "sideEffects": false }`,
"/node_modules/empty-dts/foo.d.ts": `export type Foo = number`,
},
onAfterBundle(api) {
expect(api.readFile("/out.js").trim()).toBe("");
},
});
itBundled("ts/SiblingNamespaceLet", {
todo: true,
files: {
"/let.ts": /* ts */ `
export namespace x { export let y = 123 }
export namespace x { export let z = y }
`,
},
entryPoints: ["/let.ts"],
bundling: false,
runtimeFiles: {
"/test.js": /* js */ `
import assert from 'assert'
const m = (await import('./out.js')).x
assert(m.x === m.z, "it worked")
`,
},
});
itBundled("ts/SiblingNamespaceFunction", {
todo: true,
files: {
"/function.ts": /* ts */ `
export namespace x { export function y() {} }
export namespace x { export let z = y }
`,
},
entryPoints: ["/function.ts"],
bundling: false,
runtimeFiles: {
"/test.js": /* js */ `
import assert from 'assert'
const m = (await import('./out.js')).x
assert(m.x === m.z, "it worked worked")
`,
},
});
itBundled("ts/SiblingNamespaceClass", {
todo: true,
files: {
"/let.ts": /* ts */ `
export namespace x { export class y {} }
export namespace x { export let z = y }
`,
},
entryPoints: ["/function.ts"],
bundling: false,
runtimeFiles: {
"/test.js": /* js */ `
import assert from 'assert'
const m = (await import('./out.js')).x
assert(m.x === m.z, "it worked worked")
`,
},
});
itBundled("ts/SiblingNamespaceNamespace", {
todo: true,
files: {
"/namespace.ts": /* ts */ `
export namespace x { export namespace y { 0 } }
export namespace x { export let z = y }
`,
},
entryPoints: ["/namespace.ts"],
bundling: false,
runtimeFiles: {
"/test.js": /* js */ `
import assert from 'assert'
const m = (await import('./out.js')).x
assert(m.x === m.z, "it worked worked")
`,
},
});
itBundled("ts/SiblingNamespaceEnum", {
todo: true,
files: {
"/enum.ts": /* ts */ `
export namespace x { export enum y {} }
export namespace x { export let z = y }
`,
},
entryPoints: ["/enum.ts"],
bundling: false,
runtimeFiles: {
"/test.js": /* js */ `
import assert from 'assert'
const m = (await import('./out.js')).x
assert(m.x === m.z, "it worked.ts worked")
`,
},
});
itBundled("ts/SiblingEnum", {
todo: true,
// GENERATED
files: {
"/number.ts": /* ts */ `
(0, eval)('globalThis.y = 1234');
(0, eval)('globalThis.z = 2345');
export enum x { y, yy = y }
export enum x { z = y + 1 }
declare let y: any, z: any
export namespace x { console.log(y, z) }
console.log(x.y, x.z)
`,
"/string.ts": /* ts */ `
(0, eval)('globalThis.y = 1234');
(0, eval)('globalThis.z = 2345');
export enum x { y = 'a', yy = y }
export enum x { z = y }
declare let y: any, z: any
export namespace x { console.log(y, z) }
console.log(x.y, x.z)
`,
"/propagation.ts": /* ts */ `
export enum a { b = 100 }
export enum x {
c = a.b,
d = c * 2,
e = x.d ** 2,
f = x['e'] / 4,
}
export enum x { g = f >> 4 }
console.log(a.b, a['b'], x.g, x['g'])
`,
"/nested-number.ts": /* ts */ `
(0, eval)('globalThis.y = 1234');
(0, eval)('globalThis.z = 2345');
export namespace foo { export enum x { y, yy = y } }
export namespace foo { export enum x { z = y + 1 } }
declare let y: any, z: any
export namespace foo.x {
console.log(y, z)
console.log(x.y, x.z)
}
`,
"/nested-string.ts": /* ts */ `
(0, eval)('globalThis.y = 1234');
(0, eval)('globalThis.z = 2345');
export namespace foo { export enum x { y = 'a', yy = y } }
export namespace foo { export enum x { z = y } }
declare let y: any, z: any
export namespace foo.x {
console.log(y, z)
console.log(x.y, x.z)
}
`,
"/nested-propagation.ts": /* ts */ `
export namespace n { export enum a { b = 100 } }
export namespace n {
export enum x {
c = n.a.b,
d = c * 2,
e = x.d ** 2,
f = x['e'] / 4,
}
}
export namespace n {
export enum x { g = f >> 4 }
console.log(a.b, n.a.b, n['a']['b'], x.g, n.x.g, n['x']['g'])
}
`,
},
entryPoints: [
"/number.ts",
"/string.ts",
"/propagation.ts",
"/nested-number.ts",
"/nested-string.ts",
"/nested-propagation.ts",
],
run: [
{ file: "/out/number.js", stdout: "1234 2345\n0 1" },
{ file: "/out/string.js", stdout: "1234 2345\na a" },
{ file: "/out/propagation.js", stdout: "100 100 625 625" },
{ file: "/out/nested-number.js", stdout: "1234 2345\n0 1" },
{ file: "/out/nested-string.js", stdout: "1234 2345\na a" },
{ file: "/out/nested-propagation.js", stdout: "100 100 100 625 625 625" },
],
});
itBundled("ts/EnumTreeShaking", {
todo: true,
files: {
"/simple-member.ts": /* ts */ `
enum x_DROP { y_DROP = 123 }
console.log(x_DROP.y_DROP)
`,
"/simple-enum.ts": /* ts */ `
enum x { y = 123 }
console.log(JSON.stringify(x))
`,
"/sibling-member.ts": /* ts */ `
enum drop_x { drop_y = 123 }
enum drop_x { drop_z = drop_y * 2 }
console.log(drop_x.drop_y, drop_x.drop_z)
`,
"/sibling-enum-before.ts": /* ts */ `
console.log(x)
enum x { y = 123 }
enum x { z = y * 2 }
`,
"/sibling-enum-middle.ts": /* ts */ `
enum x { y = 123 }
console.log(JSON.stringify(x))
enum x { z = y * 2 }
`,
"/sibling-enum-after.ts": /* ts */ `
enum x { y = 123 }
enum x { z = y * 2 }
console.log(JSON.stringify(x))
`,
"/namespace-before.ts": /* ts */ `
(0, eval)('globalThis.y = 1234');
(0, eval)('globalThis.x = 2345');
namespace x { console.log(x, y) }
enum x { y = 123 }
`,
"/namespace-after.ts": /* ts */ `
(0, eval)('globalThis.y = 1234');
(0, eval)('globalThis.x = 2345');
enum x { y = 123 }
namespace x { console.log(JSON.stringify(x), y) }
`,
},
entryPoints: [
"/simple-member.ts",
"/simple-enum.ts",
"/sibling-member.ts",
"/sibling-enum-before.ts",
"/sibling-enum-middle.ts",
"/sibling-enum-after.ts",
"/namespace-before.ts",
"/namespace-after.ts",
],
dce: true,
run: [
{ file: "/out/simple-member.js", stdout: "123" },
{ file: "/out/simple-enum.js", stdout: '{"123":"y","y":123}' },
{ file: "/out/sibling-member.js", stdout: "123 246" },
{ file: "/out/sibling-enum-before.js", stdout: "undefined" },
{ file: "/out/sibling-enum-middle.js", stdout: '{"123":"y","y":123}' },
{ file: "/out/sibling-enum-after.js", stdout: '{"123":"y","246":"z","y":123,"z":246}' },
{ file: "/out/namespace-before.js", stdout: "{} 1234" },
{ file: "/out/namespace-after.js", stdout: '{"123":"y","y":123} 1234' },
],
});
itBundled("ts/EnumJSX", {
todo: true,
files: {
"/element.tsx": /* tsx */ `
import { create } from 'not-react'
export enum Foo { Div = 'div' }
console.log(JSON.stringify())
`,
"/fragment.tsx": /* tsx */ `
import { create } from 'not-react'
export enum React { Fragment = 'div' }
console.log(JSON.stringify(<>test>))
`,
"/nested-element.tsx": /* tsx */ `
import { create } from 'not-react'
namespace x.y { export enum Foo { Div = 'div' } }
namespace x.y { console.log(JSON.stringify()) }
`,
"/nested-fragment.tsx": /* tsx */ `
import { create } from 'not-react'
namespace x.y { export enum React { Fragment = 'div' } }
namespace x.y { console.log(JSON.stringify(<>test>)) }
`,
},
entryPoints: ["/element.tsx", "/fragment.tsx", "/nested-element.tsx", "/nested-fragment.tsx"],
outputPaths: ["/out/element.js", "/out/fragment.js", "/out/nested-element.js", "/out/nested-fragment.js"],
external: ["*"],
jsx: {
runtime: "classic",
factory: "create",
},
runtimeFiles: {
"/node_modules/not-react/index.js": /* js */ `
export const create = (tag, props, ...children) => [tag, props, children]
`,
},
run: [
{ file: "/out/element.js", stdout: '["div",null,[]]' },
{ file: "/out/fragment.js", stdout: '["div",null,["test"]]' },
{ file: "/out/nested-element.js", stdout: '["div",null,[]]' },
{ file: "/out/nested-fragment.js", stdout: '["div",null,["test"]]' },
],
});
itBundled("ts/EnumDefine", {
todo: true,
files: {
"/entry.ts": `
enum a { b = 123, c = d }
console.log(a.b, a.c)
`,
},
define: {
d: "b",
},
run: { stdout: "123 123" },
});
itBundled("ts/EnumSameModuleInliningAccess", {
todo: true,
files: {
"/entry.ts": /* ts */ `
enum a_drop { x = 123 }
enum b_drop { x = 123 }
enum c { x = 123 }
enum d { x = 123 }
enum e { x = 123 }
console.log(JSON.stringify([
a_drop.x,
b_drop['x'],
c?.x,
d?.['x'],
e,
]))
`,
},
dce: true,
run: { stdout: '[123,123,123,123,{"123":"x","x":123}]' },
});
itBundled("ts/EnumCrossModuleInliningAccess", {
todo: true,
files: {
"/entry.ts": /* ts */ `
import { drop_a, drop_b, c, d, e } from './enums'
console.log(JSON.stringify([
drop_a.x,
drop_b['x'],
c?.x,
d?.['x'],
e,
]))
`,
"/enums.ts": /* ts */ `
export enum drop_a { x = 123 }
export enum drop_b { x = 123 }
export enum c { x = 123 }
export enum d { x = 123 }
export enum e { x = 123 }
`,
},
dce: true,
});
itBundled("ts/EnumCrossModuleInliningDefinitions", {
todo: true,
files: {
"/entry.ts": /* ts */ `
import { a } from './enums'
(0, eval)('globalThis.capture = x => x');
console.log(JSON.stringify([
capture(a.implicit_number),
capture(a.explicit_number),
capture(a.explicit_string),
a.non_constant,
]))
`,
"/enums.ts": /* ts */ `
(0, eval)('globalThis.foo = 321');
export enum a {
implicit_number,
explicit_number = 123,
explicit_string = 'xyz',
non_constant = foo,
}
`,
},
onAfterBundle(api) {
expect(api.captureFile("/out.js").map(x => x.replace(/\/\*.*\*\//g, "").trim())).toEqual(["0", "123", '"xyz"']);
},
});
itBundled("ts/EnumCrossModuleInliningReExport", {
todo: true,
files: {
"/entry.js": /* js */ `
import { a } from './re-export'
import { b } from './re-export-star'
import * as ns from './enums'
console.log([
capture(a.x),
capture(b.x),
capture(ns.c.x),
])
`,
"/re-export.js": `export { a } from './enums'`,
"/re-export-star.js": `export * from './enums'`,
"/enums.ts": /* ts */ `
export enum a { x = 'a' }
export enum b { x = 'b' }
export enum c { x = 'c' }
`,
},
onAfterBundle(api) {
expect(api.captureFile("/out.js").map(x => x.replace(/\/\*.*\*\//g, "").trim())).toEqual(['"a"', '"b"', '"c"']);
},
});
itBundled("ts/EnumCrossModuleTreeShaking", {
todo: true,
files: {
"/entry.ts": /* ts */ `
import {
a_DROP,
b_DROP,
c_DROP,
} from './enums'
console.log([
capture(a_DROP.x),
capture(b_DROP['x']),
capture(c_DROP.x),
])
import { a, b, c, d, e } from './enums'
console.log([
capture(a.x),
capture(b.x),
capture(c),
capture(d.y),
capture(e.x),
])
`,
"/enums.ts": /* ts */ `
export enum a_DROP { x = 1 } // test a dot access
export enum b_DROP { x = 2 } // test an index access
export enum c_DROP { x = '' } // test a string enum
export enum a { x = false } // false is not inlinable
export enum b { x = foo } // foo has side effects
export enum c { x = 3 } // this enum object is captured
export enum d { x = 4 } // we access "y" on this object
export let e = {} // non-enum properties should be kept
`,
},
onAfterBundle(api) {
expect(api.captureFile("/out.js").map(x => x.replace(/\/\*.*\*\//g, "").trim())).toEqual([
"1",
"2",
'""',
"a.x",
"b.x",
"c",
"d.y",
"e.x",
]);
},
});
itBundled("ts/EnumExportClause", {
todo: true,
files: {
"/entry.ts": /* ts */ `
import {
A,
B,
C as c,
d as dd,
} from './enums'
console.log([
capture(A.A),
capture(B.B),
capture(c.C),
capture(dd.D),
])
`,
"/enums.ts": /* ts */ `
export enum A { A = 1 }
enum B { B = 2 }
export enum C { C = 3 }
enum D { D = 4 }
export { B, D as d }
`,
},
onAfterBundle(api) {
expect(api.captureFile("/out.js").map(x => x.replace(/\/\*.*\*\//g, "").trim())).toEqual(["1", "2", "3", "4"]);
},
});
// itBundled("ts/CommonJSVariableInESMTypeModule", {
// // GENERATED
// files: {
// "/entry.ts": `module.exports = null`,
// "/package.json": `{ "type": "module" }`,
// },
// /* TODO FIX expectedScanLog: `entry.ts: WARNING: The CommonJS "module" variable is treated as a global variable in an ECMAScript module and may not work as expected
// package.json: NOTE: This file is considered to be an ECMAScript module because the enclosing "package.json" file sets the type of this file to "module":
// NOTE: Node's package format requires that CommonJS files in a "type": "module" package use the ".cjs" file extension. If you are using TypeScript, you can use the ".cts" file extension with esbuild instead.
// `, */
// });
itBundled("ts/EnumRulesFrom_TypeScript_5_0", {
// GENERATED
files: {
"/supported.ts":
`
// From https://github.com/microsoft/TypeScript/pull/50528:
// "An expression is considered a constant expression if it is
const enum DROP {
// a number or string literal,
X0 = 123,
X1 = 'x',
// a unary +, -, or ~ applied to a numeric constant expression,
X2 = +1,
X3 = -2,
X4 = ~3,
// a binary +, -, *, /, %, **, <<, >>, >>>, |, &, ^ applied to two numeric constant expressions,
X5 = 1 + 2,
X6 = 1 - 2,
X7 = 2 * 3,
X8 = 1 / 2,
X9 = 3 % 2,
X10 = 2 ** 3,
X11 = 1 << 2,
X12 = -9 >> 1,
X13 = -9 >>> 1,
X14 = 5 | 12,
X15 = 5 & 12,
X16 = 5 ^ 12,
// a binary + applied to two constant expressions whereof at least one is a string,
X17 = 'x' + 0,
X18 = 0 + 'x',
X19 = 'x' + 'y',
X20 = '' + NaN,
X21 = '' + Infinity,
X22 = '' + -Infinity,
X23 = '' + -0,
// a template expression where each substitution expression is a constant expression,
X24 = ` +
"`A${0}B${'x'}C${1 + 3 - 4 / 2 * 5 ** 6}D`" +
`,
// a parenthesized constant expression,
X25 = (321),
// a dotted name (e.g. x.y.z) that references a const variable with a constant expression initializer and no type annotation,
/* (we don't implement this one) */
// a dotted name that references an enum member with an enum literal type, or
X26 = X0,
X27 = X0 + 'x',
X28 = 'x' + X0,
X29 = ` +
"`a${X0}b`" +
`,
X30 = DROP.X0,
X31 = DROP.X0 + 'x',
X32 = 'x' + DROP.X0,
X33 = ` +
"`a${DROP.X0}b`" +
`,
// a dotted name indexed by a string literal (e.g. x.y["z"]) that references an enum member with an enum literal type."
X34 = X1,
X35 = X1 + 'y',
X36 = 'y' + X1,
X37 = ` +
"`a${X1}b`" +
`,
X38 = DROP['X1'],
X39 = DROP['X1'] + 'y',
X40 = 'y' + DROP['X1'],
X41 = ` +
"`a${DROP['X1']}b`" +
`,
}
console.log(JSON.stringify([
// a number or string literal,
DROP.X0,
DROP.X1,
// a unary +, -, or ~ applied to a numeric constant expression,
DROP.X2,
DROP.X3,
DROP.X4,
// a binary +, -, *, /, %, **, <<, >>, >>>, |, &, ^ applied to two numeric constant expressions,
DROP.X5,
DROP.X6,
DROP.X7,
DROP.X8,
DROP.X9,
DROP.X10,
DROP.X11,
DROP.X12,
DROP.X13,
DROP.X14,
DROP.X15,
DROP.X16,
// a template expression where each substitution expression is a constant expression,
DROP.X17,
DROP.X18,
DROP.X19,
DROP.X20,
DROP.X21,
DROP.X22,
DROP.X23,
// a template expression where each substitution expression is a constant expression,
DROP.X24,
// a parenthesized constant expression,
DROP.X25,
// a dotted name that references an enum member with an enum literal type, or
DROP.X26,
DROP.X27,
DROP.X28,
DROP.X29,
DROP.X30,
DROP.X31,
DROP.X32,
DROP.X33,
// a dotted name indexed by a string literal (e.g. x.y["z"]) that references an enum member with an enum literal type."
DROP.X34,
DROP.X35,
DROP.X36,
DROP.X37,
DROP.X38,
DROP.X39,
DROP.X40,
DROP.X41,
]))
`,
"/not-supported.ts": /* ts */ `
(0, eval)('globalThis.capture = x => x');
const enum NonIntegerNumberToString {
SUPPORTED = '' + 1,
UNSUPPORTED = '' + 1.5,
}
console.log(
capture(NonIntegerNumberToString.SUPPORTED),
capture(NonIntegerNumberToString.UNSUPPORTED),
)
const enum OutOfBoundsNumberToString {
SUPPORTED = '' + 1_000_000_000,
UNSUPPORTED = '' + 1_000_000_000_000,
}
console.log(
capture(OutOfBoundsNumberToString.SUPPORTED),
capture(OutOfBoundsNumberToString.UNSUPPORTED),
)
const enum TemplateExpressions {
// TypeScript enums don't handle any of these
NULL = '' + null,
TRUE = '' + true,
FALSE = '' + false,
BIGINT = '' + 123n,
}
console.log(
capture(TemplateExpressions.NULL),
capture(TemplateExpressions.TRUE),
capture(TemplateExpressions.FALSE),
capture(TemplateExpressions.BIGINT),
)
`,
},
// dce: true,
entryPoints: ["/supported.ts", "/not-supported.ts"],
run: [
{
file: "/out/supported.js",
stdout:
'[123,"x",1,-2,-4,3,-1,6,0.5,1,8,4,-5,2147483643,13,4,9,"x0","0x","xy","NaN","Infinity","-Infinity","0","A0BxC-31246D",321,123,"123x","x123","a123b",123,"123x","x123","a123b","x","xy","yx","axb","x","xy","yx","axb"]',
},
{
file: "/out/not-supported.js",
stdout: `
1 1.5
1000000000 1000000000000
null true false 123
`,
},
],
onAfterBundle(api) {
// expect(api.captureFile("/out/not-supported.js").map(x => x.replace(/\/\*.*\*\//g, "").trim())).toEqual([
// '"1"',
// "NonIntegerNumberToString.UNSUPPORTED",
// '"1000000000"',
// "OutOfBoundsNumberToString.UNSUPPORTED",
// "TemplateExpressions.NULL",
// "TemplateExpressions.TRUE",
// "TemplateExpressions.FALSE",
// "TemplateExpressions.BIGINT",
// ]);
},
});
itBundled("ts/EnumUseBeforeDeclare", {
todo: true,
files: {
"/entry.ts": /* ts */ `
before();
after();
export function before() {
console.log(JSON.stringify(Foo), Foo.FOO)
}
enum Foo { FOO }
export function after() {
console.log(JSON.stringify(Foo), Foo.FOO)
}
before();
after();
`,
},
run: {
stdout: `
undefined 0
undefined 0
{"0":"FOO","FOO":0} 0
{"0":"FOO","FOO":0} 0
`,
},
});
});