Continue bundler tests (#2691)

* start refine + skipping some packagejson tests

* some more tests

* stuff

* tests for minify branch

* pkgjson

* add minify/MergeAdjacentVars

* add test for #2699

* more tests!

* more tests

* finish splitting tests

* all but 2 import star tests are good

* test
This commit is contained in:
dave caruso
2023-04-24 17:12:21 -04:00
committed by GitHub
parent 923ac39c0b
commit f2112fc0de
11 changed files with 1718 additions and 660 deletions

View File

@@ -0,0 +1,284 @@
import assert from "assert";
import dedent from "dedent";
import { bundlerTest, expectBundled, itBundled, testForFile } from "./expectBundled";
var { describe, test, expect } = testForFile(import.meta.path);
describe("bundler", () => {
const nodePolyfillList = {
"assert": "polyfill",
"buffer": "polyfill",
"child_process": "no-op",
"cluster": "no-op",
"console": "polyfill",
"constants": "polyfill",
"crypto": "polyfill",
"dgram": "no-op",
"dns": "no-op",
"domain": "polyfill",
"events": "polyfill",
"fs": "no-op",
"http": "polyfill",
"https": "polyfill",
"module": "no-op",
"net": "polyfill",
"os": "polyfill",
"path": "polyfill",
"perf_hooks": "no-op",
"process": "polyfill",
"punycode": "polyfill",
"querystring": "polyfill",
"readline": "no-op",
"repl": "no-op",
"stream": "polyfill",
"string_decoder": "polyfill",
"sys": "polyfill",
"timers": "polyfill",
"tls": "no-op",
"tty": "polyfill",
"url": "polyfill",
"util": "polyfill",
"v8": "no-op",
"vm": "no-op",
"zlib": "polyfill",
};
itBundled("browser/NodeFS", {
files: {
"/entry.js": /* js */ `
import * as fs from "node:fs";
import * as fs2 from "fs";
import { readFileSync } from "fs";
console.log(typeof fs);
console.log(typeof fs2);
console.log(typeof readFileSync);
`,
},
platform: "browser",
run: {
stdout: "function\nfunction\nundefined",
},
});
// TODO: use nodePolyfillList to generate the code in here.
const NodePolyfills = itBundled("browser/NodePolyfills", {
files: {
"/entry.js": /* js */ `
import * as assert from "node:assert";
import * as buffer from "node:buffer";
import * as child_process from "node:child_process";
import * as cluster from "node:cluster";
import * as console2 from "node:console";
import * as constants from "node:constants";
import * as crypto from "node:crypto";
import * as dgram from "node:dgram";
import * as dns from "node:dns";
import * as domain from "node:domain";
import * as events from "node:events";
import * as fs from "node:fs";
import * as http from "node:http";
import * as https from "node:https";
import * as module2 from "node:module";
import * as net from "node:net";
import * as os from "node:os";
import * as path from "node:path";
import * as perf_hooks from "node:perf_hooks";
import * as process from "node:process";
import * as punycode from "node:punycode";
import * as querystring from "node:querystring";
import * as readline from "node:readline";
import * as repl from "node:repl";
import * as stream from "node:stream";
import * as string_decoder from "node:string_decoder";
import * as sys from "node:sys";
import * as timers from "node:timers";
import * as tls from "node:tls";
import * as tty from "node:tty";
import * as url from "node:url";
import * as util from "node:util";
import * as v8 from "node:v8";
import * as vm from "node:vm";
import * as zlib from "node:zlib";
function scan(obj) {
if (typeof obj === 'function') obj = obj()
return Object.keys(obj).length === 0 ? 'no-op' : 'polyfill'
}
console.log('assert :', scan(assert))
console.log('buffer :', scan(buffer))
console.log('child_process :', scan(child_process))
console.log('cluster :', scan(cluster))
console.log('console :', console2 === console ? 'equal' : 'polyfill')
console.log('constants :', scan(constants))
console.log('crypto :', scan(crypto))
console.log('dgram :', scan(dgram))
console.log('dns :', scan(dns))
console.log('domain :', scan(domain))
console.log('events :', scan(events))
console.log('fs :', scan(fs))
console.log('http :', scan(http))
console.log('https :', scan(https))
console.log('module :', scan(module2))
console.log('net :', scan(net))
console.log('os :', scan(os))
console.log('path :', scan(path))
console.log('perf_hooks :', scan(perf_hooks))
console.log('process :', scan(process))
console.log('punycode :', scan(punycode))
console.log('querystring :', scan(querystring))
console.log('readline :', scan(readline))
console.log('repl :', scan(repl))
console.log('stream :', scan(stream))
console.log('string_decoder :', scan(string_decoder))
console.log('sys :', scan(sys))
console.log('timers :', scan(timers))
console.log('tls :', scan(tls))
console.log('tty :', scan(tty))
console.log('url :', scan(url))
console.log('util :', scan(util))
console.log('v8 :', scan(v8))
console.log('vm :', scan(vm))
console.log('zlib :', scan(zlib))
`,
},
platform: "browser",
onAfterBundle(api) {
assert(!api.readFile("/out.js").includes("\0"), "bundle should not contain null bytes");
const file = api.readFile("/out.js");
const imports = new Bun.Transpiler().scanImports(file);
expect(imports).toStrictEqual([]);
},
run: {
stdout: `
assert : polyfill
buffer : polyfill
child_process : no-op
cluster : no-op
console : polyfill
constants : polyfill
crypto : polyfill
dgram : no-op
dns : no-op
domain : polyfill
events : polyfill
fs : no-op
http : polyfill
https : polyfill
module : no-op
net : polyfill
os : polyfill
path : polyfill
perf_hooks : no-op
process : polyfill
punycode : polyfill
querystring : polyfill
readline : no-op
repl : no-op
stream : polyfill
string_decoder : polyfill
sys : polyfill
timers : polyfill
tls : no-op
tty : polyfill
url : polyfill
util : polyfill
v8 : no-op
vm : no-op
zlib : polyfill
`,
},
});
itBundled("browser/NodePolyfillExternal", {
skipOnEsbuild: true,
files: {
"/entry.js": NodePolyfills.options.files["/entry.js"],
},
platform: "browser",
external: Object.keys(nodePolyfillList),
onAfterBundle(api) {
const file = api.readFile("/out.js");
const imports = new Bun.Transpiler().scanImports(file);
expect(imports).toStrictEqual(
Object.keys(nodePolyfillList).map(x => ({
kind: "import-statement",
path: "node:" + x,
})),
);
},
});
// unsure: do we want polyfills or no-op stuff like node:* has
// right now all error except bun:wrap which errors at resolve time, but is included if external
const bunModules: Record<string, "no-op" | "polyfill" | "error"> = {
"bun": "error",
"bun:ffi": "error",
"bun:dns": "error",
"bun:test": "error",
"bun:sqlite": "error",
"bun:wrap": "error",
"bun:internal": "error",
"bun:jsc": "error",
};
const nonErroringBunModules = Object.entries(bunModules)
.filter(x => x[1] !== "error")
.map(x => x[0]);
// segfaults the test runner
itBundled.skip("browser/BunPolyfill", {
skipOnEsbuild: true,
files: {
"/entry.js": `
${nonErroringBunModules.map((x, i) => `import * as bun_${i} from "${x}";`).join("\n")}
function scan(obj) {
if (typeof obj === 'function') obj = obj()
return Object.keys(obj).length === 0 ? 'no-op' : 'polyfill'
}
${nonErroringBunModules.map((x, i) => `console.log("${x.padEnd(12, " ")}:", scan(bun_${i}));`).join("\n")}
`,
},
platform: "browser",
onAfterBundle(api) {
assert(!api.readFile("/out.js").includes("\0"), "bundle should not contain null bytes");
const file = api.readFile("/out.js");
const imports = new Bun.Transpiler().scanImports(file);
expect(imports).toStrictEqual([]);
},
run: {
stdout: nonErroringBunModules.map(x => `${x.padEnd(12, " ")}: ${bunModules[x]}`).join("\n"),
},
});
const ImportBunError = itBundled("browser/ImportBunError", {
skipOnEsbuild: true,
files: {
"/entry.js": `
${Object.keys(bunModules)
.map((x, i) => `import * as bun_${i} from "${x}";`)
.join("\n")}
${Object.keys(bunModules)
.map((x, i) => `console.log("${x.padEnd(12, " ")}:", !!bun_${i});`)
.join("\n")}
`,
},
platform: "browser",
bundleErrors: {
"/entry.js": Object.keys(bunModules)
.filter(x => bunModules[x] === "error")
.map(x => `Could not resolve: "${x}". Maybe you need to "bun install"?`),
},
});
itBundled("browser/BunPolyfillExternal", {
skipOnEsbuild: true,
files: ImportBunError.options.files,
platform: "browser",
external: Object.keys(bunModules),
onAfterBundle(api) {
const file = api.readFile("/out.js");
const imports = new Bun.Transpiler().scanImports(file);
expect(imports).toStrictEqual(
Object.keys(bunModules).map(x => ({
kind: "import-statement",
path: x,
})),
);
},
});
});

View File

@@ -16,14 +16,155 @@ describe("bundler", () => {
}
`,
},
minifySyntax: true,
platform: "bun",
// TODO: better assertion
onAfterBundle(api) {
assert(!api.readFile("/out.js").includes("__commonJS"), "should not include the commonJS helper");
},
cjs2esm: true,
run: {
stdout: "foo",
},
});
itBundled("cjs2esm/ExportsFunction", {
files: {
"/entry.js": /* js */ `
import { foo } from 'lib';
console.log(foo());
`,
"/node_modules/lib/index.js": /* js */ `
exports.foo = function() {
return 'foo';
}
`,
},
cjs2esm: true,
run: {
stdout: "foo",
},
});
itBundled("cjs2esm/ModuleExportsFunctionTreeShaking", {
files: {
"/entry.js": /* js */ `
import { foo } from 'lib';
console.log(foo());
`,
"/node_modules/lib/index.js": /* js */ `
module.exports.foo = function() {
return 'foo';
}
module.exports.bar = function() {
return 'remove_me';
}
`,
},
cjs2esm: true,
dce: true,
treeShaking: true,
run: {
stdout: "foo",
},
});
itBundled("cjs2esm/ModuleExportsEqualsRequire", {
files: {
"/entry.js": /* js */ `
import { foo } from 'lib';
console.log(foo);
`,
"/node_modules/lib/index.js": /* js */ `
// bundler should see through this
module.exports = require('./library.js')
`,
"/node_modules/lib/library.js": /* js */ `
module.exports.foo = 'bar';
`,
},
cjs2esm: true,
run: {
stdout: "bar",
},
});
itBundled("cjs2esm/ModuleExportsBasedOnNodeEnvProduction", {
files: {
"/entry.js": /* js */ `
import { foo } from 'lib';
console.log(foo);
`,
"/node_modules/lib/index.js": /* js */ `
// bundler should see through this
if (process.env.NODE_ENV === 'production') {
module.exports = require('./library.prod.js')
} else {
module.exports = require('./library.dev.js')
}
`,
"/node_modules/lib/library.prod.js": /* js */ `
module.exports.foo = 'production';
`,
"/node_modules/lib/library.dev.js": /* js */ `
module.exports.foo = 'FAILED';
`,
},
cjs2esm: true,
dce: true,
env: {
NODE_ENV: "production",
},
run: {
stdout: "production",
},
});
itBundled("cjs2esm/ModuleExportsBasedOnNodeEnvDevelopment", {
files: {
"/entry.js": /* js */ `
import { foo } from 'lib';
console.log(foo);
`,
"/node_modules/lib/index.js": /* js */ `
if (process.env.NODE_ENV === 'production') {
module.exports = require('./library.prod.js')
} else {
module.exports = require('./library.dev.js')
}
`,
"/node_modules/lib/library.prod.js": /* js */ `
module.exports.foo = 'FAILED';
`,
"/node_modules/lib/library.dev.js": /* js */ `
module.exports.foo = 'development';
`,
},
cjs2esm: true,
dce: true,
env: {
NODE_ENV: "development",
},
run: {
stdout: "development",
},
});
itBundled("cjs2esm/ModuleExportsEqualsRuntimeCondition", {
notImplemented: true,
files: {
"/entry.js": /* js */ `
import { foo } from 'lib';
console.log(foo);
`,
"/node_modules/lib/index.js": /* js */ `
if (globalThis.USE_PROD) {
module.exports = require('./library.prod.js')
} else {
module.exports = require('./library.dev.js')
}
`,
// these should have the cjs transform
"/node_modules/lib/library.prod.js": /* js */ `
module.exports.foo = 'production';
`,
"/node_modules/lib/library.dev.js": /* js */ `
module.exports.foo = 'development';
`,
},
cjs2esm: {
exclude: ["/node_modules/lib/index.js"],
},
run: {
stdout: "development",
},
});
});

View File

@@ -9,6 +9,18 @@ describe("bundler", () => {
"/entry.js": "",
},
});
itBundled("edgecase/EmptyCommonJSModule", {
files: {
"/entry.js": /* js */ `
import * as module from './module.cjs';
console.log(typeof module)
`,
"/module.cjs": /* js */ ``,
},
run: {
stdout: "object",
},
});
itBundled("edgecase/ImportStarFunction", {
files: {
"/entry.js": /* js */ `
@@ -83,4 +95,77 @@ describe("bundler", () => {
},
capture: ['"Hello\0"'],
});
// https://github.com/oven-sh/bun/issues/2699
itBundled("edgecase/ImportNamedFromExportStarCJS", {
files: {
"/entry.js": /* js */ `
import { foo } from './foo';
console.log(foo);
`,
"/foo.js": /* js */ `
export * from './bar.cjs';
`,
"/bar.cjs": /* js */ `
module.exports = { foo: 'bar' };
`,
},
run: {
stdout: "bar",
},
});
itBundled("edgecase/NodeEnvDefaultUnset", {
files: {
"/entry.js": /* js */ `
capture(process.env.NODE_ENV);
capture(process.env.NODE_ENV === 'production');
capture(process.env.NODE_ENV === 'development');
`,
},
platform: "browser",
capture: ['"development"', "false", "true"],
env: {
// undefined will ensure this variable is not passed to the bundler
NODE_ENV: undefined,
},
});
itBundled("edgecase/NodeEnvDefaultDevelopment", {
files: {
"/entry.js": /* js */ `
capture(process.env.NODE_ENV);
capture(process.env.NODE_ENV === 'production');
capture(process.env.NODE_ENV === 'development');
`,
},
platform: "browser",
capture: ['"development"', "false", "true"],
env: {
NODE_ENV: "development",
},
});
itBundled("edgecase/NodeEnvDefaultProduction", {
files: {
"/entry.js": /* js */ `
capture(process.env.NODE_ENV);
capture(process.env.NODE_ENV === 'production');
capture(process.env.NODE_ENV === 'development');
`,
},
platform: "browser",
capture: ['"production"', "true", "false"],
env: {
NODE_ENV: "production",
},
});
itBundled("edgecase/ProcessEnvArbitrary", {
files: {
"/entry.js": /* js */ `
capture(process.env.ARBITRARY);
`,
},
platform: "browser",
capture: ["process.env.ARBITRARY"],
env: {
ARBITRARY: "secret environment stuff!",
},
});
});

View File

@@ -1,5 +1,7 @@
import { describe } from "bun:test";
import { itBundled } from "./expectBundled";
import assert from "assert";
import dedent from "dedent";
import { bundlerTest, expectBundled, itBundled, testForFile } from "./expectBundled";
var { describe, test, expect } = testForFile(import.meta.path);
describe("bundler", () => {
itBundled("minify/TemplateStringFolding", {
@@ -49,7 +51,77 @@ describe("bundler", () => {
"!1",
"!1",
],
minifySyntax: true,
platform: "bun",
minifySyntax: true,
});
itBundled("minify/FunctionExpressionRemoveName", {
notImplemented: true,
files: {
"/entry.js": /* js */ `
capture(function remove() {});
capture(function() {});
capture(function rename_me() { rename_me() });
`,
},
// capture is pretty stupid and will stop at first )
capture: ["function(", "function(", "function e("],
minifySyntax: true,
minifyIdentifiers: true,
platform: "bun",
});
itBundled("minify/PrivateIdentifiersNameCollision", {
files: {
"/entry.js": /* js */ `
class C {
${new Array(500)
.fill(null)
.map((_, i) => `#identifier${i} = 123;`)
.join("\n")}
a = 456;
getAllValues() {
return [
${new Array(500)
.fill(null)
.map((_, i) => `this.#identifier${i}`)
.join(",")}
]
}
}
const values = new C().getAllValues();
for (const value of values) {
if(value !== 123) { throw new Error("Expected 123!"); }
}
console.log("a = " + new C().a);
`,
},
minifyIdentifiers: true,
run: { stdout: "a = 456" },
});
itBundled("minify/MergeAdjacentVars", {
files: {
"/entry.js": /* js */ `
var a = 1;
var b = 2;
var c = 3;
// some code to prevent inlining
a = 4;
console.log(a, b, c)
b = 5;
console.log(a, b, c)
c = 6;
console.log(a, b, c)
`,
},
minifySyntax: true,
run: { stdout: "4 2 3\n4 5 3\n4 5 6" },
onAfterBundle(api) {
const code = api.readFile("/out.js");
assert([...code.matchAll(/var /g)].length === 1, "expected only 1 variable declaration statement");
},
});
});

View File

@@ -279,7 +279,6 @@ describe("bundler", () => {
function nested() { return import('./c') },
]
import { deepEqual } from 'node:assert'
deepEqual(a, 1, 'a');
deepEqual(a2, 4, 'a2');
deepEqual(c3, 2, 'c3');
@@ -305,7 +304,9 @@ describe("bundler", () => {
export const a2 = 4;
`,
"/test.js": String.raw/* js */ `
import './out.js';
import { deepEqual } from 'node:assert';
globalThis.deepEqual = deepEqual;
await import ('./out.js');
if (!globalThis.aWasImported) {
throw new Error('"import \'./a\'" was tree-shaken when it should not have been.')
}
@@ -314,10 +315,11 @@ describe("bundler", () => {
}
`,
},
mode: "transform",
// mode: "transform",
run: {
file: "/test.js",
},
external: ["node:assert", "./a", "./b", "./c"],
} as const;
itBundled("default/ImportFormsWithNoBundle", {
...importFormsConfig,
@@ -1859,7 +1861,7 @@ describe("bundler", () => {
});
itBundled("default/ArgumentsSpecialCaseNoBundle", {
files: {
"/entry.js": /* js */ `
"/entry.cjs": /* js */ `
(async() => {
var arguments = 'var';
@@ -1902,7 +1904,7 @@ describe("bundler", () => {
// assertions:
// we need this helper function to get "Arguments" objects, though this only applies for tests using v8
const argumentsFor = new Function('return arguments;');
const assert = require('assert');
const assert = (0, require)('assert');
assert.deepEqual(f1(), [argumentsFor(), argumentsFor()], 'f1()');
assert.deepEqual(f1(1), [1, argumentsFor(1)], 'f1(1)');
assert.deepEqual(f2(), [argumentsFor(), argumentsFor()], 'f2()');
@@ -1949,42 +1951,58 @@ describe("bundler", () => {
assert.deepEqual(a19(1), [1, 'var'], 'a19(1)');
assert.deepEqual(await a20(), ['var', 'var'], 'a20()');
assert.deepEqual(await a20(1), [1, 'var'], 'a20(1)');
})();
})(1,3,5);
`,
},
format: "cjs",
format: "esm",
outfile: "/out.js",
minifyIdentifiers: true,
mode: "transform",
// mode: "transform",
});
itBundled("default/WithStatementTaintingNoBundle", {
// TODO: MANUAL CHECK: make sure the snapshot we use works.
files: {
"/entry.js": /* js */ `
(() => {
let local = 1
let outer = 2
let outerDead = 3
with ({}) {
console.log(local, outer, outerDead)
with ({ outer: 100, local: 150, hoisted: 200, extra: 500 }) {
console.log(outer, outerDead, hoisted, extra)
var hoisted = 4
let local = 5
hoisted++
local++
console.log(local, outer, outerDead, hoisted, extra)
if (1) outer++
if (0) outerDead++
console.log(local, outer, outerDead, hoisted, extra)
}
console.log(local, outer, outerDead, hoisted)
if (1) {
hoisted++
local++
outer++
outerDead++
}
console.log(local, outer, outerDead, hoisted)
})()
`,
},
format: "iife",
minifyIdentifiers: true,
mode: "transform",
run: {
runtime: "node",
stdout: `
1 2 3
100 3 200 500
6 100 3 5 500
6 101 3 5 500
1 2 3 undefined
2 3 4 NaN
`,
},
});
itBundled("default/DirectEvalTaintingNoBundle", {
files: {
@@ -2279,6 +2297,7 @@ describe("bundler", () => {
},
});
itBundled("default/AutoExternalNode", {
skipOnEsbuild: true,
files: {
"/entry.js": /* js */ `
// These URLs should be external automatically
@@ -2287,9 +2306,12 @@ describe("bundler", () => {
// This should be external and should be tree-shaken because it's side-effect free
import "node:path";
import "bun";
import "bun:sqlite";
// This should be external too, but shouldn't be tree-shaken because it could be a run-time error
import "node:what-is-this";
import "bun:what-is-this";
`,
},
platform: "node",
@@ -2368,18 +2390,22 @@ describe("bundler", () => {
get #bar() {}
set #bar(x) {}
}
cool(Foo)
cool(Bar)
`,
},
minifyIdentifiers: true,
mode: "transform",
onAfterBundle(api) {
const text = api.readFile("/out.js");
assert(text.includes("doNotRenameMe"), "bundler should not have renamed `doNotRenameMe`");
assert(!text.includes("#foo"), "bundler should have renamed `#foo`");
assert(text.includes("#"), "bundler keeps private variables private `#`");
},
});
// These labels should all share the same minified names
itBundled("default/MinifySiblingLabelsNoBundle", {
notImplemented: true,
files: {
"/entry.js": /* js */ `
foo: {
@@ -2403,48 +2429,68 @@ describe("bundler", () => {
`,
},
minifyIdentifiers: true,
mode: "transform",
onAfterBundle(api) {
const text = api.readFile("/out.js");
const labels = [...text.matchAll(/([a-z0-9]+):/gi)].map(x => x[1]);
expect(labels).toStrictEqual([labels[0], labels[1], labels[0], labels[1], labels[0], labels[1]]);
},
});
// This is such a fun file. it crashes prettier and some other parsers.
const crazyNestedLabelFile = dedent`
L001:{L002:{L003:{L004:{L005:{L006:{L007:{L008:{L009:{L010:{L011:{L012:{L013:{L014:{L015:{L016:{console.log('a')
L017:{L018:{L019:{L020:{L021:{L022:{L023:{L024:{L025:{L026:{L027:{L028:{L029:{L030:{L031:{L032:{console.log('a')
L033:{L034:{L035:{L036:{L037:{L038:{L039:{L040:{L041:{L042:{L043:{L044:{L045:{L046:{L047:{L048:{console.log('a')
L049:{L050:{L051:{L052:{L053:{L054:{L055:{L056:{L057:{L058:{L059:{L060:{L061:{L062:{L063:{L064:{console.log('a')
L065:{L066:{L067:{L068:{L069:{L070:{L071:{L072:{L073:{L074:{L075:{L076:{L077:{L078:{L079:{L080:{console.log('a')
L081:{L082:{L083:{L084:{L085:{L086:{L087:{L088:{L089:{L090:{L091:{L092:{L093:{L094:{L095:{L096:{console.log('a')
L097:{L098:{L099:{L100:{L101:{L102:{L103:{L104:{L105:{L106:{L107:{L108:{L109:{L110:{L111:{L112:{console.log('a')
L113:{L114:{L115:{L116:{L117:{L118:{L119:{L120:{L121:{L122:{L123:{L124:{L125:{L126:{L127:{L128:{console.log('a')
L129:{L130:{L131:{L132:{L133:{L134:{L135:{L136:{L137:{L138:{L139:{L140:{L141:{L142:{L143:{L144:{console.log('a')
L145:{L146:{L147:{L148:{L149:{L150:{L151:{L152:{L153:{L154:{L155:{L156:{L157:{L158:{L159:{L160:{console.log('a')
L161:{L162:{L163:{L164:{L165:{L166:{L167:{L168:{L169:{L170:{L171:{L172:{L173:{L174:{L175:{L176:{console.log('a')
L177:{L178:{L179:{L180:{L181:{L182:{L183:{L184:{L185:{L186:{L187:{L188:{L189:{L190:{L191:{L192:{console.log('a')
L193:{L194:{L195:{L196:{L197:{L198:{L199:{L200:{L201:{L202:{L203:{L204:{L205:{L206:{L207:{L208:{console.log('a')
L209:{L210:{L211:{L212:{L213:{L214:{L215:{L216:{L217:{L218:{L219:{L220:{L221:{L222:{L223:{L224:{console.log('a')
L225:{L226:{L227:{L228:{L229:{L230:{L231:{L232:{L233:{L234:{L235:{L236:{L237:{L238:{L239:{L240:{console.log('a')
L241:{L242:{L243:{L244:{L245:{L246:{L247:{L248:{L249:{L250:{L251:{L252:{L253:{L254:{L255:{L256:{console.log('a')
L257:{L258:{L259:{L260:{L261:{L262:{L263:{L264:{L265:{L266:{L267:{L268:{L269:{L270:{L271:{L272:{console.log('a')
L273:{L274:{L275:{L276:{L277:{L278:{L279:{L280:{L281:{L282:{L283:{L284:{L285:{L286:{L287:{L288:{console.log('a')
L289:{L290:{L291:{L292:{L293:{L294:{L295:{L296:{L297:{L298:{L299:{L300:{L301:{L302:{L303:{L304:{console.log('a')
L305:{L306:{L307:{L308:{L309:{L310:{L311:{L312:{L313:{L314:{L315:{L316:{L317:{L318:{L319:{L320:{console.log('a')
L321:{L322:{L323:{L324:{L325:{L326:{L327:{L328:{L329:{L330:{L331:{L332:{L333:{}}}}}}}}}}}}}}}}}}console.log('a')
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}console.log('a')
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}console.log('a')
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}console.log('a')
}}}}}}}}}}}}}}}}}}}}}}}}}}}
`;
itBundled("default/NestedLabelsBundle", {
files: {
"/entry.js": crazyNestedLabelFile,
},
});
itBundled("default/NestedLabelsNoBundle", {
files: {
"/entry.js": crazyNestedLabelFile,
},
mode: "transform",
});
itBundled("default/MinifyNestedLabelsNoBundle", {
files: {
"/entry.js": dedent`
L001:{L002:{L003:{L004:{L005:{L006:{L007:{L008:{L009:{L010:{L011:{L012:{L013:{L014:{L015:{L016:{console.log('a')
L017:{L018:{L019:{L020:{L021:{L022:{L023:{L024:{L025:{L026:{L027:{L028:{L029:{L030:{L031:{L032:{console.log('a')
L033:{L034:{L035:{L036:{L037:{L038:{L039:{L040:{L041:{L042:{L043:{L044:{L045:{L046:{L047:{L048:{console.log('a')
L049:{L050:{L051:{L052:{L053:{L054:{L055:{L056:{L057:{L058:{L059:{L060:{L061:{L062:{L063:{L064:{console.log('a')
L065:{L066:{L067:{L068:{L069:{L070:{L071:{L072:{L073:{L074:{L075:{L076:{L077:{L078:{L079:{L080:{console.log('a')
L081:{L082:{L083:{L084:{L085:{L086:{L087:{L088:{L089:{L090:{L091:{L092:{L093:{L094:{L095:{L096:{console.log('a')
L097:{L098:{L099:{L100:{L101:{L102:{L103:{L104:{L105:{L106:{L107:{L108:{L109:{L110:{L111:{L112:{console.log('a')
L113:{L114:{L115:{L116:{L117:{L118:{L119:{L120:{L121:{L122:{L123:{L124:{L125:{L126:{L127:{L128:{console.log('a')
L129:{L130:{L131:{L132:{L133:{L134:{L135:{L136:{L137:{L138:{L139:{L140:{L141:{L142:{L143:{L144:{console.log('a')
L145:{L146:{L147:{L148:{L149:{L150:{L151:{L152:{L153:{L154:{L155:{L156:{L157:{L158:{L159:{L160:{console.log('a')
L161:{L162:{L163:{L164:{L165:{L166:{L167:{L168:{L169:{L170:{L171:{L172:{L173:{L174:{L175:{L176:{console.log('a')
L177:{L178:{L179:{L180:{L181:{L182:{L183:{L184:{L185:{L186:{L187:{L188:{L189:{L190:{L191:{L192:{console.log('a')
L193:{L194:{L195:{L196:{L197:{L198:{L199:{L200:{L201:{L202:{L203:{L204:{L205:{L206:{L207:{L208:{console.log('a')
L209:{L210:{L211:{L212:{L213:{L214:{L215:{L216:{L217:{L218:{L219:{L220:{L221:{L222:{L223:{L224:{console.log('a')
L225:{L226:{L227:{L228:{L229:{L230:{L231:{L232:{L233:{L234:{L235:{L236:{L237:{L238:{L239:{L240:{console.log('a')
L241:{L242:{L243:{L244:{L245:{L246:{L247:{L248:{L249:{L250:{L251:{L252:{L253:{L254:{L255:{L256:{console.log('a')
L257:{L258:{L259:{L260:{L261:{L262:{L263:{L264:{L265:{L266:{L267:{L268:{L269:{L270:{L271:{L272:{console.log('a')
L273:{L274:{L275:{L276:{L277:{L278:{L279:{L280:{L281:{L282:{L283:{L284:{L285:{L286:{L287:{L288:{console.log('a')
L289:{L290:{L291:{L292:{L293:{L294:{L295:{L296:{L297:{L298:{L299:{L300:{L301:{L302:{L303:{L304:{console.log('a')
L305:{L306:{L307:{L308:{L309:{L310:{L311:{L312:{L313:{L314:{L315:{L316:{L317:{L318:{L319:{L320:{console.log('a')
L321:{L322:{L323:{L324:{L325:{L326:{L327:{L328:{L329:{L330:{L331:{L332:{L333:{}}}}}}}}}}}}}}}}}}console.log('a')
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}console.log('a')
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}console.log('a')
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}console.log('a')
}}}}}}}}}}}}}}}}}}}}}}}}}}}
`,
"/entry.js": crazyNestedLabelFile,
},
minifyWhitespace: true,
minifyIdentifiers: true,
minifySyntax: true,
mode: "transform",
});
itBundled("default/MinifyNestedLabelsBundle", {
files: {
"/entry.js": crazyNestedLabelFile,
},
minifyWhitespace: true,
minifyIdentifiers: true,
minifySyntax: true,
});
itBundled("default/ExportsAndModuleFormatCommonJS", {
files: {
"/entry.js": /* js */ `
@@ -4474,8 +4520,14 @@ describe("bundler", () => {
}
const a = capture(api.readFile("/out/a.js"));
const b = capture(api.readFile("/out/b.js"));
expect(a).toEqual(b);
expect(a).not.toEqual(["one", "two", "three", "four"]);
expect(b).not.toEqual(["one", "two", "three", "four"]);
try {
expect(a).toEqual(b);
} catch (error) {
console.error("Comments should not affect minified names!");
throw error;
}
},
});
itBundled("default/ImportRelativeAsPackage", {

View File

@@ -32,6 +32,10 @@ describe("bundler", () => {
"/foo.js": `export const foo = 123`,
},
run: {
// esbuild:
// stdout: '{"default":{"foo":123},"foo":123} 123 234',
// bun:
stdout: '{"foo":123} 123 234',
},
});
@@ -209,7 +213,7 @@ describe("bundler", () => {
"/foo.js": `exports.foo = 123`,
},
run: {
stdout: '{"default":{"foo":123},"foo":123} 123 234',
stdout: '{"foo":123} 123 234',
},
});
itBundled("importstar/ImportStarCommonJSNoCapture", {
@@ -247,6 +251,7 @@ describe("bundler", () => {
runtimeFiles: {
"/foo.js": `console.log('foo')`,
},
external: ["./foo"],
run: {
stdout: "foo\n234",
},
@@ -263,6 +268,7 @@ describe("bundler", () => {
runtimeFiles: {
"/foo.js": `export const foo = 123`,
},
external: ["./foo"],
run: {
stdout: '{"foo":123} 123 234',
},
@@ -275,6 +281,7 @@ describe("bundler", () => {
console.log(ns.foo, ns.foo, foo)
`,
},
external: ["./foo"],
mode: "transform",
runtimeFiles: {
"/foo.js": `export const foo = 123`,
@@ -292,6 +299,7 @@ describe("bundler", () => {
`,
},
minifySyntax: true,
external: ["./foo"],
mode: "transform",
runtimeFiles: {
"/foo.js": `console.log('foo')`,
@@ -310,6 +318,7 @@ describe("bundler", () => {
},
minifySyntax: true,
mode: "transform",
external: ["./foo"],
runtimeFiles: {
"/foo.js": `export const foo = 123`,
},
@@ -327,6 +336,7 @@ describe("bundler", () => {
},
minifySyntax: true,
mode: "transform",
external: ["./foo"],
runtimeFiles: {
"/foo.js": `export const foo = 123`,
},
@@ -355,7 +365,7 @@ describe("bundler", () => {
},
dce: true,
run: {
stdout: '{"x":1,"z":4}',
stdout: '{"z":4,"x":1}',
},
});
itBundled("importstar/ImportExportStarAmbiguousError", {
@@ -847,6 +857,9 @@ describe("bundler", () => {
run: {
stdout: '{"x":123} undefined',
},
bundleWarnings: {
"/entry.js": [`Import "foo" will always be undefined because there is no matching export in "foo.js"`],
},
});
itBundled("importstar/ExportOtherCommonJS", {
files: {
@@ -897,6 +910,9 @@ describe("bundler", () => {
run: {
stdout: "undefined",
},
bundleWarnings: {
"/entry.js": [`Import "foo" will always be undefined because there is no matching export in "foo.js"`],
},
});
itBundled("importstar/NamespaceImportMissingCommonJS", {
files: {
@@ -959,7 +975,7 @@ describe("bundler", () => {
"/bar.js": `export const x = 123`,
},
bundleErrors: {
"/foo.js": ['ERROR: No matching export in "bar.js" for import "foo"'],
"/foo.js": [`No matching export in "bar.js" for import "foo"`],
},
});
itBundled("importstar/NamespaceImportReExportUnusedMissingES6", {
@@ -972,7 +988,7 @@ describe("bundler", () => {
"/bar.js": `export const x = 123`,
},
bundleErrors: {
"/foo.js": ['ERROR: No matching export in "bar.js" for import "foo"'],
"/foo.js": [`No matching export in "bar.js" for import "foo"`],
},
});
itBundled("importstar/NamespaceImportReExportStarMissingES6", {
@@ -1208,7 +1224,7 @@ describe("bundler", () => {
`,
},
});
itBundled("importstar/ImportDefaultNamespaceComboNoDefault", {
const ImportDefaultNamespaceComboNoDefault = itBundled("importstar/ImportDefaultNamespaceComboNoDefault1", {
files: {
"/entry-default-ns-prop.js": `import def, * as ns from './foo'; console.log(def, ns, ns.default)`,
"/entry-default-ns.js": `import def, * as ns from './foo'; console.log(def, ns)`,
@@ -1217,20 +1233,40 @@ describe("bundler", () => {
"/entry-prop.js": `import * as ns from './foo'; console.log(ns.default)`,
"/foo.js": `export let foo = 123`,
},
entryPoints: [
"/entry-default-ns-prop.js",
"/entry-default-ns.js",
"/entry-default-prop.js",
"/entry-default.js",
"/entry-prop.js",
],
entryPoints: ["/entry-default-ns-prop.js"],
bundleErrors: {
"/entry-default-ns-prop.js": ['No matching export in "foo.js" for import "default"'],
},
});
itBundled("importstar/ImportDefaultNamespaceComboNoDefault2", {
...ImportDefaultNamespaceComboNoDefault.options,
entryPoints: ["/entry-default-ns.js"],
bundleErrors: {
"/entry-default-ns.js": ['No matching export in "foo.js" for import "default"'],
},
});
itBundled("importstar/ImportDefaultNamespaceComboNoDefault3", {
...ImportDefaultNamespaceComboNoDefault.options,
entryPoints: ["/entry-default-prop.js"],
bundleErrors: {
"/entry-default-prop.js": ['No matching export in "foo.js" for import "default"'],
},
});
itBundled("importstar/ImportDefaultNamespaceComboNoDefault4", {
...ImportDefaultNamespaceComboNoDefault.options,
entryPoints: ["/entry-default.js"],
bundleErrors: {
"/entry-default.js": ['No matching export in "foo.js" for import "default"'],
},
});
itBundled("importstar/ImportDefaultNamespaceComboNoDefault5", {
...ImportDefaultNamespaceComboNoDefault.options,
entryPoints: ["/entry-prop.js"],
bundleErrors: undefined,
bundleWarnings: {
"/entry-prop.js": [`Import "default" will always be undefined because there is no matching export in "foo.js"`],
},
});
itBundled("importstar/ImportNamespaceUndefinedPropertyEmptyFile", {
files: {
"/entry-nope.js": /* js */ `
@@ -1260,9 +1296,9 @@ describe("bundler", () => {
entryPoints: ["/entry-nope.js", "/entry-default.js"],
bundleWarnings: {
"/entry-nope.js": [
'Import "nope" will always be undefined because the file "empty.js" has no exports',
'Import "nope" will always be undefined because the file "empty.mjs" has no exports',
'Import "nope" will always be undefined because the file "empty.cjs" has no exports',
`Import "nope" will always be undefined because there is no matching export in "empty.js"`,
`Import "nope" will always be undefined because there is no matching export in "empty.mjs"`,
`Import "nope" will always be undefined because there is no matching export in "empty.cjs"`,
],
},
run: [
@@ -1314,6 +1350,21 @@ describe("bundler", () => {
stdout: `js\ncjs\n{} undefined {}`,
},
],
bundleWarnings: {
"/foo/no-side-effects.js": [
`Import "nope" will always be undefined because the file "foo/no-side-effects.js" has no exports`,
],
"/foo/no-side-effects.mjs": [
`Import "nope" will always be undefined because the file "foo/no-side-effects.mjs" has no exports`,
],
"/foo/no-side-effects.cjs": [
`Import "nope" will always be undefined because the file "foo/no-side-effects.cjs" has no exports`,
],
"/entry-default.js": [
`Import "default" will always be undefined because there is no matching export in "foo/no-side-effects.js"`,
`Import "default" will always be undefined because there is no matching export in "foo/no-side-effects.mjs"`,
],
},
});
itBundled("importstar/ReExportStarEntryPointAndInnerFile", {
files: {

View File

@@ -102,26 +102,28 @@ describe("bundler", () => {
},
});
itBundled("loader/JSXPreserveCapitalLetterMinify", {
// GENERATED
files: {
"/entry.jsx": /* jsx */ `
import { mustStartWithUpperCaseLetter as XYYYY } from './foo'
console.log(<XYYYY tag-must-start-with-capital-letter />)
// This should be named "Y" due to frequency analysis
console.log(<XYYYY YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY />)
`,
"/foo.js": `export class mustStartWithUpperCaseLetter {}`,
},
external: ["react"],
minifyIdentifiers: true,
});
itBundled("loader/JSXPreserveCapitalLetterMinifyNested", {
// GENERATED
files: {
"/entry.jsx": /* jsx */ `
x = () => {
class XYYYYY {} // This should be named "Y" due to frequency analysis
return <XYYYYY tag-must-start-with-capital-letter />
class RENAME_ME {} // This should be named "Y" due to frequency analysis
capture(RENAME_ME)
return <RENAME_ME YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY />
}
`,
},
external: ["react"],
minifyIdentifiers: true,
});
itBundled("loader/RequireCustomExtensionString", {

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,6 @@
import { RUN_UNCHECKED_TESTS, itBundled, testForFile } from "../expectBundled";
import assert from "assert";
import { readdirSync } from "fs";
import { itBundled, testForFile } from "../expectBundled";
var { describe, test, expect } = testForFile(import.meta.path);
// Tests ported from:
@@ -7,7 +9,7 @@ var { describe, test, expect } = testForFile(import.meta.path);
// For debug, all files are written to $TEMP/bun-bundle-tests/splitting
describe("bundler", () => {
itBundled("splitting/SplittingSharedES6IntoES6", {
itBundled("splitting/SharedES6IntoES6", {
files: {
"/a.js": /* js */ `
import {foo} from "./shared.js"
@@ -21,7 +23,6 @@ describe("bundler", () => {
},
entryPoints: ["/a.js", "/b.js"],
splitting: true,
format: "esm",
run: [
{ file: "/out/a.js", stdout: "123" },
{ file: "/out/b.js", stdout: "123" },
@@ -31,9 +32,7 @@ describe("bundler", () => {
"/out/b.js": "123",
},
});
if (!RUN_UNCHECKED_TESTS) return;
itBundled("splitting/SplittingSharedCommonJSIntoES6", {
// GENERATED
itBundled("splitting/SharedCommonJSIntoES6", {
files: {
"/a.js": /* js */ `
const {foo} = require("./shared.js")
@@ -47,28 +46,46 @@ describe("bundler", () => {
},
entryPoints: ["/a.js", "/b.js"],
splitting: true,
format: "esm",
run: [
{ file: "/out/a.js", stdout: "123" },
{ file: "/out/b.js", stdout: "123" },
],
assertNotPresent: {
"/out/a.js": "123",
"/out/b.js": "123",
},
});
itBundled("splitting/SplittingDynamicES6IntoES6", {
// GENERATED
itBundled("splitting/DynamicES6IntoES6", {
files: {
"/entry.js": `import("./foo.js").then(({bar}) => console.log(bar))`,
"/foo.js": `export let bar = 123`,
},
splitting: true,
format: "esm",
outdir: "/out",
assertNotPresent: {
"/out/entry.js": "123",
},
run: {
file: "/out/entry.js",
stdout: "123",
},
});
itBundled("splitting/SplittingDynamicCommonJSIntoES6", {
// GENERATED
itBundled("splitting/DynamicCommonJSIntoES6", {
files: {
"/entry.js": `import("./foo.js").then(({default: {bar}}) => console.log(bar))`,
"/foo.js": `exports.bar = 123`,
},
splitting: true,
format: "esm",
outdir: "/out",
assertNotPresent: {
"/out/entry.js": "123",
},
run: {
file: "/out/entry.js",
stdout: "123",
},
});
itBundled("splitting/SplittingDynamicAndNotDynamicES6IntoES6", {
// GENERATED
itBundled("splitting/DynamicAndNotDynamicES6IntoES6", {
files: {
"/entry.js": /* js */ `
import {bar as a} from "./foo.js"
@@ -77,10 +94,10 @@ describe("bundler", () => {
"/foo.js": `export let bar = 123`,
},
splitting: true,
format: "esm",
outdir: "/out",
});
itBundled("splitting/SplittingDynamicAndNotDynamicCommonJSIntoES6", {
// GENERATED
itBundled("splitting/DynamicAndNotDynamicCommonJSIntoES6", {
skipOnEsbuild: true,
files: {
"/entry.js": /* js */ `
import {bar as a} from "./foo.js"
@@ -88,11 +105,14 @@ describe("bundler", () => {
`,
"/foo.js": `exports.bar = 123`,
},
outdir: "/out",
splitting: true,
format: "esm",
run: {
file: "/out/entry.js",
stdout: "123 123",
},
});
itBundled("splitting/SplittingAssignToLocal", {
// GENERATED
itBundled("splitting/AssignToLocal", {
files: {
"/a.js": /* js */ `
import {foo, setFoo} from "./shared.js"
@@ -104,7 +124,7 @@ describe("bundler", () => {
console.log(foo)
`,
"/shared.js": /* js */ `
export let foo
export let foo = 456
export function setFoo(value) {
foo = value
}
@@ -112,10 +132,24 @@ describe("bundler", () => {
},
entryPoints: ["/a.js", "/b.js"],
splitting: true,
format: "esm",
runtimeFiles: {
"/test1.js": /* js */ `
await import('./out/a.js')
await import('./out/b.js')
`,
"/test2.js": /* js */ `
await import('./out/b.js')
await import('./out/a.js')
`,
},
run: [
{ file: "/out/a.js", stdout: "123" },
{ file: "/out/b.js", stdout: "456" },
{ file: "/test1.js", stdout: "123\n123" },
{ file: "/test2.js", stdout: "456\n123" },
],
});
itBundled("splitting/SplittingSideEffectsWithoutDependencies", {
// GENERATED
itBundled("splitting/SideEffectsWithoutDependencies", {
files: {
"/a.js": /* js */ `
import {a} from "./shared.js"
@@ -133,10 +167,24 @@ describe("bundler", () => {
},
entryPoints: ["/a.js", "/b.js"],
splitting: true,
format: "esm",
runtimeFiles: {
"/test1.js": /* js */ `
await import('./out/a.js')
await import('./out/b.js')
`,
"/test2.js": /* js */ `
await import('./out/b.js')
await import('./out/a.js')
`,
},
run: [
{ file: "/out/a.js", stdout: "side effect\n1" },
{ file: "/out/b.js", stdout: "side effect\n2" },
{ file: "/test1.js", stdout: "side effect\n1\n2" },
{ file: "/test2.js", stdout: "side effect\n2\n1" },
],
});
itBundled("splitting/SplittingNestedDirectories", {
// GENERATED
itBundled("splitting/NestedDirectories", {
files: {
"/Users/user/project/src/pages/pageA/page.js": /* js */ `
import x from "../shared.js"
@@ -149,11 +197,15 @@ describe("bundler", () => {
"/Users/user/project/src/pages/shared.js": `export default 123`,
},
entryPoints: ["/Users/user/project/src/pages/pageA/page.js", "/Users/user/project/src/pages/pageB/page.js"],
outputPaths: ["/out/pageA/page.js", "/out/pageB/page.js"],
splitting: true,
format: "esm",
run: [
{ file: "/out/pageA/page.js", stdout: "123" },
{ file: "/out/pageB/page.js", stdout: "-123" },
],
});
itBundled("splitting/SplittingCircularReferenceESBuildIssue251", {
// GENERATED
itBundled("splitting/CircularReferenceESBuildIssue251", {
files: {
"/a.js": /* js */ `
export * from './b.js';
@@ -162,22 +214,37 @@ describe("bundler", () => {
"/b.js": /* js */ `
export * from './a.js';
export var q = 6;
export function foo() {
q = 7;
}
`,
},
entryPoints: ["/a.js", "/b.js"],
splitting: true,
format: "esm",
runtimeFiles: {
"/test.js": /* js */ `
import { p, q, foo } from './out/a.js';
console.log(p, q)
import { p as p2, q as q2, foo as foo2 } from './out/b.js';
console.log(p2, q2)
console.log(foo === foo2)
foo();
console.log(q, q2)
`,
},
run: [{ file: "/test.js", stdout: "5 6\n5 6\ntrue\n7 7" }],
});
itBundled("splitting/SplittingMissingLazyExport", {
// GENERATED
itBundled("splitting/MissingLazyExport", {
files: {
"/a.js": /* js */ `
import {foo} from './common.js'
console.log(foo())
console.log(JSON.stringify(foo()))
`,
"/b.js": /* js */ `
import {bar} from './common.js'
console.log(bar())
console.log(JSON.stringify(bar()))
`,
"/common.js": /* js */ `
import * as ns from './empty.js'
@@ -191,49 +258,75 @@ describe("bundler", () => {
},
entryPoints: ["/a.js", "/b.js"],
splitting: true,
format: "esm",
/* TODO FIX expectedCompileLog: `common.js: WARNING: Import "missing" will always be undefined because the file "empty.js" has no exports
`, */
run: [
{ file: "/out/a.js", stdout: "[{},null]" },
{ file: "/out/b.js", stdout: "[null]" },
],
bundleWarnings: {
"/empty.js": [`Import "missing" will always be undefined because the file "empty.js" has no exports`],
},
});
itBundled("splitting/SplittingReExportESBuildIssue273", {
// GENERATED
itBundled("splitting/ReExportESBuildIssue273", {
files: {
"/a.js": `export const a = 1`,
"/a.js": `export const a = { value: 1 }`,
"/b.js": `export { a } from './a'`,
},
entryPoints: ["/a.js", "/b.js"],
splitting: true,
format: "esm",
runtimeFiles: {
"/test.js": /* js */ `
import { a } from './out/a.js';
import { a as a2 } from './out/b.js';
console.log(a === a2, a.value, a2.value)
`,
},
run: [{ file: "/test.js", stdout: "true 1 1" }],
});
itBundled("splitting/SplittingDynamicImportESBuildIssue272", {
// GENERATED
itBundled("splitting/DynamicImportESBuildIssue272", {
files: {
"/a.js": `import('./b')`,
"/b.js": `export default 1`,
"/b.js": `export default 1; console.log('imported')`,
},
entryPoints: ["/a.js", "/b.js"],
splitting: true,
format: "esm",
run: [{ file: "/out/a.js", stdout: "imported" }],
assertNotPresent: {
"/out/a.js": "imported",
},
});
itBundled("splitting/SplittingDynamicImportOutsideSourceTreeESBuildIssue264", {
// GENERATED
itBundled("splitting/DynamicImportOutsideSourceTreeESBuildIssue264", {
files: {
"/Users/user/project/src/entry1.js": `import('package')`,
"/Users/user/project/src/entry2.js": `import('package')`,
"/Users/user/project/node_modules/package/index.js": `console.log('imported')`,
},
runtimeFiles: {
"/both.js": /* js */ `
import('./out/entry1.js');
import('./out/entry2.js');
`,
},
entryPoints: ["/Users/user/project/src/entry1.js", "/Users/user/project/src/entry2.js"],
splitting: true,
format: "esm",
run: [
{ file: "/out/entry1.js", stdout: "imported" },
{ file: "/out/entry2.js", stdout: "imported" },
{ file: "/both.js", stdout: "imported" },
],
});
itBundled("splitting/SplittingCrossChunkAssignmentDependencies", {
// GENERATED
itBundled("splitting/CrossChunkAssignmentDependencies", {
files: {
"/a.js": /* js */ `
import {setValue} from './shared'
setValue(123)
`,
"/b.js": `import './shared'`,
"/b.js": `import './shared'; console.log('b')`,
"/c.js": /* js */ `
import * as shared from './shared'
globalThis.shared = shared;
`,
"/shared.js": /* js */ `
var observer;
var value;
@@ -244,59 +337,136 @@ describe("bundler", () => {
return value;
}
export function setValue(next) {
console.log('setValue', next)
value = next;
if (observer) observer();
}
sideEffects(getValue);
console.log("side effects!", getValue);
`,
},
entryPoints: ["/a.js", "/b.js"],
entryPoints: ["/a.js", "/b.js", "/c.js"],
splitting: true,
format: "esm",
runtimeFiles: {
"/test.js": /* js */ `
import './out/c.js';
const { getValue, setObserver } = globalThis.shared;
function observer() {
console.log('observer', getValue());
}
setObserver(observer);
import('./out/a.js');
import('./out/b.js');
`,
},
run: [
{ file: "/out/a.js", stdout: "side effects! [Function]\nsetValue 123" },
{ file: "/out/b.js", stdout: "side effects! [Function]\nb" },
{ file: "/test.js", stdout: "side effects! [Function]\nsetValue 123\nobserver 123\nb" },
],
});
itBundled("splitting/SplittingCrossChunkAssignmentDependenciesRecursive", {
// GENERATED
itBundled("splitting/CrossChunkAssignmentDependenciesRecursive", {
files: {
"/a.js": /* js */ `
import { setX } from './x'
setX()
globalThis.a = { setX };
`,
"/b.js": /* js */ `
import { setZ } from './z'
setZ()
globalThis.b = { setZ };
`,
"/c.js": /* js */ `
import { setX2 } from './x'
import { setY2 } from './y'
import { setZ2 } from './z'
setX2();
setY2();
setZ2();
globalThis.c = { setX2, setY2, setZ2 };
`,
"/x.js": /* js */ `
let _x
export function setX(v) { _x = v }
export function setX2(v) { _x = v }
globalThis.x = { setX, setX2 };
`,
"/y.js": /* js */ `
import { setX } from './x'
let _y
export function setY(v) { _y = v }
export function setY2(v) { setX(v); _y = v }
globalThis.y = { setY, setY2 };
`,
"/z.js": /* js */ `
import { setY } from './y'
let _z
export function setZ(v) { _z = v }
export function setZ2(v) { setY(v); _z = v }
globalThis.z = { setZ, setZ2, setY };
`,
},
entryPoints: ["/a.js", "/b.js", "/c.js"],
splitting: true,
format: "esm",
runtimeFiles: {
"/test_all.js": /* js */ `
import './out/a.js';
import './out/b.js';
import './out/c.js';
try {
a; b; c; x; y; z; // throw if not defined
} catch (error) {
throw new Error('chunks were not emitted right.')
}
import assert from 'assert';
assert(a.setX === x.setX, 'a.setX');
assert(b.setZ === z.setZ, 'b.setZ');
assert(c.setX2 === x.setX2, 'c.setX2');
assert(c.setY2 === y.setY2, 'c.setY2');
assert(c.setZ2 === z.setZ2, 'c.setZ2');
assert(z.setY === y.setY, 'z.setY');
`,
"/test_a_only.js": /* js */ `
import './out/a.js';
try {
a; x; // throw if not defined
} catch (error) {
throw new Error('chunks were not emitted right.')
}
import assert from 'assert';
assert(a.setX === x.setX, 'a.setX');
assert(globalThis.b === undefined, 'b should not be loaded');
assert(globalThis.c === undefined, 'c should not be loaded');
assert(globalThis.y === undefined, 'y should not be loaded');
assert(globalThis.z === undefined, 'z should not be loaded');
`,
"/test_b_only.js": /* js */ `
import './out/b.js';
try {
b; x; y; z; // throw if not defined
} catch (error) {
throw new Error('chunks were not emitted right.')
}
import assert from 'assert';
assert(globalThis.a === undefined, 'a should not be loaded');
assert(globalThis.c === undefined, 'c should not be loaded');
`,
"/test_c_only.js": /* js */ `
import './out/c.js';
try {
c; x; y; z; // throw if not defined
} catch (error) {
throw new Error('chunks were not emitted right.')
}
import assert from 'assert';
assert(globalThis.a === undefined, 'a should not be loaded');
assert(globalThis.b === undefined, 'b should not be loaded');
`,
},
run: [
{ file: "/test_all.js" },
{ file: "/test_a_only.js" },
{ file: "/test_b_only.js" },
{ file: "/test_c_only.js" },
],
});
itBundled("splitting/SplittingDuplicateChunkCollision", {
// GENERATED
itBundled("splitting/DuplicateChunkCollision", {
files: {
"/a.js": `import "./ab"`,
"/b.js": `import "./ab"`,
@@ -308,10 +478,12 @@ describe("bundler", () => {
entryPoints: ["/a.js", "/b.js", "/c.js", "/d.js"],
splitting: true,
minifyWhitespace: true,
format: "esm",
onAfterBundle(api) {
const files = readdirSync(api.outdir);
expect(files.length).toBe(6);
},
});
itBundled("splitting/SplittingMinifyIdentifiersCrashESBuildIssue437", {
// GENERATED
itBundled("splitting/MinifyIdentifiersCrashESBuildIssue437", {
files: {
"/a.js": /* js */ `
import {foo} from "./shared"
@@ -327,39 +499,65 @@ describe("bundler", () => {
entryPoints: ["/a.js", "/b.js", "/c.js"],
splitting: true,
minifyIdentifiers: true,
format: "esm",
run: [
{ file: "/out/a.js", stdout: "[Function]" },
{ file: "/out/b.js", stdout: "[Function]" },
],
});
itBundled("splitting/SplittingHybridESMAndCJSESBuildIssue617", {
// GENERATED
itBundled("splitting/HybridESMAndCJSESBuildIssue617", {
files: {
"/a.js": `export let foo`,
"/a.js": `export let foo = 123`,
"/b.js": `export let bar = require('./a')`,
},
entryPoints: ["/a.js", "/b.js"],
splitting: true,
format: "esm",
assertNotPresent: {
"/out/b.js": `123`,
},
runtimeFiles: {
"/test.js": /* js */ `
import { foo } from './out/a.js'
import { bar } from './out/b.js'
console.log(JSON.stringify({ foo, bar }))
`,
},
run: {
file: "/test.js",
stdout: '{"foo":123,"bar":{"foo":123}}',
},
});
itBundled("splitting/SplittingPublicPathEntryName", {
// GENERATED
itBundled("splitting/PublicPathEntryName", {
files: {
"/a.js": `import("./b")`,
"/b.js": `console.log('b')`,
},
outdir: "/out",
splitting: true,
format: "esm",
publicPath: "/www",
onAfterBundle(api) {
const t = new Bun.Transpiler();
const imports = t.scanImports(api.readFile("/out/a.js"));
expect(imports.length).toBe(1);
expect(imports[0].kind).toBe("dynamic-import");
assert(imports[0].path.startsWith("/www/"), `Expected path to start with "/www/" but got "${imports[0].path}"`);
},
});
itBundled("splitting/SplittingChunkPathDirPlaceholderImplicitOutbase", {
// GENERATED
itBundled("splitting/ChunkPathDirPlaceholderImplicitOutbase", {
files: {
"/project/entry.js": `console.log(import('./output-path/should-contain/this-text/file'))`,
"/project/output-path/should-contain/this-text/file.js": `console.log('file.js')`,
},
format: "esm",
outdir: "/out",
splitting: true,
chunkNames: "[dir]/[name]-[hash].[ext]",
onAfterBundle(api) {
assert(
readdirSync(api.outdir + "/output-path/should-contain/this-text").length === 1,
"Expected one file in out/output-path/should-contain/this-text/",
);
},
});
itBundled("splitting/EdgeCaseESBuildIssue2793WithSplitting", {
// GENERATED
const EdgeCaseESBuildIssue2793WithSplitting = itBundled("splitting/EdgeCaseESBuildIssue2793WithSplitting", {
files: {
"/src/a.js": `export const A = 42;`,
"/src/b.js": `export const B = async () => (await import(".")).A`,
@@ -370,21 +568,31 @@ describe("bundler", () => {
},
outdir: "/out",
entryPoints: ["/src/index.js"],
format: "esm",
splitting: true,
platform: "browser",
runtimeFiles: {
"/test.js": /* js */ `
import { A, B } from './out/index.js'
console.log(A, B() instanceof Promise, await B())
`,
},
run: {
file: "/test.js",
stdout: "42 true 42",
},
});
itBundled("splitting/EdgeCaseESBuildIssue2793WithoutSplitting", {
// GENERATED
files: {
"/src/a.js": `export const A = 42;`,
"/src/b.js": `export const B = async () => (await import(".")).A`,
"/src/index.js": /* js */ `
export * from "./a"
export * from "./b"
...EdgeCaseESBuildIssue2793WithSplitting.options,
splitting: false,
runtimeFiles: {
"/test.js": /* js */ `
import { A, B } from './out/index.js'
console.log(A, B() instanceof Promise, await B())
`,
},
entryPoints: ["/src/index.js"],
format: "esm",
outdir: "/out",
run: {
file: "/test.js",
stdout: "42 true 42",
},
});
});

View File

@@ -5,10 +5,10 @@ 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/ts
// For debug, all files are written to $TEMP/bun-bundle-tests/
describe("bundler", () => {
itBundled("ts/TSDeclareConst", {
itBundled("ts/DeclareConst", {
files: {
"/entry.ts": /* ts */ `
declare const require: any
@@ -27,7 +27,7 @@ describe("bundler", () => {
assert(!api.readFile("/out.js").includes("const foo"), 'does not include "const foo"');
},
});
itBundled("ts/TSDeclareLet", {
itBundled("ts/DeclareLet", {
files: {
"/entry.ts": /* ts */ `
declare let require: any
@@ -45,7 +45,7 @@ describe("bundler", () => {
assert(!api.readFile("/out.js").includes("module"), 'does not include "module"');
},
});
itBundled("ts/TSDeclareVar", {
itBundled("ts/DeclareVar", {
files: {
"/entry.ts": /* ts */ `
declare var require: any
@@ -63,7 +63,7 @@ describe("bundler", () => {
assert(!api.readFile("/out.js").includes("module"), 'does not include "module"');
},
});
itBundled("ts/TSDeclareClass", {
itBundled("ts/DeclareClass", {
files: {
"/entry.ts": /* ts */ `
declare class require {}
@@ -82,7 +82,7 @@ describe("bundler", () => {
assert(!api.readFile("/out.js").includes("class"), 'does not include "class"');
},
});
itBundled("ts/TSDeclareClassFields", {
itBundled("ts/DeclareClassFields", {
files: {
"/entry.ts": /* ts */ `
import './setup'
@@ -101,15 +101,15 @@ describe("bundler", () => {
`,
"/define-false/index.ts": /* ts */ `
class Foo {
a
declare b
[(() => null, c)]
declare [(() => null, d)]
a = 1
declare b: number
[(() => null, c)] = 3
declare [(() => null, d)]: number
static A
static declare B
static [(() => null, C)]
static declare [(() => null, D)]
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))
@@ -141,14 +141,14 @@ describe("bundler", () => {
},
run: {
stdout: `
Foo {"name":{"value":"Foo","writable":false,"enumerable":false,"configurable":true}}
new Foo {}
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/TSDeclareFunction", {
itBundled("ts/DeclareFunction", {
files: {
"/entry.ts": /* ts */ `
declare function require(): void
@@ -167,7 +167,7 @@ describe("bundler", () => {
assert(!api.readFile("/out.js").includes("function"), 'does not include "function"');
},
});
itBundled("ts/TSDeclareNamespace", {
itBundled("ts/DeclareNamespace", {
files: {
"/entry.ts": /* ts */ `
declare namespace require {}
@@ -186,7 +186,7 @@ describe("bundler", () => {
assert(!api.readFile("/out.js").includes("namespace"), 'does not include "namespace"');
},
});
itBundled("ts/TSDeclareEnum", {
itBundled("ts/DeclareEnum", {
files: {
"/entry.ts": /* ts */ `
declare enum require {}
@@ -205,7 +205,7 @@ describe("bundler", () => {
assert(!api.readFile("/out.js").includes("enum"), 'does not include "enum"');
},
});
itBundled("ts/TSDeclareConstEnum", {
itBundled("ts/DeclareConstEnum", {
files: {
"/entry.ts": /* ts */ `
declare const enum require {}
@@ -225,7 +225,10 @@ describe("bundler", () => {
assert(!api.readFile("/out.js").includes("const"), 'does not include "const"');
},
});
itBundled("ts/TSConstEnumComments", {
itBundled("ts/ConstEnumComments", {
// When it comes time to implement this inlining, we may decide we do NOT
// want to insert helper comments.
notImplemented: true,
files: {
"/bar.ts": /* ts */ `
export const enum Foo {
@@ -268,7 +271,7 @@ describe("bundler", () => {
stdout: `{"should have comments":[1,1],"should not have comments":[2,2]}`,
},
});
itBundled("ts/TSImportEmptyNamespace", {
itBundled("ts/ImportEmptyNamespace", {
files: {
"/entry.ts": /* ts */ `
import {REMOVE} from './ns.ts'
@@ -280,7 +283,7 @@ describe("bundler", () => {
dce: true,
run: true,
});
itBundled("ts/TSImportMissingES6", {
itBundled("ts/ImportMissingES6", {
files: {
"/entry.ts": /* ts */ `
import fn, {x as a, y as b} from './foo'
@@ -290,24 +293,25 @@ describe("bundler", () => {
},
bundleErrors: {
"/entry.ts": [
`No matching export "default" in "foo.js" for import "default"`,
`No matching export "y" in "foo.js" for import "y"`,
`No matching export in "foo.js" for import "default"`,
`No matching export in "foo.js" for import "y"`,
],
},
});
itBundled("ts/TSImportMissingUnusedES6", {
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/TSExportMissingES6", {
itBundled("ts/ExportMissingES6", {
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`,
},
@@ -315,7 +319,7 @@ describe("bundler", () => {
stdout: `{}`,
},
});
itBundled("ts/TSImportMissingFile", {
itBundled("ts/ImportMissingFile", {
files: {
"/entry.ts": /* ts */ `
import {Something} from './doesNotExist.ts'
@@ -326,7 +330,7 @@ describe("bundler", () => {
"/entry.ts": [`Could not resolve: "./doesNotExist.ts"`],
},
});
itBundled("ts/TSImportTypeOnlyFile", {
itBundled("ts/ImportTypeOnlyFile", {
files: {
"/entry.ts": /* ts */ `
import {SomeType1} from './doesNotExist1.ts'
@@ -340,7 +344,7 @@ describe("bundler", () => {
stdout: "2",
},
});
itBundled("ts/TSExportEquals", {
itBundled("ts/ExportEquals", {
files: {
"/a.ts": /* ts */ `
import b from './b.ts'
@@ -355,7 +359,7 @@ describe("bundler", () => {
stdout: `[123,null]`,
},
});
itBundled("ts/TSExportNamespace", {
itBundled("ts/ExportNamespace", {
files: {
"/a.ts": /* ts */ `
import {Foo} from './b.ts'
@@ -377,33 +381,34 @@ describe("bundler", () => {
stdout: `{}\n1\n2`,
},
});
itBundled("ts/TSMinifyEnum", {
itBundled("ts/MinifyEnum", {
notImplemented: true,
files: {
"/a.ts": `enum Foo { A, B, C = Foo }\ncapture(Foo)`,
"/b.ts": `export enum Foo { X, Y, Z = Foo }`,
// "/b.ts": `export enum Foo { X, Y, Z = Foo }`,
},
entryPoints: ["/a.ts", "/b.ts"],
entryPoints: ["/a.ts"],
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
mode: "transform",
onAfterBundle(api) {
const a = api.readFile("/out/a.js");
api.writeFile("/out/a.edited.js", a.replace(/capture\((.*?)\)/, `export const Foo = $1`));
const b = api.readFile("/out/b.js");
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)");
// 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 {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')
@@ -411,6 +416,37 @@ describe("bundler", () => {
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", {
notImplemented: true,
files: {
"/b.ts": `export enum Foo { X, Y, Z = Foo }`,
},
entryPoints: ["/b.ts"],
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
mode: "transform",
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')
@@ -420,7 +456,7 @@ describe("bundler", () => {
`,
},
});
itBundled("ts/TSMinifyNestedEnum", {
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 }`,
@@ -429,7 +465,6 @@ describe("bundler", () => {
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
mode: "transform",
onAfterBundle(api) {
const a = api.readFile("/out/a.js");
api.writeFile("/out/a.edited.js", a.replace(/capture\((.*?)\)/, `export const Foo = $1`));
@@ -461,7 +496,7 @@ describe("bundler", () => {
`,
},
});
itBundled("ts/TSMinifyNestedEnumNoLogicalAssignment", {
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 }`,
@@ -481,7 +516,7 @@ describe("bundler", () => {
assert(!b.includes("||="), "b should not use logical assignment");
},
});
itBundled("ts/TSMinifyNestedEnumNoArrow", {
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 }`,
@@ -502,7 +537,7 @@ describe("bundler", () => {
assert(!b.includes("=>"), "b should not use arrow");
},
});
itBundled("ts/TSMinifyNamespace", {
itBundled("ts/MinifyNamespace", {
files: {
"/a.ts": /* ts */ `
namespace Foo {
@@ -524,7 +559,6 @@ describe("bundler", () => {
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
mode: "transform",
onAfterBundle(api) {
api.writeFile("/out/a.edited.js", api.readFile("/out/a.js").replace(/capture\((.*?)\)/, `export const Foo = $1`));
},
@@ -540,7 +574,7 @@ describe("bundler", () => {
`,
},
});
itBundled("ts/TSMinifyNamespaceNoLogicalAssignment", {
itBundled("ts/MinifyNamespaceNoLogicalAssignment", {
files: {
"/a.ts": /* ts */ `
namespace Foo {
@@ -573,7 +607,7 @@ describe("bundler", () => {
assert(!b.includes("||="), "b should not use logical assignment");
},
});
itBundled("ts/TSMinifyNamespaceNoArrow", {
itBundled("ts/MinifyNamespaceNoArrow", {
files: {
"/a.ts": /* ts */ `
namespace Foo {
@@ -606,7 +640,7 @@ describe("bundler", () => {
assert(!b.includes("=>"), "b should not use arrow");
},
});
itBundled("ts/TSMinifyDerivedClass", {
itBundled("ts/MinifyDerivedClass", {
files: {
"/entry.ts": /* ts */ `
class Foo extends Bar {
@@ -648,7 +682,7 @@ describe("bundler", () => {
stdout: "super\n1 2 3",
},
});
itBundled("ts/TSImportVsLocalCollisionAllTypes", {
itBundled("ts/ImportVsLocalCollisionAllTypes", {
files: {
"/entry.ts": /* ts */ `
import {a, b, c, d, e} from './other.ts'
@@ -665,7 +699,7 @@ describe("bundler", () => {
stdout: '[null,0,null,5,{"prop":2}]',
},
});
itBundled("ts/TSImportVsLocalCollisionMixed", {
itBundled("ts/ImportVsLocalCollisionMixed", {
files: {
"/entry.ts": /* ts */ `
import {a, b, c, d, e, real} from './other.ts'
@@ -682,7 +716,8 @@ describe("bundler", () => {
stdout: '[null,0,null,5,{"prop":2},123]',
},
});
itBundled("ts/TSImportEqualsEliminationTest", {
itBundled("ts/ImportEqualsEliminationTest", {
notImplemented: true,
files: {
"/entry.ts": /* ts */ `
import a = foo.a
@@ -713,7 +748,7 @@ describe("bundler", () => {
stdout: "123",
},
});
itBundled("ts/TSImportEqualsTreeShakingFalse", {
itBundled("ts/ImportEqualsTreeShakingFalse", {
files: {
"/entry.ts": /* ts */ `
import { foo } from 'pkg'
@@ -725,8 +760,9 @@ describe("bundler", () => {
treeShaking: false,
dce: true,
mode: "transform",
external: ["pkg"],
});
itBundled("ts/TSImportEqualsTreeShakingTrue", {
itBundled("ts/ImportEqualsTreeShakingTrue", {
files: {
"/entry.ts": /* ts */ `
import { foo } from 'pkg'
@@ -737,9 +773,11 @@ describe("bundler", () => {
},
dce: true,
treeShaking: true,
external: ["pkg"],
mode: "transform",
});
itBundled("ts/TSImportEqualsBundle", {
itBundled("ts/ImportEqualsBundle", {
notImplemented: true,
files: {
"/entry.ts": /* ts */ `
import { foo } from 'pkg'
@@ -749,9 +787,10 @@ describe("bundler", () => {
`,
},
dce: true,
treeShaking: true,
external: ["pkg"],
});
itBundled("ts/TSMinifiedBundleES6", {
itBundled("ts/MinifiedBundleES6", {
files: {
"/entry.ts": /* ts */ `
import {foo} from './a'
@@ -770,7 +809,7 @@ describe("bundler", () => {
stdout: "123",
},
});
itBundled("ts/TSMinifiedBundleCommonJS", {
itBundled("ts/MinifiedBundleCommonJS", {
files: {
"/entry.ts": /* ts */ `
const {foo} = require('./a')
@@ -1004,7 +1043,7 @@ describe("bundler", () => {
]);
},
});
itBundled("ts/TSExportDefaultTypeESBuildIssue316", {
itBundled("ts/ExportDefaultTypeESBuildIssue316", {
files: {
"/entry.ts": /* ts */ `
import dc_def, { bar as dc } from './keep/declare-class'
@@ -1139,7 +1178,7 @@ describe("bundler", () => {
`,
},
});
itBundled("ts/TSImplicitExtensions", {
itBundled("ts/ImplicitExtensions", {
files: {
"/entry.ts": /* ts */ `
import './pick-js.js'
@@ -1166,7 +1205,7 @@ describe("bundler", () => {
stdout: "correct\n".repeat(6),
},
});
itBundled("ts/TSImplicitExtensionsMissing", {
itBundled("ts/ImplicitExtensionsMissing", {
files: {
"/entry.ts": /* ts */ `
import './mjs.mjs'
@@ -1499,7 +1538,7 @@ describe("bundler", () => {
file: "/test.js",
},
});
itBundled("ts/TSComputedClassFieldUseDefineFalse", {
itBundled("ts/ComputedClassFieldUseDefineFalse", {
files: {
"/entry.ts": /* ts */ `
class Foo {
@@ -1546,7 +1585,7 @@ describe("bundler", () => {
file: "/test.js",
},
});
itBundled("ts/TSComputedClassFieldUseDefineTrue", {
itBundled("ts/ComputedClassFieldUseDefineTrue", {
files: {
"/entry.ts": /* ts */ `
class Foo {
@@ -1593,7 +1632,7 @@ describe("bundler", () => {
file: "/test.js",
},
});
itBundled("ts/TSComputedClassFieldUseDefineTrueLower", {
itBundled("ts/ComputedClassFieldUseDefineTrueLower", {
files: {
"/entry.ts": /* ts */ `
class Foo {
@@ -1641,7 +1680,7 @@ describe("bundler", () => {
},
unsupportedJSFeatures: ["class-field"],
});
itBundled("ts/TSAbstractClassFieldUseAssign", {
itBundled("ts/AbstractClassFieldUseAssign", {
files: {
"/entry.ts": /* ts */ `
const keepThis = Symbol('keepThis')
@@ -1659,7 +1698,7 @@ describe("bundler", () => {
dce: true,
useDefineForClassFields: false,
});
itBundled("ts/TSAbstractClassFieldUseDefine", {
itBundled("ts/AbstractClassFieldUseDefine", {
files: {
"/entry.ts": /* ts */ `
const keepThisToo = Symbol('keepThisToo')
@@ -1677,7 +1716,7 @@ describe("bundler", () => {
mode: "transform",
useDefineForClassFields: true,
});
itBundled("ts/TSImportMTS", {
itBundled("ts/ImportMTS", {
files: {
"/entry.ts": `import './imported.mjs'`,
"/imported.mts": `console.log('works')`,
@@ -1686,7 +1725,7 @@ describe("bundler", () => {
stdout: "works",
},
});
itBundled("ts/TSImportCTS", {
itBundled("ts/ImportCTS", {
files: {
"/entry.ts": `require('./required.cjs')`,
"/required.cjs": `console.log('works')`,
@@ -1695,7 +1734,7 @@ describe("bundler", () => {
stdout: "works",
},
});
itBundled("ts/TSSideEffectsFalseWarningTypeDeclarations", {
itBundled("ts/SideEffectsFalseWarningTypeDeclarations", {
files: {
"/entry.ts": /* ts */ `
import "some-js"
@@ -1719,7 +1758,7 @@ describe("bundler", () => {
expect(api.readFile("/out.js").trim()).toBe("");
},
});
itBundled("ts/TSSiblingNamespace", {
itBundled("ts/SiblingNamespace", {
files: {
"/let.ts": /* ts */ `
export namespace x { export let y = 123 }
@@ -1760,8 +1799,7 @@ describe("bundler", () => {
`,
},
});
if (!RUN_UNCHECKED_TESTS) return;
itBundled("ts/TSSiblingEnum", {
itBundled("ts/SiblingEnum", {
// GENERATED
files: {
"/number.ts": /* ts */ `
@@ -1837,7 +1875,7 @@ describe("bundler", () => {
],
mode: "passthrough",
});
itBundled("ts/TSEnumTreeShaking", {
itBundled("ts/EnumTreeShaking", {
// GENERATED
files: {
"/simple-member.ts": /* ts */ `
@@ -1888,7 +1926,7 @@ describe("bundler", () => {
"/namespace-after.ts",
],
});
itBundled("ts/TSEnumJSX", {
itBundled("ts/EnumJSX", {
// GENERATED
files: {
"/element.tsx": /* tsx */ `
@@ -1911,14 +1949,14 @@ describe("bundler", () => {
entryPoints: ["/element.tsx", "/fragment.tsx", "/nested-element.tsx", "/nested-fragment.tsx"],
mode: "passthrough",
});
itBundled("ts/TSEnumDefine", {
itBundled("ts/EnumDefine", {
// GENERATED
files: {
"/entry.ts": `enum a { b = 123, c = d }`,
},
mode: "passthrough",
});
itBundled("ts/TSEnumSameModuleInliningAccess", {
itBundled("ts/EnumSameModuleInliningAccess", {
// GENERATED
files: {
"/entry.ts": /* ts */ `
@@ -1937,7 +1975,7 @@ describe("bundler", () => {
`,
},
});
itBundled("ts/TSEnumCrossModuleInliningAccess", {
itBundled("ts/EnumCrossModuleInliningAccess", {
// GENERATED
files: {
"/entry.ts": /* ts */ `
@@ -1959,7 +1997,7 @@ describe("bundler", () => {
`,
},
});
itBundled("ts/TSEnumCrossModuleInliningDefinitions", {
itBundled("ts/EnumCrossModuleInliningDefinitions", {
// GENERATED
files: {
"/entry.ts": /* ts */ `
@@ -1981,7 +2019,7 @@ describe("bundler", () => {
`,
},
});
itBundled("ts/TSEnumCrossModuleInliningReExport", {
itBundled("ts/EnumCrossModuleInliningReExport", {
// GENERATED
files: {
"/entry.js": /* js */ `
@@ -2003,7 +2041,7 @@ describe("bundler", () => {
`,
},
});
itBundled("ts/TSEnumCrossModuleTreeShaking", {
itBundled("ts/EnumCrossModuleTreeShaking", {
// GENERATED
files: {
"/entry.ts": /* ts */ `
@@ -2048,7 +2086,7 @@ describe("bundler", () => {
`,
},
});
itBundled("ts/TSEnumExportClause", {
itBundled("ts/EnumExportClause", {
// GENERATED
files: {
"/entry.ts": /* ts */ `
@@ -2075,7 +2113,7 @@ describe("bundler", () => {
`,
},
});
itBundled("ts/TSThisIsUndefinedWarning", {
itBundled("ts/ThisIsUndefinedWarning", {
// GENERATED
files: {
"/warning1.ts": `export var foo = this`,
@@ -2093,7 +2131,7 @@ describe("bundler", () => {
warning3.ts: NOTE: This file is considered to be an ECMAScript module because of the "export" keyword here:
`, */
});
itBundled("ts/TSCommonJSVariableInESMTypeModule", {
itBundled("ts/CommonJSVariableInESMTypeModule", {
// GENERATED
files: {
"/entry.ts": `module.exports = null`,
@@ -2182,7 +2220,7 @@ describe("bundler", () => {
},
entryPoints: ["/supported.ts", "/not-supported.ts"],
});
itBundled("ts/TSEnumUseBeforeDeclare", {
itBundled("ts/EnumUseBeforeDeclare", {
// GENERATED
files: {
"/entry.ts": /* ts */ `

View File

@@ -30,9 +30,11 @@ const ESBUILD = process.env.BUN_BUNDLER_TEST_USE_ESBUILD;
const DEBUG = process.env.BUN_BUNDLER_TEST_DEBUG;
/** Set this to the id of a bundle test to run just that test */
const FILTER = process.env.BUN_BUNDLER_TEST_FILTER;
/** Set this to hide skips */
const HIDE_SKIP = process.env.BUN_BUNDLER_TEST_HIDE_SKIP;
/** Path to the bun. TODO: Once bundler is merged, we should remove the `bun-debug` fallback. */
const BUN_EXE = (process.env.BUN_EXE && Bun.which(process.env.BUN_EXE)) ?? Bun.which("bun-debug") ?? bunExe();
export const RUN_UNCHECKED_TESTS = true;
export const RUN_UNCHECKED_TESTS = false;
const outBaseTemplate = path.join(tmpdir(), "bun-build-tests", `${ESBUILD ? "esbuild" : "bun"}-`);
if (!existsSync(path.dirname(outBaseTemplate))) mkdirSync(path.dirname(outBaseTemplate), { recursive: true });
@@ -73,6 +75,8 @@ export interface BundlerTestInput {
define?: Record<string, string | number>;
/** Default is "[name].[ext]" */
entryNames?: string;
/** Default is "[name]-[hash].[ext]" */
chunkNames?: string;
extensionOrder?: string[];
/** Replaces "{{root}}" with the file root */
external?: string[];
@@ -114,6 +118,10 @@ export interface BundlerTestInput {
useDefineForClassFields?: boolean;
sourceMap?: boolean | "inline" | "external";
// pass subprocess.env
env?: Record<string, any>;
nodePaths?: string[];
// assertion options
/**
@@ -137,6 +145,13 @@ export interface BundlerTestInput {
* Checks source code for REMOVE, FAIL, DROP, which will fail the test.
*/
dce?: boolean;
/**
* Shorthand for testing CJS->ESM cases.
* Checks source code for the commonjs helper.
*
* Set to true means all cjs files should be converted. You can pass `exclude` to expect them to stay commonjs.
*/
cjs2esm?: boolean | { exclude: string[] };
/**
* Override the number of keep markers, which is auto detected by default.
* Does nothing if dce is false.
@@ -233,12 +248,14 @@ export function expectBundled(
bundleErrors,
bundleWarnings,
capture,
chunkNames,
dce,
dceKeepMarkerCount,
define,
entryNames,
entryPoints,
entryPointsRaw,
env,
external,
files,
format,
@@ -261,6 +278,7 @@ export function expectBundled(
outfile,
outputPaths,
platform,
publicPath,
run,
runtimeFiles,
skipOnEsbuild,
@@ -273,10 +291,6 @@ export function expectBundled(
...unknownProps
} = opts;
if (!ESBUILD && platform === "neutral") {
platform = "browser";
}
// TODO: Remove this check once all options have been implemented
if (Object.keys(unknownProps).length > 0) {
throw new Error("expectBundled recieved unexpected options: " + Object.keys(unknownProps).join(", "));
@@ -337,6 +351,12 @@ export function expectBundled(
if (!ESBUILD && inject) {
throw new Error("inject not implemented in bun build");
}
if (!ESBUILD && publicPath) {
throw new Error("publicPath not implemented in bun build");
}
if (!ESBUILD && chunkNames) {
throw new Error("chunkNames is not implemented in bun build");
}
if (ESBUILD && skipOnEsbuild) {
return testRef(id, opts);
}
@@ -362,8 +382,13 @@ export function expectBundled(
: entryPaths.map(file => path.join(outdir!, path.basename(file)))
).map(x => x.replace(/\.ts$/, ".js"));
if (mode === "transform" && !outfile) {
throw new Error("transform mode requires one single outfile");
}
if (outdir) {
entryNames ??= "[name].[ext]";
chunkNames ??= "[name]-[hash].[ext]";
}
// Option validation
@@ -410,7 +435,7 @@ export function expectBundled(
"build",
...entryPaths,
...(entryPointsRaw ?? []),
outfile ? `--outfile=${outfile}` : `--outdir=${outdir}`,
mode === "bundle" ? [outfile ? `--outfile=${outfile}` : `--outdir=${outdir}`] : [],
define && Object.entries(define).map(([k, v]) => ["--define", `${k}=${v}`]),
`--platform=${platform}`,
external && external.map(x => ["--external", x]),
@@ -426,6 +451,7 @@ export function expectBundled(
// metafile && `--metafile=${metafile}`,
// sourceMap && `--sourcemap${sourceMap !== true ? `=${sourceMap}` : ""}`,
entryNames && entryNames !== "[name].[ext]" && [`--entry-names`, entryNames],
// chunkNames && chunkNames !== "[name]-[hash].[ext]" && [`--chunk-names`, chunkNames],
// `--format=${format}`,
// legalComments && `--legal-comments=${legalComments}`,
splitting && `--splitting`,
@@ -434,6 +460,7 @@ export function expectBundled(
// keepNames && `--keep-names`,
// mainFields && `--main-fields=${mainFields}`,
// loader && Object.entries(loader).map(([k, v]) => ["--loader", `${k}=${v}`]),
// publicPath && `--public-path=${publicPath}`,
mode === "transform" && "--transform",
]
: [
@@ -454,6 +481,7 @@ export function expectBundled(
jsx.fragment && `--jsx-fragment=${jsx.fragment}`,
jsx.development && `--jsx-dev`,
entryNames && entryNames !== "[name].[ext]" && `--entry-names=${entryNames.replace(/\.\[ext]$/, "")}`,
chunkNames && chunkNames !== "[name]-[hash].[ext]" && `--chunk-names=${chunkNames.replace(/\.\[ext]$/, "")}`,
metafile && `--metafile=${metafile}`,
sourceMap && `--sourcemap${sourceMap !== true ? `=${sourceMap}` : ""}`,
banner && `--banner:js=${banner}`,
@@ -464,6 +492,7 @@ export function expectBundled(
keepNames && `--keep-names`,
mainFields && `--main-fields=${mainFields.join(",")}`,
loader && Object.entries(loader).map(([k, v]) => `--loader:${k}=${v}`),
publicPath && `--public-path=${publicPath}`,
[...(unsupportedJSFeatures ?? []), ...(unsupportedCSSFeatures ?? [])].map(x => `--supported:${x}=false`),
...entryPaths,
...(entryPointsRaw ?? []),
@@ -508,11 +537,19 @@ export function expectBundled(
);
}
const bundlerEnv = { ...bunEnv, ...env };
// remove undefined keys instead of passing "undefined"
for (const key in bundlerEnv) {
if (bundlerEnv[key] === undefined) {
delete bundlerEnv[key];
}
}
const { stdout, stderr, success } = Bun.spawnSync({
cmd,
cwd: root,
stdio: ["ignore", "pipe", "pipe"],
env: bunEnv,
env: bundlerEnv,
});
// Check for errors
@@ -607,6 +644,11 @@ export function expectBundled(
throw new Error("Errors were expected while bundling:\n" + expectedErrors.map(formatError).join("\n"));
}
if (mode === "transform" && !ESBUILD) {
mkdirSync(path.dirname(outfile!), { recursive: true });
Bun.write(outfile!, stdout);
}
// Check for warnings
let warningReference: Record<string, { file: string; error: string; line?: string; col?: string }[]> = {};
if (!ESBUILD) {
@@ -967,7 +1009,7 @@ export function itBundled(id: string, opts: BundlerTestInput): BundlerTestRef {
try {
expectBundled(id, opts, true);
} catch (error) {
it.skip(id, () => {});
if (!HIDE_SKIP) it.skip(id, () => {});
return ref;
}
}
@@ -981,7 +1023,7 @@ export function itBundled(id: string, opts: BundlerTestInput): BundlerTestRef {
);
});
} catch (error: any) {
it.skip(id, () => {});
if (!HIDE_SKIP) it.skip(id, () => {});
}
} else {
it(id, () => expectBundled(id, opts));
@@ -989,8 +1031,11 @@ export function itBundled(id: string, opts: BundlerTestInput): BundlerTestRef {
return ref;
}
itBundled.skip = (id: string, opts: BundlerTestInput) => {
if (FILTER && id !== FILTER) {
return testRef(id, opts);
}
const { it } = testForFile(callerSourceOrigin());
it.skip(id, () => expectBundled(id, opts));
if (!HIDE_SKIP) it.skip(id, () => expectBundled(id, opts));
return testRef(id, opts);
};
@@ -1003,8 +1048,11 @@ export function bundlerTest(id: string, cb: () => void) {
it(id, cb);
}
bundlerTest.skip = (id: string, cb: any) => {
if (FILTER && id !== FILTER) {
return;
}
const { it } = testForFile(callerSourceOrigin());
it.skip(id, cb);
if (!HIDE_SKIP) it.skip(id, cb);
};
function formatError(err: { file: string; error: string; line?: string; col?: string }) {