mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
2501 lines
74 KiB
TypeScript
2501 lines
74 KiB
TypeScript
import assert from "assert";
|
|
import { describe, expect } from "bun:test";
|
|
import { itBundled } from "../expectBundled";
|
|
|
|
// 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", {
|
|
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", {
|
|
files: {
|
|
"/a.ts": `enum Foo { A, B, C = Foo }\ncapture(Foo)`,
|
|
"/b.ts": `export enum Foo { X, Y, Z = Foo }`,
|
|
},
|
|
entryPoints: ["/a.ts", "./b.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", {
|
|
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}]',
|
|
},
|
|
});
|
|
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
|
|
@x @y declare mAbst
|
|
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()]
|
|
@x @y abstract [mAbst()]
|
|
|
|
// 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/ExportTypeSTAR", {
|
|
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", {
|
|
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", {
|
|
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", {
|
|
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", {
|
|
files: {
|
|
"/let.ts": /* ts */ `
|
|
export namespace x { export class y {} }
|
|
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 worked")
|
|
`,
|
|
},
|
|
});
|
|
itBundled("ts/SiblingNamespaceNamespace", {
|
|
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", {
|
|
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")
|
|
`,
|
|
},
|
|
minifySyntax: false, // intentionally disabled. enum inlining always happens
|
|
});
|
|
itBundled("ts/SiblingEnum", {
|
|
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" },
|
|
],
|
|
minifySyntax: false, // intentionally disabled. enum inlining always happens
|
|
});
|
|
itBundled("ts/EnumTreeShaking", {
|
|
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' },
|
|
],
|
|
minifySyntax: false, // intentionally disabled. enum inlining always happens
|
|
});
|
|
itBundled("ts/EnumJSX", {
|
|
// Blocking:
|
|
// - jsx bugs (configuration does not seem to be respected)
|
|
todo: true,
|
|
files: {
|
|
"/element.tsx": /* tsx */ `
|
|
import { create } from 'not-react'
|
|
|
|
export enum Foo { Div = 'div' }
|
|
console.log(JSON.stringify(<Foo.Div />))
|
|
`,
|
|
"/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(<Foo.Div />)) }
|
|
`,
|
|
"/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]
|
|
`,
|
|
},
|
|
minifySyntax: false, // intentionally disabled. enum inlining always happens
|
|
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",
|
|
},
|
|
minifySyntax: false, // intentionally disabled. enum inlining always happens
|
|
run: { stdout: "123 123" },
|
|
});
|
|
itBundled("ts/EnumSameModuleInliningAccess", {
|
|
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,
|
|
minifySyntax: false, // intentionally disabled. enum inlining always happens
|
|
run: { stdout: '[123,123,123,123,{"123":"x","x":123}]' },
|
|
});
|
|
itBundled("ts/EnumCrossModuleInliningAccess", {
|
|
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 }
|
|
`,
|
|
},
|
|
minifySyntax: false, // intentionally disabled. enum inlining always happens
|
|
dce: true,
|
|
});
|
|
itBundled("ts/EnumCrossModuleInliningDefinitions", {
|
|
files: {
|
|
"/entry.ts": /* ts */ `
|
|
import { a } from './enums'
|
|
(0, eval)('globalThis.["captu" + "re"] = 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,
|
|
}
|
|
`,
|
|
},
|
|
minifySyntax: false, // intentionally disabled. enum inlining always happens
|
|
onAfterBundle(api) {
|
|
expect(api.captureFile("/out.js").map(x => x.replace(/\/\*.*\*\//g, "").trim())).toEqual(["0", "123", '"xyz"']);
|
|
},
|
|
});
|
|
itBundled("ts/EnumCrossModuleInliningReExport", {
|
|
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' }
|
|
`,
|
|
},
|
|
minifySyntax: false, // intentionally disabled. enum inlining always happens
|
|
onAfterBundle(api) {
|
|
expect(api.captureFile("/out.js").map(x => x.replace(/\/\*.*\*\//g, "").trim())).toEqual(['"a"', '"b"', '"c"']);
|
|
},
|
|
});
|
|
itBundled("ts/EnumCrossModuleTreeShaking", {
|
|
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
|
|
`,
|
|
},
|
|
minifySyntax: false, // intentionally disabled. enum inlining always happens
|
|
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", {
|
|
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 }
|
|
`,
|
|
},
|
|
minifySyntax: false, // intentionally disabled. enum inlining always happens
|
|
onAfterBundle(api) {
|
|
expect(api.captureFile("/out.js").map(x => x.replace(/\/\*.*\*\//g, "").trim())).toEqual(["1", "2", "3", "4"]);
|
|
},
|
|
});
|
|
itBundled("ts/EnumRulesFrom_TypeScript_5_0", {
|
|
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["captu" + "re"] = x => x');
|
|
|
|
const enum NumberToString {
|
|
DROP_One = '' + 1,
|
|
DROP_OnePointFive = '' + 1.5,
|
|
DROP_Other = '' + 4132879497321892437432187943789312894378237491578123414321431,
|
|
DROP_Billion = '' + 1_000_000_000,
|
|
DROP_Trillion = '' + 1_000_000_000_000,
|
|
}
|
|
console.log(
|
|
capture(NumberToString.DROP_One),
|
|
capture(NumberToString.DROP_OnePointFive),
|
|
capture(NumberToString.DROP_Other),
|
|
capture(NumberToString.DROP_Billion),
|
|
capture(NumberToString.DROP_Trillion),
|
|
)
|
|
|
|
const enum DROP_TemplateExpressions {
|
|
// TypeScript enums don't handle any of these
|
|
NULL = '' + null,
|
|
TRUE = '' + true,
|
|
FALSE = '' + false,
|
|
BIGINT = '' + 123n,
|
|
BIGINT_2 = '' + 4132879497321892437432187943789312894378237491578123414321431n,
|
|
}
|
|
|
|
console.log(
|
|
capture(DROP_TemplateExpressions.NULL),
|
|
capture(DROP_TemplateExpressions.TRUE),
|
|
capture(DROP_TemplateExpressions.FALSE),
|
|
capture(DROP_TemplateExpressions.BIGINT),
|
|
capture(DROP_TemplateExpressions.BIGINT_2),
|
|
)
|
|
`,
|
|
},
|
|
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 4.1328794973218926e+60 1000000000 1000000000000
|
|
null true false 123 4132879497321892437432187943789312894378237491578123414321431
|
|
`,
|
|
},
|
|
],
|
|
onAfterBundle(api) {
|
|
expect(api.captureFile("/out/not-supported.js").map(x => x.replace(/\/\*.*\*\//g, "").trim())).toEqual([
|
|
'"1"',
|
|
'"1.5"',
|
|
'"4.1328794973218926e+60"',
|
|
'"1000000000"',
|
|
'"1000000000000"',
|
|
'"null"',
|
|
'"true"',
|
|
'"false"',
|
|
'"123"',
|
|
'"4132879497321892437432187943789312894378237491578123414321431"',
|
|
]);
|
|
},
|
|
});
|
|
itBundled("ts/EnumUseBeforeDeclare", {
|
|
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
|
|
`,
|
|
},
|
|
});
|
|
});
|