534 lines
33 KiB
TypeScript
534 lines
33 KiB
TypeScript
import assert from "assert";
|
|||
|
|
import { describe, expect } from "bun:test";
|
||
|
|
import { itBundled } from "./expectBundled";
|
||
|
|
|
||
|
|
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/NodeBuffer#21522", {
|
||
|
|
files: {
|
||
|
|
"/entry.js": /* js */ `
|
||
|
|
import { Buffer } from "node:buffer";
|
||
|
|
const x = Buffer.alloc(5);
|
||
|
|
x.write("68656c6c6f", "hex");
|
||
|
|
console.log(x);
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "browser",
|
||
|
|
run: {
|
||
|
|
stdout: "<Buffer 68 65 6c 6c 6f>",
|
||
|
|
},
|
||
|
|
onAfterBundle(api) {
|
||
|
|
api.expectFile("out.js").not.toInclude("import ");
|
||
|
|
},
|
||
|
|
});
|
||
|
|
itBundled("browser/NodeBuffer#12272", {
|
||
|
|
files: {
|
||
|
|
"/entry.js": /* js */ `
|
||
|
|
import * as buffer from "node:buffer";
|
||
|
|
import { Buffer } from "buffer";
|
||
|
|
import Buffer2 from "buffer";
|
||
|
|
import { Blob, File } from "buffer";
|
||
|
|
if (Buffer !== Buffer2) throw new Error("Buffer is not the same");
|
||
|
|
if (Blob !== globalThis.Blob) throw new Error("Blob is not the same");
|
||
|
|
if (File !== globalThis.File) throw new Error("File is not the same");
|
||
|
|
if (Buffer.from("foo").toString("hex") !== "666f6f") throw new Error("Buffer.from is broken");
|
||
|
|
if (buffer.isAscii("foo") !== true) throw new Error("Buffer.isAscii is broken");
|
||
|
|
if (Buffer2.alloc(10, 'b').toString("hex") !== "62626262626262626262") throw new Error("Buffer.alloc is broken");
|
||
|
|
console.log("Success!");
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "browser",
|
||
|
|
run: {
|
||
|
|
stdout: "Success!",
|
||
|
|
},
|
||
|
|
onAfterBundle(api) {
|
||
|
|
api.expectFile("out.js").not.toInclude("import ");
|
||
|
|
},
|
||
|
|
});
|
||
|
|
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);
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "browser",
|
||
|
|
run: {
|
||
|
|
stdout: "function\nfunction\nundefined",
|
||
|
|
},
|
||
|
|
onAfterBundle(api) {
|
||
|
|
api.expectFile("out.js").not.toInclude("import ");
|
||
|
|
},
|
||
|
|
});
|
||
|
|
itBundled("browser/NodeTTY", {
|
||
|
|
files: {
|
||
|
|
"/entry.js": /* js */ `
|
||
|
|
import { isatty, ReadStream, WriteStream } from "node:tty";
|
||
|
|
console.log(typeof ReadStream);
|
||
|
|
console.log(typeof WriteStream);
|
||
|
|
console.log(isatty(0));
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "browser",
|
||
|
|
run: {
|
||
|
|
stdout: "function\nfunction\nfalse",
|
||
|
|
},
|
||
|
|
onAfterBundle(api) {
|
||
|
|
api.expectFile("out.js").not.toInclude("import ");
|
||
|
|
},
|
||
|
|
});
|
||
|
|
itBundled("browser/NodeUrlProtocolTablesIgnorePrototype", {
|
||
|
|
files: {
|
||
|
|
"/entry.js": /* js */ `
|
||
|
|
import { parse } from "node:url";
|
||
|
|
const clean = parse("evil://h/p").slashes;
|
||
|
|
Object.prototype["evil:"] = true;
|
||
|
|
const polluted = parse("evil://h/p").slashes;
|
||
|
|
delete Object.prototype["evil:"];
|
||
|
|
console.log(clean === true && polluted === true ? "PASS" : "FAIL " + clean + " " + polluted);
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "browser",
|
||
|
|
run: {
|
||
|
|
stdout: "PASS",
|
||
|
|
},
|
||
|
|
});
|
||
|
|
// 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";
|
||
|
|
const modules = {
|
||
|
|
assert,
|
||
|
|
buffer,
|
||
|
|
child_process,
|
||
|
|
cluster,
|
||
|
|
console2,
|
||
|
|
constants,
|
||
|
|
crypto,
|
||
|
|
dgram,
|
||
|
|
dns,
|
||
|
|
domain,
|
||
|
|
events,
|
||
|
|
fs,
|
||
|
|
http,
|
||
|
|
https,
|
||
|
|
module: module2,
|
||
|
|
net,
|
||
|
|
os,
|
||
|
|
path,
|
||
|
|
perf_hooks,
|
||
|
|
process,
|
||
|
|
punycode,
|
||
|
|
querystring,
|
||
|
|
readline,
|
||
|
|
repl,
|
||
|
|
stream,
|
||
|
|
string_decoder,
|
||
|
|
sys,
|
||
|
|
timers,
|
||
|
|
tls,
|
||
|
|
tty,
|
||
|
|
url,
|
||
|
|
util,
|
||
|
|
v8,
|
||
|
|
vm,
|
||
|
|
zlib,
|
||
|
|
}
|
||
|
|
console.log(Bun.inspect(modules))
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "browser",
|
||
|
|
run: {
|
||
|
|
stdout:
|
||
|
|
"{\n assert: {\n AssertionError: [Getter/Setter],\n CallTracker: [Getter/Setter],\n deepEqual: [Getter/Setter],\n deepStrictEqual: [Getter/Setter],\n default: [Getter/Setter],\n doesNotMatch: [Getter/Setter],\n doesNotReject: [Getter/Setter],\n doesNotThrow: [Getter/Setter],\n equal: [Getter/Setter],\n fail: [Getter/Setter],\n ifError: [Getter/Setter],\n match: [Getter/Setter],\n notDeepEqual: [Getter/Setter],\n notDeepStrictEqual: [Getter/Setter],\n notEqual: [Getter/Setter],\n notStrictEqual: [Getter/Setter],\n ok: [Getter/Setter],\n rejects: [Getter/Setter],\n strict: [Getter/Setter],\n strictEqual: [Getter/Setter],\n throws: [Getter/Setter],\n },\n buffer: {\n Blob: [Getter/Setter],\n Buffer: [Getter/Setter],\n File: [Getter/Setter],\n INSPECT_MAX_BYTES: [Getter/Setter],\n atob: [Getter/Setter],\n btoa: [Getter/Setter],\n constants: [Getter/Setter],\n default: [Getter/Setter],\n isAscii: [Getter/Setter],\n isUtf8: [Getter/Setter],\n kMaxLength: [Getter/Setter],\n kStringMaxLength: [Getter/Setter],\n resolveObjectURL: [Getter/Setter],\n transcode: [Getter/Setter],\n },\n child_process: [Function: child_process],\n cluster: [Function: cluster],\n console2: {\n default: [Getter/Setter],\n },\n constants: {\n DH_CHECK_P_NOT_PRIME: [Getter/Setter],\n DH_CHECK_P_NOT_SAFE_PRIME: [Getter/Setter],\n DH_NOT_SUITABLE_GENERATOR: [Getter/Setter],\n DH_UNABLE_TO_CHECK_GENERATOR: [Getter/Setter],\n E2BIG: [Getter/Setter],\n EACCES: [Getter/Setter],\n EADDRINUSE: [Getter/Setter],\n EADDRNOTAVAIL: [Getter/Setter],\n EAFNOSUPPORT: [Getter/Setter],\n EAGAIN: [Getter/Setter],\n EALREADY: [Getter/Setter],\n EBADF: [Getter/Setter],\n EBADMSG: [Getter/Setter],\n EBUSY: [Getter/Setter],\n ECANCELED: [Getter/Setter],\n ECHILD: [Getter/Setter],\n ECONNABORTED: [Getter/Setter],\n ECONNREFUSED: [Getter/Setter],\n ECONNRESET: [Getter/Setter],\n EDEADLK: [Getter/Setter],\n EDESTADDRREQ: [Getter/Setter],\n EDOM: [Getter/Setter],\n EDQUOT: [Getter/Setter],\n EEXIST: [Getter/Setter],\n EFAULT: [Getter/Setter],\n EFBIG: [Getter/Setter],\n EHOSTUNREACH: [Getter/Setter],\n EIDRM: [Getter/Setter],\n EILSEQ: [Getter/Setter],\n EINPROGRESS: [Getter/Setter],\n EINTR: [Getter/Setter],\n EINVAL: [Getter/Setter],\n EIO: [Getter/Setter],\n EISCONN: [Getter/Setter],\n EISDIR: [Getter/Setter],\n ELOOP: [Getter/Setter],\n EMFILE: [Getter/Setter],\n EMLINK: [Getter/Setter],\n EMSGSIZE: [Getter/Setter],\n EMULTIHOP: [Getter/Setter],\n ENAMETOOLONG: [Getter/Setter],\n ENETDOWN: [Getter/Setter],\n ENETRESET: [Getter/Setter],\n ENETUNREACH: [Getter/Setter],\n ENFILE: [Getter/Setter],\n ENGINE_METHOD_ALL: [Getter/Setter],\n ENGINE_METHOD_CIPHERS: [Getter/Setter],\n ENGINE_METHOD_DH: [Getter/Setter],\n ENGINE_METHOD_DIGESTS: [Getter/Setter],\n ENGINE_METHOD_DSA: [Getter/Setter],\n ENGINE_METHOD_ECDH: [Getter/Setter],\n ENGINE_METHOD_ECDSA: [Getter/Setter],\n ENGINE_METHOD_NONE: [Getter/Setter],\n ENGINE_METHOD_PKEY_ASN1_METHS: [Getter/Setter],\n ENGINE_METHOD_PKEY_METHS: [Getter/Setter],\n ENGINE_METHOD_RAND: [Getter/Setter],\n ENGINE_METHOD_STORE: [Getter/Setter],\n ENOBUFS: [Getter/Setter],\n ENODATA: [Getter/Setter],\n ENODEV: [Getter/Setter],\n ENOENT: [Getter/Setter],\n ENOEXEC: [Getter/Setter],\n ENOLCK: [Getter/Setter],\n ENOLINK: [Getter/Setter],\n ENOMEM: [Getter/Setter],\n ENOMSG: [Getter/Setter],\n ENOPROTOOPT: [Getter/Setter],\n ENOSPC: [Getter/Setter],\n ENOSR: [Getter/Setter],\n ENOSTR: [Getter/Setter],\n ENOSYS: [Getter/Setter],\n ENOTCONN: [Getter/Setter],\n ENOTDIR: [Getter/Setter],\n ENOTEMPTY: [Getter/Setter],\n ENOTSOCK: [Getter/Setter],\n ENOTSUP: [Getter/Setter],\n ENOTTY: [Getter/Setter],\n ENXIO: [Getter/Setter],\n EOPNOTSUPP: [Getter/Setter],\n EOVERFLOW: [Getter/Setter],\n EPERM: [G
|
||
|
|
|
||
|
|
validate(ctx) {},
|
||
|
|
},
|
||
|
|
});
|
||
|
|
itBundled("browser/NodePolyfillExternal", {
|
||
|
|
todo: true,
|
||
|
|
skipOnEsbuild: true,
|
||
|
|
files: {
|
||
|
|
"/entry.js": NodePolyfills.options.files["/entry.js"],
|
||
|
|
},
|
||
|
|
target: "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,
|
||
|
|
})),
|
||
|
|
);
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
// #4928: a package.json "browser": {"<builtin>": false} must win over the
|
||
|
|
// builtin polyfill for --target browser. Packages set this to keep their
|
||
|
|
// node-only code paths from dragging crypto/stream/buffer into browser bundles.
|
||
|
|
itBundled("browser/BrowserFieldDisablesPolyfilledBuiltin#4928", {
|
||
|
|
files: {
|
||
|
|
"/entry.js": /* js */ `
|
||
|
|
import pkg from "pkg";
|
||
|
|
console.log(JSON.stringify(pkg));
|
||
|
|
`,
|
||
|
|
"/node_modules/pkg/package.json": /* json */ `
|
||
|
|
{
|
||
|
|
"name": "pkg",
|
||
|
|
"main": "./index.js",
|
||
|
|
"browser": {
|
||
|
|
"crypto": false,
|
||
|
|
"stream": false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
`,
|
||
|
|
"/node_modules/pkg/index.js": /* js */ `
|
||
|
|
const nodeCrypto = require("crypto");
|
||
|
|
const nodeStream = require("stream");
|
||
|
|
module.exports = {
|
||
|
|
hasRandomBytes: typeof nodeCrypto.randomBytes,
|
||
|
|
hasReadable: typeof nodeStream.Readable,
|
||
|
|
};
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "browser",
|
||
|
|
run: {
|
||
|
|
stdout: '{"hasRandomBytes":"undefined","hasReadable":"undefined"}',
|
||
|
|
},
|
||
|
|
onAfterBundle(api) {
|
||
|
|
const out = api.readFile("/out.js");
|
||
|
|
// The crypto polyfill pulls in createHash/Transform; neither should appear
|
||
|
|
// when the browser map disables the builtin.
|
||
|
|
assert(!out.includes("createHash"), "crypto polyfill should not be bundled when browser:{crypto:false}");
|
||
|
|
assert(!out.includes("Transform"), "stream polyfill should not be bundled when browser:{stream:false}");
|
||
|
|
assert(out.length < 10000, `output should be a small stub, got ${out.length} bytes`);
|
||
|
|
},
|
||
|
|
});
|
||
|
|
itBundled("browser/BrowserFieldDisablesPolyfilledBuiltinNodePrefix#4928", {
|
||
|
|
// Same as above but with the node: prefix on the import. The polyfill table
|
||
|
|
// strips node: before lookup, so the browser-map check must too.
|
||
|
|
files: {
|
||
|
|
"/entry.js": /* js */ `
|
||
|
|
import pkg from "pkg";
|
||
|
|
console.log(pkg);
|
||
|
|
`,
|
||
|
|
"/node_modules/pkg/package.json": /* json */ `
|
||
|
|
{
|
||
|
|
"name": "pkg",
|
||
|
|
"main": "./index.js",
|
||
|
|
"browser": {
|
||
|
|
"crypto": false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
`,
|
||
|
|
"/node_modules/pkg/index.js": /* js */ `
|
||
|
|
const nodeCrypto = require("node:crypto");
|
||
|
|
module.exports = typeof nodeCrypto.randomBytes;
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "browser",
|
||
|
|
run: {
|
||
|
|
stdout: "undefined",
|
||
|
|
},
|
||
|
|
onAfterBundle(api) {
|
||
|
|
const out = api.readFile("/out.js");
|
||
|
|
assert(!out.includes("createHash"), "crypto polyfill should not be bundled when browser:{crypto:false}");
|
||
|
|
},
|
||
|
|
});
|
||
|
|
itBundled("browser/BrowserFieldRemapsPolyfilledBuiltin#4928", {
|
||
|
|
files: {
|
||
|
|
"/entry.js": /* js */ `
|
||
|
|
import pkg from "pkg";
|
||
|
|
console.log(pkg.id);
|
||
|
|
`,
|
||
|
|
"/node_modules/pkg/package.json": /* json */ `
|
||
|
|
{
|
||
|
|
"name": "pkg",
|
||
|
|
"main": "./index.js",
|
||
|
|
"browser": {
|
||
|
|
"crypto": "./crypto-shim.js"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
`,
|
||
|
|
"/node_modules/pkg/crypto-shim.js": /* js */ `
|
||
|
|
module.exports = { id: "shimmed-crypto" };
|
||
|
|
`,
|
||
|
|
"/node_modules/pkg/index.js": /* js */ `
|
||
|
|
module.exports = require("crypto");
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "browser",
|
||
|
|
run: {
|
||
|
|
stdout: "shimmed-crypto",
|
||
|
|
},
|
||
|
|
onAfterBundle(api) {
|
||
|
|
const out = api.readFile("/out.js");
|
||
|
|
assert(out.includes("shimmed-crypto"), "browser shim should be bundled");
|
||
|
|
assert(!out.includes("createHash"), "crypto polyfill should not be bundled when browser map remaps it");
|
||
|
|
},
|
||
|
|
});
|
||
|
|
itBundled("browser/BrowserFieldDisabledBuiltinStillPolyfillsOutsideScope", {
|
||
|
|
// A browser:{events:false} in one package must not leak into a sibling package.
|
||
|
|
files: {
|
||
|
|
"/entry.js": /* js */ `
|
||
|
|
import disabled from "disabled-pkg";
|
||
|
|
import live from "live-pkg";
|
||
|
|
console.log(disabled, live);
|
||
|
|
`,
|
||
|
|
"/node_modules/disabled-pkg/package.json": /* json */ `
|
||
|
|
{ "name": "disabled-pkg", "main": "./index.js", "browser": { "events": false } }
|
||
|
|
`,
|
||
|
|
"/node_modules/disabled-pkg/index.js": /* js */ `
|
||
|
|
const ev = require("events");
|
||
|
|
module.exports = typeof ev.EventEmitter;
|
||
|
|
`,
|
||
|
|
"/node_modules/live-pkg/package.json": /* json */ `
|
||
|
|
{ "name": "live-pkg", "main": "./index.js" }
|
||
|
|
`,
|
||
|
|
"/node_modules/live-pkg/index.js": /* js */ `
|
||
|
|
const ev = require("events");
|
||
|
|
module.exports = typeof ev.EventEmitter;
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "browser",
|
||
|
|
run: {
|
||
|
|
stdout: "undefined function",
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
// 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]);
|
||
|
|
|
||
|
|
// all of them are set to error so this test doesnt make sense to run
|
||
|
|
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")}
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "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")}
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "browser",
|
||
|
|
bundleErrors: {
|
||
|
|
"/entry.js": Object.keys(bunModules)
|
||
|
|
.filter(x => bunModules[x] === "error")
|
||
|
|
.map(x => `Browser build cannot import Bun builtin: "${x}". When bundling for Bun, set target to 'bun'`),
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
// not implemented right now
|
||
|
|
itBundled("browser/BunPolyfillExternal", {
|
||
|
|
skipOnEsbuild: true,
|
||
|
|
files: ImportBunError.options.files,
|
||
|
|
target: "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,
|
||
|
|
})),
|
||
|
|
);
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
itBundled("browser/ImportNonExistentNodeBuiltinShouldError", {
|
||
|
|
skipOnEsbuild: true,
|
||
|
|
files: {
|
||
|
|
"/entry.js": `
|
||
|
|
import net1 from "node:net1";
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
bundleErrors: {
|
||
|
|
"/entry.js": [`Could not resolve: "node:net1". Maybe you need to "bun install"?`],
|
||
|
|
},
|
||
|
|
});
|
||
|
|
itBundled("browser/ImportNonExistentWithoutNodePrefix", {
|
||
|
|
skipOnEsbuild: true,
|
||
|
|
files: {
|
||
|
|
"/entry.js": `
|
||
|
|
import net1 from "net1";
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
bundleErrors: {
|
||
|
|
"/entry.js": [`Could not resolve: "net1". Maybe you need to "bun install"?`],
|
||
|
|
},
|
||
|
|
});
|
||
|
|
itBundled("browser/TargetNodeNonExistentBuiltinShouldBeExternal", {
|
||
|
|
files: {
|
||
|
|
"/entry.js": `
|
||
|
|
import net1 from "node:net1";
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "node",
|
||
|
|
onAfterBundle(api) {
|
||
|
|
const contents = api.readFile("out.js");
|
||
|
|
expect(contents).toBe("");
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
itBundled("browser/AwaitUsingStatement", {
|
||
|
|
files: {
|
||
|
|
"/entry.js": `
|
||
|
|
async function test() {
|
||
|
|
await using resource = {
|
||
|
|
async [Symbol.asyncDispose]() {
|
||
|
|
console.log("The function was called");
|
||
|
|
await 42;
|
||
|
|
console.log("and the await finished");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
console.log("Before!");
|
||
|
|
}
|
||
|
|
test();
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "browser",
|
||
|
|
run: {
|
||
|
|
stdout: "Before!\nThe function was called\nand the await finished\n",
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
itBundled("browser/UsingStatement", {
|
||
|
|
files: {
|
||
|
|
"/entry.js": `
|
||
|
|
function test() {
|
||
|
|
using resource = {
|
||
|
|
[Symbol.dispose]() {
|
||
|
|
console.log("The dispose function was called");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
console.log("Before!");
|
||
|
|
}
|
||
|
|
test();
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
target: "browser",
|
||
|
|
run: {
|
||
|
|
stdout: "Before!\nThe dispose function was called\n",
|
||
|
|
},
|
||
|
|
});
|
||
|
|
});
|