diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/bundler/bundler_browser.test.ts | 18 | ||||
-rw-r--r-- | test/bundler/bundler_edgecase.test.ts | 162 | ||||
-rw-r--r-- | test/bundler/bundler_jsx.test.ts | 322 | ||||
-rw-r--r-- | test/bundler/bundler_minify.test.ts | 15 | ||||
-rw-r--r-- | test/bundler/bundler_plugin.test.ts | 146 | ||||
-rw-r--r-- | test/bundler/esbuild/css.test.ts | 2 | ||||
-rw-r--r-- | test/bundler/esbuild/default.test.ts | 227 | ||||
-rw-r--r-- | test/bundler/esbuild/importstar_ts.test.ts | 2 | ||||
-rw-r--r-- | test/bundler/esbuild/loader.test.ts | 347 | ||||
-rw-r--r-- | test/bundler/esbuild/packagejson.test.ts | 93 | ||||
-rw-r--r-- | test/bundler/esbuild/splitting.test.ts | 4 | ||||
-rw-r--r-- | test/bundler/expectBundled.ts | 174 | ||||
-rw-r--r-- | test/bundler/transpiler.test.js | 8 |
13 files changed, 1059 insertions, 461 deletions
diff --git a/test/bundler/bundler_browser.test.ts b/test/bundler/bundler_browser.test.ts index 422e860b5..be2e5a43e 100644 --- a/test/bundler/bundler_browser.test.ts +++ b/test/bundler/bundler_browser.test.ts @@ -52,7 +52,7 @@ describe("bundler", () => { console.log(typeof readFileSync); `, }, - platform: "browser", + target: "browser", run: { stdout: "function\nfunction\nundefined", }, @@ -137,7 +137,7 @@ describe("bundler", () => { console.log('zlib :', scan(zlib)) `, }, - platform: "browser", + target: "browser", onAfterBundle(api) { assert(!api.readFile("/out.js").includes("\0"), "bundle should not contain null bytes"); const file = api.readFile("/out.js"); @@ -189,7 +189,7 @@ describe("bundler", () => { files: { "/entry.js": NodePolyfills.options.files["/entry.js"], }, - platform: "browser", + target: "browser", external: Object.keys(nodePolyfillList), onAfterBundle(api) { const file = api.readFile("/out.js"); @@ -211,7 +211,7 @@ describe("bundler", () => { "bun:dns": "error", "bun:test": "error", "bun:sqlite": "error", - "bun:wrap": "error", + // "bun:wrap": "error", "bun:internal": "error", "bun:jsc": "error", }; @@ -220,7 +220,7 @@ describe("bundler", () => { .filter(x => x[1] !== "error") .map(x => x[0]); - // segfaults the test runner + // all of them are set to error so this test doesnt make sense to run itBundled.skip("browser/BunPolyfill", { skipOnEsbuild: true, files: { @@ -233,7 +233,7 @@ describe("bundler", () => { ${nonErroringBunModules.map((x, i) => `console.log("${x.padEnd(12, " ")}:", scan(bun_${i}));`).join("\n")} `, }, - platform: "browser", + target: "browser", onAfterBundle(api) { assert(!api.readFile("/out.js").includes("\0"), "bundle should not contain null bytes"); const file = api.readFile("/out.js"); @@ -257,7 +257,7 @@ describe("bundler", () => { .join("\n")} `, }, - platform: "browser", + target: "browser", bundleErrors: { "/entry.js": Object.keys(bunModules) .filter(x => bunModules[x] === "error") @@ -266,10 +266,10 @@ describe("bundler", () => { }); // not implemented right now - itBundled.skip("browser/BunPolyfillExternal", { + itBundled("browser/BunPolyfillExternal", { skipOnEsbuild: true, files: ImportBunError.options.files, - platform: "browser", + target: "browser", external: Object.keys(bunModules), onAfterBundle(api) { const file = api.readFile("/out.js"); diff --git a/test/bundler/bundler_edgecase.test.ts b/test/bundler/bundler_edgecase.test.ts index cd4b57bc8..216e813d6 100644 --- a/test/bundler/bundler_edgecase.test.ts +++ b/test/bundler/bundler_edgecase.test.ts @@ -74,10 +74,11 @@ describe("bundler", () => { external: ["external"], mode: "transform", minifySyntax: true, - platform: "bun", + target: "bun", run: { file: "/entry.ts" }, }); itBundled("edgecase/TemplateStringIssue622", { + notImplemented: true, files: { "/entry.ts": /* js */ ` capture(\`\\?\`); @@ -85,7 +86,7 @@ describe("bundler", () => { `, }, capture: ["`\\\\?`", "hello`\\\\?`"], - platform: "bun", + target: "bun", }); itBundled("edgecase/StringNullBytes", { files: { @@ -121,7 +122,7 @@ describe("bundler", () => { capture(process.env.NODE_ENV === 'development'); `, }, - platform: "browser", + target: "browser", capture: ['"development"', "false", "true"], env: { // undefined will ensure this variable is not passed to the bundler @@ -136,7 +137,7 @@ describe("bundler", () => { capture(process.env.NODE_ENV === 'development'); `, }, - platform: "browser", + target: "browser", capture: ['"development"', "false", "true"], env: { NODE_ENV: "development", @@ -150,19 +151,40 @@ describe("bundler", () => { capture(process.env.NODE_ENV === 'development'); `, }, - platform: "browser", + target: "browser", capture: ['"production"', "true", "false"], env: { NODE_ENV: "production", }, }); + itBundled("edgecase/NodeEnvOptionalChaining", { + notImplemented: true, + files: { + "/entry.js": /* js */ ` + capture(process?.env?.NODE_ENV); + capture(process?.env?.NODE_ENV === 'production'); + capture(process?.env?.NODE_ENV === 'development'); + capture(process.env?.NODE_ENV); + capture(process.env?.NODE_ENV === 'production'); + capture(process.env?.NODE_ENV === 'development'); + capture(process?.env.NODE_ENV); + capture(process?.env.NODE_ENV === 'production'); + capture(process?.env.NODE_ENV === 'development'); + `, + }, + target: "browser", + capture: ['"development"', "false", "true", '"development"', "false", "true", '"development"', "false", "true"], + env: { + NODE_ENV: "development", + }, + }); itBundled("edgecase/ProcessEnvArbitrary", { files: { "/entry.js": /* js */ ` capture(process.env.ARBITRARY); `, }, - platform: "browser", + target: "browser", capture: ["process.env.ARBITRARY"], env: { ARBITRARY: "secret environment stuff!", @@ -228,4 +250,132 @@ describe("bundler", () => { stdout: "1", }, }); + itBundled("edgecase/ValidLoaderSeenAsInvalid", { + files: { + "/entry.js": /* js */ `console.log(1)`, + }, + outdir: "/out", + loader: { + ".a": "file", // segfaults + ".b": "text", // InvalidLoader + ".c": "toml", // InvalidLoader + ".d": "json", + ".e": "js", + ".f": "ts", + ".g": "jsx", + ".h": "tsx", + // ".i": "wasm", + // ".j": "napi", + // ".k": "base64", + // ".l": "dataurl", + // ".m": "binary", + // ".n": "empty", + // ".o": "copy", + }, + }); + itBundled("edgecase/InvalidLoaderSegfault", { + files: { + "/entry.js": /* js */ `console.log(1)`, + }, + outdir: "/out", + loader: { + ".cool": "wtf", + }, + bundleErrors: { + // todo: get the exact error + "<bun>": ["InvalidLoader"], + }, + }); + itBundled("edgecase/ScriptTagEscape", { + files: { + "/entry.js": /* js */ ` + console.log('<script></script>'); + console.log(await import('./text-file.txt')) + `, + "/text-file.txt": /* txt */ ` + <script></script> + `, + }, + outdir: "/out", + onAfterBundle(api) { + try { + expect(api.readFile("/out/entry.js")).not.toContain("</script>"); + } catch (error) { + console.error("Bundle contains </script> which will break if this bundle is placed in a script tag."); + throw error; + } + }, + }); + itBundled("edgecase/JSONDefaultImport", { + files: { + "/entry.js": /* js */ ` + import def from './test.json' + console.log(JSON.stringify(def)) + `, + "/test.json": `{ "hello": 234, "world": 123 }`, + }, + run: { + stdout: '{"hello":234,"world":123}', + }, + }); + itBundled("edgecase/JSONDefaultKeyImport", { + files: { + "/entry.js": /* js */ ` + import def from './test.json' + console.log(def.hello) + `, + "/test.json": `{ "hello": 234, "world": "REMOVE" }`, + }, + run: { + stdout: "234", + }, + }); + itBundled("edgecase/JSONDefaultAndNamedImport", { + files: { + "/entry.js": /* js */ ` + import def from './test.json' + import { hello } from './test.json' + console.log(def.hello, hello) + `, + "/test.json": `{ "hello": 234, "world": "REMOVE" }`, + }, + dce: true, + run: { + stdout: "234 234", + }, + }); + itBundled("edgecase/JSONWithDefaultKey", { + files: { + "/entry.js": /* js */ ` + import def from './test.json' + console.log(JSON.stringify(def)) + `, + "/test.json": `{ "default": 234 }`, + }, + dce: true, + run: { + stdout: '{"default":234}', + }, + }); + itBundled("edgecase/JSONWithDefaultKeyNamespace", { + files: { + "/entry.js": /* js */ ` + import * as ns from './test.json' + console.log(JSON.stringify(ns)) + `, + "/test.json": `{ "default": 234 }`, + }, + dce: true, + run: { + stdout: '{"default":234}', + }, + }); + itBundled("edgecase/RequireUnknownExtension", { + files: { + "/entry.js": /* js */ ` + require('./x.aaaa') + `, + "/x.aaaa": `x`, + }, + }); }); diff --git a/test/bundler/bundler_jsx.test.ts b/test/bundler/bundler_jsx.test.ts new file mode 100644 index 000000000..3129f06be --- /dev/null +++ b/test/bundler/bundler_jsx.test.ts @@ -0,0 +1,322 @@ +import assert from "assert"; +import dedent from "dedent"; +import { BundlerTestInput, itBundled, testForFile } from "./expectBundled"; +var { describe, test, expect } = testForFile(import.meta.path); + +const helpers = { + "/node_modules/bun-test-helpers/index.js": /* js */ ` + export function print(arg) { + const replacer = (_, val) => { + if(typeof val === "function") { + if(val.name) return 'Function:' + val.name; + return val.toString(); + } + if(typeof val === "symbol") return val.toString(); + if(val === undefined) return "undefined"; + if(val === null) return "null"; + return val; + } + const stringified = JSON.stringify(arg, replacer); + if(!process.env.IS_TEST_RUNNER) { + console.log(arg); + } + console.log(stringified); + } + `, + "/node_modules/react/jsx-dev-runtime.js": /* js */ ` + const $$typeof = Symbol.for("jsxdev"); + export function jsxDEV(type, props, key, source, self) { + return { + $$typeof, type, props, key, source, self + } + } + export const Fragment = Symbol.for("jsxdev.fragment"); + `, + "/node_modules/react/jsx-runtime.js": /* js */ ` + const $$typeof = Symbol.for("jsx"); + export function jsx(type, props, key) { + return { + $$typeof, type, props, key + } + } + export const Fragment = Symbol.for("jsx.fragment"); + `, + "/node_modules/custom-jsx-dev/index.js": /* js */ ` + export function jsxDEV(type, props, key, source, self) { + return ['custom-jsx-dev', type, props, key, source, self] + } + export const Fragment = "CustomFragment" + `, + "/node_modules/custom-jsx/index.js": /* js */ ` + export function jsx(a, b, c) { + return ['custom-jsx', a, b, c] + } + export const Fragment = "CustomFragment" + `, + "/node_modules/custom-classic/index.js": /* js */ ` + export function createElement(type, props, ...children) { + return ['custom-classic', type, props, children] + } + export const Fragment = "CustomFragment" + export const something = "something" + `, + "/node_modules/custom-automatic/jsx-runtime.js": /* js */ ` + const $$typeof = Symbol.for("custom_jsx"); + export function jsx(type, props, key) { + return { + $$typeof, type, props, key + } + } + export const Fragment = Symbol.for("custom.fragment"); + `, + "/node_modules/custom-automatic/jsx-dev-runtime.js": /* js */ ` + const $$typeof = Symbol.for("custom_jsxdev"); + export function jsxDEV(type, props, key, source, self) { + return { + $$typeof, type, props, key, source, self + } + } + export const Fragment = Symbol.for("custom_dev.fragment"); + `, + "/node_modules/custom-automatic/index.js": /* js */ ` + export const Fragment = "FAILED" + `, + "/node_modules/react/index.js": /* js */ ` + export function createElement(type, props, ...children) { + return ['react', type, props, children] + } + export const Fragment = Symbol.for("react.fragment") + + export const fn = () => { + throw new Error('test failed') + } + export const something = 'test failed'; + `, + "/node_modules/custom-renamed/index.js": /* js */ ` + export function fn(type, props, ...children) { + return ['custom-renamed', type, props, children] + } + export const Fragment = "CustomFragment" + export const something = "something" + `, + "/node_modules/preact/index.js": /* js */ ` + export function h(type, props, ...children) { + return ['preact', type, props, children] + } + export const Fragment = "PreactFragment" + `, +}; + +function itBundledDevAndProd( + id: string, + opts: BundlerTestInput & { + devStdout?: string; + prodStdout?: string; + devNotImplemented?: boolean; + prodNotImplemented?: boolean; + }, +) { + const { devStdout, prodStdout, ...rest } = opts; + itBundled(id + "Dev", { + notImplemented: opts.devNotImplemented, + ...rest, + env: { + NODE_ENV: "development", + }, + run: devStdout + ? { + ...(rest.run === true ? {} : rest.run), + stdout: devStdout, + } + : rest.run, + }); + itBundled(id + "Prod", { + notImplemented: opts.prodNotImplemented, + ...rest, + env: { + NODE_ENV: "production", + }, + run: prodStdout + ? { + ...(rest.run === true ? {} : rest.run), + stdout: prodStdout, + } + : rest.run, + }); +} + +describe("bundler", () => { + itBundledDevAndProd("jsx/Automatic", { + files: { + "index.jsx": /* js*/ ` + import { print } from 'bun-test-helpers' + const Component = 'hello' + print(<div>Hello World</div>) + print(<div className="container"><Component prop={2}><h1 onClick={() => 1}>hello</h1></Component></div>) + `, + ...helpers, + }, + target: "bun", + devStdout: ` + {"$$typeof":"Symbol(jsxdev)","type":"div","props":{"children":"Hello World"},"key":"undefined","source":false,"self":"undefined"} + {"$$typeof":"Symbol(jsxdev)","type":"div","props":{"className":"container","children":{"$$typeof":"Symbol(jsxdev)","type":"hello","props":{"prop":2,"children":{"$$typeof":"Symbol(jsxdev)","type":"h1","props":{"onClick":"Function:onClick","children":"hello"},"key":"undefined","source":false,"self":"undefined"}},"key":"undefined","source":false,"self":"undefined"}},"key":"undefined","source":false,"self":"undefined"} + `, + prodStdout: ` + {"$$typeof":"Symbol(react.element)","type":"div","key":"null","ref":"null","props":{"children":"Hello World"},"_owner":"null"} + {"$$typeof":"Symbol(react.element)","type":"div","key":"null","ref":"null","props":{"className":"container","children":{"$$typeof":"Symbol(react.element)","type":"hello","key":"null","ref":"null","props":{"prop":2,"children":{"$$typeof":"Symbol(react.element)","type":"h1","key":"null","ref":"null","props":{"onClick":"Function:onClick","children":"hello"},"_owner":"null"}},"_owner":"null"}},"_owner":"null"} + `, + }); + // bun does not do the production transform for fragments as good as it could be right now. + itBundledDevAndProd("jsx/AutomaticFragment", { + notImplemented: true, + files: { + "index.jsx": /* js*/ ` + import { print } from 'bun-test-helpers' + const Component = 'hello' + print(<div>Hello World</div>) + print(<div className="container"><Component prop={2}><h1 onClick={() => 1}>hello</h1></Component></div>) + print(<>Fragment</>) + `, + ...helpers, + }, + target: "bun", + devStdout: ` + {"$$typeof":"Symbol(jsxdev)","type":"Symbol(jsxdev.fragment)","props":{"children":"Fragment"},"key":"undefined","source":false,"self":"undefined"} + `, + prodStdout: ` + {"$$typeof":"Symbol(react.element)","type":"Symbol("jsx.fragment")","key":"null","ref":"null","props":{"children":"Fragment"},"_owner":"null"} + `, + }); + itBundledDevAndProd("jsx/ImportSource", { + files: { + "/index.jsx": /* js*/ ` + import { print } from 'bun-test-helpers' + print([<div props={123}>Hello World</div>, <>Fragment</>]) + `, + ...helpers, + }, + target: "bun", + jsx: { + importSource: "custom-automatic", + }, + devStdout: ` + [{"$$typeof":"Symbol(custom_jsxdev)","type":"div","props":{"props":123,"children":"Hello World"},"key":"undefined","source":false,"self":"undefined"},{"$$typeof":"Symbol(custom_jsxdev)","type":"Symbol(custom_dev.fragment)","props":{"children":"Fragment"},"key":"undefined","source":false,"self":"undefined"}] + `, + prodStdout: ` + [{"$$typeof":"Symbol(custom_jsx)","type":"div","props":{"props":123,"children":"Hello World"},"key":"undefined"},{"$$typeof":"Symbol(custom_jsx)","type":"Symbol(custom_dev.fragment)","props":{"children":"Fragment"},"key":"undefined"}] + `, + }); + itBundledDevAndProd("jsx/Classic", { + files: { + "/index.jsx": /* js*/ ` + import { print } from 'bun-test-helpers' + // not react to catch if bun auto imports or uses the global + import * as React from 'custom-classic' + print([<div props={123}>Hello World</div>, <>Fragment</>]) + `, + ...helpers, + }, + target: "bun", + jsx: { + runtime: "classic", + importSource: "ignore-me", + }, + run: { + stdout: ` + [["custom-classic","div",{"props":123},["Hello World"]],["custom-classic","CustomFragment","null",["Fragment"]]] + `, + }, + }); + itBundledDevAndProd("jsx/ClassicPragma", { + files: { + "/index.jsx": /* js*/ ` + // @jsx fn + // @jsxFrag something + import { print } from 'bun-test-helpers' + import { fn, something } from 'custom-renamed' + print([<div props={123}>Hello World</div>, <>Fragment</>]) + `, + ...helpers, + }, + target: "bun", + jsx: { + runtime: "classic", + importSource: "ignore-me", + }, + run: { + stdout: ` + [["custom-renamed","div",{"props":123},["Hello World"]],["custom-renamed","something","null",["Fragment"]]] + `, + }, + }); + itBundledDevAndProd("jsx/PragmaMultiple", { + files: { + "/index.jsx": /* js*/ ` + import './classic.jsx' + import './classic-renamed.jsx' + import './automatic.jsx' + import './automatic-source2.jsx' + `, + "/classic.jsx": /* js*/ ` + /* @jsxRuntime classic */ + import { print } from 'bun-test-helpers' + // not react to catch if bun auto imports or uses the global + import * as React from 'custom-classic' + print(['classic.jsx',<div props={123}>Hello World</div>, <>Fragment</>]) + `, + "/classic-renamed.jsx": /* js*/ ` + /* @jsxRuntime classic */ + // @jsx fn + // @jsxFrag something + import { print } from 'bun-test-helpers' + import { fn, something } from 'custom-renamed' + print(['classic-renamed.jsx',<div props={123}>Hello World</div>, <>Fragment</>]) + `, + "/automatic.jsx": /* js*/ ` + import { print } from 'bun-test-helpers' + print(['automatic.jsx',<div props={123}>Hello World</div>, process.env.NODE_ENV === 'production' ? '' : <>Fragment</>]) + `, + "/automatic-source2.jsx": /* js*/ ` + // @jsxImportSource custom-automatic + import { print } from 'bun-test-helpers' + print(['automatic-source2.jsx',<div props={123}>Hello World</div>, <>Fragment</>]) + `, + ...helpers, + }, + target: "bun", + devStdout: ` + ["classic.jsx",["custom-classic","div",{"props":123},["Hello World"]],["custom-classic","CustomFragment","null",["Fragment"]]] + ["classic-renamed.jsx",["custom-renamed","div",{"props":123},["Hello World"]],["custom-renamed","something","null",["Fragment"]]] + ["automatic.jsx",{"$$typeof":"Symbol(jsxdev)","type":"div","props":{"props":123,"children":"Hello World"},"key":"undefined","source":false,"self":"undefined"},{"$$typeof":"Symbol(jsxdev)","type":"Symbol(jsxdev.fragment)","props":{"children":"Fragment"},"key":"undefined","source":false,"self":"undefined"}] + ["automatic-source2.jsx",{"$$typeof":"Symbol(custom_jsxdev)","type":"div","props":{"props":123,"children":"Hello World"},"key":"undefined","source":false,"self":"undefined"},{"$$typeof":"Symbol(custom_jsxdev)","type":"Symbol(custom_dev.fragment)","props":{"children":"Fragment"},"key":"undefined","source":false,"self":"undefined"}] + `, + prodStdout: ` + ["classic.jsx",["custom-classic","div",{"props":123},["Hello World"]],["custom-classic","CustomFragment","null",["Fragment"]]] + ["classic-renamed.jsx",["custom-renamed","div",{"props":123},["Hello World"]],["custom-renamed","something","null",["Fragment"]]] + ["automatic.jsx",{"$$typeof":"Symbol(react.element)","type":"div","key":"null","ref":"null","props":{"props":123,"children":"Hello World"},"_owner":"null"},""] + ["automatic-source2.jsx",{"$$typeof":"Symbol(custom_jsx)","type":"div","props":{"props":123,"children":"Hello World"},"key":"undefined"},{"$$typeof":"Symbol(custom_jsx)","type":"Symbol(custom.fragment)","props":{"children":"Fragment"},"key":"undefined"}] + `, + }); + itBundledDevAndProd("jsx/Factory", { + files: { + "/index.jsx": /* js*/ ` + const h = () => 'hello' + const Fragment = 123; + + import { print } from 'bun-test-helpers' + print([<div props={123}>Hello World</div>, <>Fragment</>]) + `, + ...helpers, + }, + target: "bun", + jsx: { + runtime: "classic", + factory: "h", + }, + run: { + stdout: ` + hello hello + `, + }, + }); +}); diff --git a/test/bundler/bundler_minify.test.ts b/test/bundler/bundler_minify.test.ts index cad991f2a..1bd255e46 100644 --- a/test/bundler/bundler_minify.test.ts +++ b/test/bundler/bundler_minify.test.ts @@ -52,7 +52,7 @@ describe("bundler", () => { "!1", ], minifySyntax: true, - platform: "bun", + target: "bun", }); itBundled("minify/FunctionExpressionRemoveName", { notImplemented: true, @@ -67,7 +67,7 @@ describe("bundler", () => { capture: ["function(", "function(", "function e("], minifySyntax: true, minifyIdentifiers: true, - platform: "bun", + target: "bun", }); itBundled("minify/PrivateIdentifiersNameCollision", { files: { @@ -123,4 +123,15 @@ describe("bundler", () => { assert([...code.matchAll(/var /g)].length === 1, "expected only 1 variable declaration statement"); }, }); + itBundled("minify/InlineArraySpread", { + files: { + "/entry.js": /* js */ ` + capture([1, 2, ...[3, 4], 5, 6, ...[7, ...[...[...[...[8, 9]]]]], 10, ...[...[...[...[...[...[...[11]]]]]]]]); + capture([1, 2, ...[3, 4], 5, 6, ...[7, [...[...[...[8, 9]]]]], 10, ...[...[...[...[...[...[...11]]]]]]]); + `, + }, + capture: ["[1,2,3,4,5,6,7,8,9,10,11]", "[1,2,3,4,5,6,7,[8,9],10,...11]"], + minifySyntax: true, + minifyWhitespace: true, + }); }); diff --git a/test/bundler/bundler_plugin.test.ts b/test/bundler/bundler_plugin.test.ts index bde2f180c..312c8f252 100644 --- a/test/bundler/bundler_plugin.test.ts +++ b/test/bundler/bundler_plugin.test.ts @@ -425,6 +425,152 @@ describe("bundler", () => { onAfterBundle(api) {}, }; }); + itBundled("plugin/TwoPluginBug", ({ root }) => { + return { + files: { + "index.ts": /* ts */ ` + import { foo } from "plugin1"; + console.log(foo); + `, + }, + plugins: [ + { + name: "1", + setup(builder) { + builder.onResolve({ filter: /plugin1/ }, args => { + return { + path: "plugin1", + namespace: "plugin1", + }; + }); + builder.onLoad({ filter: /plugin1/, namespace: "plugin1" }, args => { + return { + contents: "export * from 'plugin2';", + loader: "js", + }; + }); + }, + }, + { + name: "2", + setup(builder) { + builder.onResolve({ filter: /plugin2/ }, args => { + return { + path: "plugin2", + namespace: "plugin2", + }; + }); + builder.onLoad({ filter: /plugin2/, namespace: "plugin2" }, args => { + return { + contents: "export const foo = 'foo';", + loader: "js", + }; + }); + }, + }, + ], + run: { + stdout: "foo", + }, + }; + }); + itBundled("plugin/LoadCalledOnce", ({ root }) => { + let resolveCount = 0; + let loadCount = 0; + return { + files: { + "index.ts": /* ts */ ` + import { foo } from "plugin:first"; + import { foo as foo2 } from "plugin:second"; + import { foo as foo3 } from "plugin:third"; + console.log(foo === foo2, foo === foo3); + `, + }, + plugins: [ + { + name: "1", + setup(builder) { + builder.onResolve({ filter: /^plugin:/ }, args => { + resolveCount++; + return { + path: "plugin", + namespace: "plugin", + }; + }); + builder.onLoad({ filter: /^plugin$/, namespace: "plugin" }, args => { + loadCount++; + return { + contents: "export const foo = { };", + loader: "js", + }; + }); + }, + }, + ], + run: { + stdout: "true true", + }, + onAfterBundle(api) { + expect(resolveCount).toBe(3); + expect(loadCount).toBe(1); + }, + }; + }); + itBundled("plugin/ResolveManySegfault", ({ root }) => { + let resolveCount = 0; + let loadCount = 0; + return { + files: { + "index.ts": /* ts */ ` + import { foo as foo1 } from "plugin:100"; + console.log(foo1); + `, + }, + plugins: [ + { + name: "1", + setup(builder) { + builder.onResolve({ filter: /^plugin:/ }, args => { + resolveCount++; + return { + path: args.path, + namespace: "plugin", + }; + }); + builder.onLoad({ filter: /^plugin:/, namespace: "plugin" }, args => { + loadCount++; + const number = parseInt(args.path.replace("plugin:", "")); + if (number > 1) { + const numberOfImports = number > 100 ? 100 : number; + const imports = Array.from({ length: numberOfImports }) + .map((_, i) => `import { foo as foo${i} } from "plugin:${number - i - 1}";`) + .join("\n"); + const exports = `export const foo = ${Array.from({ length: numberOfImports }) + .map((_, i) => `foo${i}`) + .join(" + ")};`; + return { + contents: `${imports}\n${exports}`, + loader: "js", + }; + } else { + return { + contents: `export const foo = 1;`, + loader: "js", + }; + } + }); + }, + }, + ], + run: { + stdout: "101 102", + }, + onAfterBundle(api) { + expect(resolveCount).toBe(103); + expect(loadCount).toBe(102); + }, + }; + }); }); // TODO: add async on resolve stuff diff --git a/test/bundler/esbuild/css.test.ts b/test/bundler/esbuild/css.test.ts index 75eeb21c5..f1bfde569 100644 --- a/test/bundler/esbuild/css.test.ts +++ b/test/bundler/esbuild/css.test.ts @@ -484,7 +484,7 @@ var { describe, test, expect } = testForFile(import.meta.path); // }, // metafile: true, // entryPoints: ["/foo/entry.js", "/bar/entry.js"], -// entryNames: "[ext]/[hash]", +// entryNaming: "[ext]/[hash]", // outdir: "/", // }); // itBundled("css/DeduplicateRules", { diff --git a/test/bundler/esbuild/default.test.ts b/test/bundler/esbuild/default.test.ts index 83822411f..03a7f1adf 100644 --- a/test/bundler/esbuild/default.test.ts +++ b/test/bundler/esbuild/default.test.ts @@ -494,11 +494,15 @@ describe("bundler", () => { itBundled("default/JSXSyntaxInJS", { files: { "/entry.mjs": `console.log(<div/>)`, + "/entry.cjs": `console.log(<div/>)`, }, bundleErrors: { // TODO: this could be a nicer error "/entry.mjs": [`Unexpected <`], + "/entry.cjs": [`Unexpected <`], }, + outdir: "/out", + entryPoints: ["/entry.mjs", "/entry.cjs"], }); itBundled("default/JSXConstantFragments", { notImplemented: true, // jsx in bun is too different to esbuild @@ -856,7 +860,7 @@ describe("bundler", () => { require.resolve(v ? y ? 'a' : 'b' : c) `, }, - platform: "node", + target: "node", format: "cjs", // esbuild seems to not need externals for require.resolve, but it should be specified external: ["a", "b", "c"], @@ -925,7 +929,7 @@ describe("bundler", () => { await import('./out/b'); `, }, - entryNames: "[name].[ext]", + entryNaming: "[name].[ext]", entryPoints: ["/a.js", "/b.js"], external: ["a", "b", "c"], run: [ @@ -1023,42 +1027,6 @@ describe("bundler", () => { stdout: "./test.txt", }, }); - itBundled("default/RequireWithoutCallPlatformNeutral", { - notImplemented: true, - // `require` on line one has to be renamed to `__require` - files: { - "/entry.js": /* js */ ` - const req = require - req('./entry') - capture(req) - `, - }, - platform: "neutral", - onAfterBundle(api) { - const varName = api.captureFile("/out.js")[0]; - const assignmentValue = api.readFile("/out.js").match(new RegExp(`${varName} = (.*);`))![1]; - expect(assignmentValue).not.toBe("require"); - }, - }); - itBundled("default/NestedRequireWithoutCallPlatformNeutral", { - notImplemented: true, - // `require` on line one has to be renamed to `__require` - files: { - "/entry.js": /* js */ ` - (() => { - const req = require - req('./entry') - capture(req) - })() - `, - }, - platform: "neutral", - onAfterBundle(api) { - const varName = api.captureFile("/out.js")[0]; - const assignmentValue = api.readFile("/out.js").match(new RegExp(`${varName} = (.*);`))![1]; - expect(assignmentValue).not.toBe("require"); - }, - }); itBundled("default/RequireWithCallInsideTry", { files: { "/entry.js": /* js */ ` @@ -1093,27 +1061,6 @@ describe("bundler", () => { }, run: [{ file: "/test1.js" }, { file: "/test2.js" }], }); - itBundled("default/RequireWithoutCallInsideTry", { - notImplemented: true, - // `require` has to be renamed to `__require` - files: { - "/entry.js": /* js */ ` - try { - oldLocale = globalLocale._abbr; - var aliasedRequire = require; - aliasedRequire('./locale/' + name); - getSetGlobalLocale(oldLocale); - capture(aliasedRequire) - } catch (e) {} - `, - }, - platform: "neutral", - onAfterBundle(api) { - const varName = api.captureFile("/out.js")[0]; - const assignmentValue = api.readFile("/out.js").match(new RegExp(`${varName} = (.*);`))![1]; - expect(assignmentValue).not.toBe("require"); - }, - }); itBundled("default/RequirePropertyAccessCommonJS", { files: { "/entry.js": /* js */ ` @@ -1124,7 +1071,7 @@ describe("bundler", () => { delete require.extensions['.json'] `, }, - platform: "node", + target: "node", format: "cjs", onAfterBundle(api) { api.prependFile( @@ -1252,18 +1199,6 @@ describe("bundler", () => { assert(api.readFile("/out.js").startsWith("#!/usr/bin/env a"), "hashbang exists on bundle"); }, }); - itBundled("default/HashbangNoBundle", { - files: { - "/entry.js": /* js */ ` - #!/usr/bin/env node - process.exit(0); - `, - }, - mode: "transform", - onAfterBundle(api) { - assert(api.readFile("/out.js").startsWith("#!/usr/bin/env node"), "hashbang exists on bundle"); - }, - }); itBundled("default/HashbangBannerUseStrictOrder", { files: { "/entry.js": /* js */ ` @@ -1281,7 +1216,7 @@ describe("bundler", () => { files: { "/entry.js": `console.log(require('fs'))`, }, - platform: "browser", + target: "browser", run: { stdout: "[Function]", }, @@ -1291,7 +1226,7 @@ describe("bundler", () => { "/entry.js": `console.log('existsSync' in require('fs'))`, }, format: "cjs", - platform: "node", + target: "node", run: { stdout: "true", }, @@ -1302,7 +1237,7 @@ describe("bundler", () => { }, minifyWhitespace: true, format: "cjs", - platform: "node", + target: "node", run: { stdout: "true", }, @@ -1320,7 +1255,7 @@ describe("bundler", () => { run: { stdout: "[Function] undefined undefined", }, - platform: "browser", + target: "browser", }); itBundled("default/ImportFSNodeCommonJS", { files: { @@ -1332,7 +1267,7 @@ describe("bundler", () => { console.log('writeFileSync' in fs, readFileSync, 'writeFileSync' in defaultValue) `, }, - platform: "node", + target: "node", format: "cjs", run: { stdout: "true [Function: readFileSync] true", @@ -1348,7 +1283,7 @@ describe("bundler", () => { console.log('writeFileSync' in fs, readFileSync, 'writeFileSync' in defaultValue) `, }, - platform: "node", + target: "node", run: { stdout: "true [Function: readFileSync] true", }, @@ -1360,7 +1295,7 @@ describe("bundler", () => { export {readFileSync} from 'fs' `, }, - platform: "browser", + target: "browser", run: { file: "out.js", }, @@ -1380,7 +1315,7 @@ describe("bundler", () => { assert(module.readFileSync === fs.readFileSync, 'export {readFileSync} from "fs"; works') `, }, - platform: "node", + target: "node", run: { file: "/test.js", }, @@ -1404,7 +1339,7 @@ describe("bundler", () => { assert(module.rfs === fs.readFileSync, 'export {rfs} works') `, }, - platform: "node", + target: "node", run: { file: "/test.js", }, @@ -1428,7 +1363,7 @@ describe("bundler", () => { assert(mod.foo === 123, 'exports.foo') `, }, - platform: "node", + target: "node", run: { file: "/test.js", }, @@ -1444,7 +1379,7 @@ describe("bundler", () => { `, }, format: "esm", - platform: "node", + target: "node", run: { file: "/test.js", }, @@ -1460,7 +1395,7 @@ describe("bundler", () => { `, }, format: "cjs", - platform: "node", + target: "node", run: { file: "/test.js", }, @@ -2310,7 +2245,7 @@ describe("bundler", () => { }, }); itBundled("default/AutoExternalNode", { - notImplemented: true, + // notImplemented: true, files: { "/entry.js": /* js */ ` // These URLs should be external automatically @@ -2325,7 +2260,7 @@ describe("bundler", () => { import "node:what-is-this"; `, }, - platform: "node", + target: "node", treeShaking: true, onAfterBundle(api) { const file = api.readFile("/out.js"); @@ -2356,7 +2291,7 @@ describe("bundler", () => { import "bun:what-is-this"; `, }, - platform: "bun", + target: "bun", onAfterBundle(api) { const file = api.readFile("/out.js"); const imports = new Bun.Transpiler().scanImports(file); @@ -2613,7 +2548,7 @@ describe("bundler", () => { `, }, inject: ["/shims.js"], - platform: "node", + target: "node", run: { stdout: "function", }, @@ -3794,7 +3729,7 @@ describe("bundler", () => { `, "/present-file.js": ``, }, - platform: "node", + target: "node", format: "cjs", external: ["external-pkg", "@scope/external-pkg", "{{root}}/external-file"], }); @@ -4315,6 +4250,7 @@ describe("bundler", () => { }, }); itBundled("default/DefineOptionalChain", { + notImplemented: true, files: { "/entry.js": /* js */ ` log([ @@ -5205,7 +5141,7 @@ describe("bundler", () => { "/node_modules/second-path/index.js": `module.exports = 567`, }, external: ["*"], - platform: "browser", + target: "browser", format: "esm", outfile: "/out.mjs", run: { @@ -5232,7 +5168,7 @@ describe("bundler", () => { files: RequireShimSubstitutionBrowser.options.files, runtimeFiles: RequireShimSubstitutionBrowser.options.runtimeFiles, external: ["*"], - platform: "node", + target: "node", format: "esm", outfile: "/out.mjs", run: { @@ -5292,7 +5228,7 @@ describe("bundler", () => { "/node_modules/fs/index.js": `console.log('include this too')`, "/node_modules/fs/promises.js": `throw 'DO NOT INCLUDE THIS'`, }, - platform: "node", + target: "node", }); itBundled("default/EntryNamesNoSlashAfterDir", { // GENERATED @@ -5303,7 +5239,7 @@ describe("bundler", () => { }, entryPoints: ["/src/app1/main.ts", "/src/app2/main.ts", "/src/app3/main.ts"], outputPaths: ["/out/app1-main.js", "/out/app2-main.js", "/out/app3-main.js"], - entryNames: "[dir]-[name].[ext]", + entryNaming: "[dir]-[name].[ext]", }); // itBundled("default/EntryNamesNonPortableCharacter", { // // GENERATED @@ -5331,7 +5267,7 @@ describe("bundler", () => { // entryPoints: ["/src/entries/entry1.js", "/src/entries/entry2.js"], // outbase: "/src", // splitting: true, - // entryNames: "main/[ext]/[name]-[hash].[ext]", + // entryNaming: "main/[ext]/[name]-[hash].[ext]", // }); itBundled("default/MinifyIdentifiersImportPathFrequencyAnalysis", { files: { @@ -5350,10 +5286,22 @@ describe("bundler", () => { minifyWhitespace: true, minifyIdentifiers: true, onAfterBundle(api) { - let importFile = api.readFile("/out/import.js").replace(/remove\(.*?\)/g, "remove()"); - let requireFile = api.readFile("/out/require.js").replace(/remove\(.*?\)/g, "remove()"); - assert(!["W", "X", "Y", "Z"].some(x => importFile.includes(x))); - assert(!["A", "B", "C", "D"].some(x => requireFile.includes(x))); + let importFile = api + .readFile("/out/import.js") + .replace(/remove\(.*?\)/g, "remove()") + .replace(/Object\.[a-z]+\b/gi, "null"); + let requireFile = api + .readFile("/out/require.js") + .replace(/remove\(.*?\)/g, "remove()") + .replace(/Object\.[a-z]+\b/gi, "null"); + assert( + !["W", "X", "Y", "Z"].some(x => importFile.includes(x)), + 'import.js should not contain "W", "X", "Y", or "Z"', + ); + assert( + !["A", "B", "C", "D"].some(x => requireFile.includes(x)), + 'require.js should not contain "A", "B", "C", or "D"', + ); }, }); itBundled("default/ToESMWrapperOmission", { @@ -6199,28 +6147,6 @@ describe("bundler", () => { // NOTE: You can either keep the import assertion and only use the "default" import, or you can remove the import assertion and use the "prop" import (which is non-standard behavior). // `, */ // }); - return; - itBundled("default/ExternalPackages", { - // GENERATED - files: { - "/project/entry.js": /* js */ ` - import 'pkg1' - import './file' - import './node_modules/pkg2/index.js' - import '#pkg3' - `, - "/project/package.json": /* json */ ` - { - "imports": { - "#pkg3": "./libs/pkg3.js" - } - } - `, - "/project/file.js": `console.log('file')`, - "/project/node_modules/pkg2/index.js": `console.log('pkg2')`, - "/project/libs/pkg3.js": `console.log('pkg3')`, - }, - }); itBundled("default/MetafileVariousCases", { // GENERATED files: { @@ -6290,7 +6216,8 @@ describe("bundler", () => { `, }, entryPoints: ["/project/entry.js", "/project/entry.css"], - mode: "convertformat", + external: ["*"], + metafile: true, }); itBundled("default/MetafileVeryLongExternalPaths", { // GENERATED @@ -6321,7 +6248,7 @@ describe("bundler", () => { }, }); itBundled("default/CommentPreservation", { - // GENERATED + notImplemented: true, files: { "/entry.js": /* js */ ` console.log( @@ -6467,28 +6394,54 @@ describe("bundler", () => { for (a of /*foo*/b); if (/*foo*/a); - with (/*foo*/a); while (/*foo*/a); do {} while (/*foo*/a); switch (/*foo*/a) {} `, }, - format: "cjs", + external: ["foo"], + onAfterBundle(api) { + const commentCounts: Record<string, number> = { + before: 44, + after: 18, + "comment before": 4, + "comment after": 4, + foo: 21, + bar: 4, + a: 1, + b: 1, + c: 1, + }; + const file = api.readFile("/out.js"); + const comments = [...file.matchAll(/\/\*([^*]+)\*\//g), ...file.matchAll(/\/\/([^\n]+)/g)] + .map(m => m[1].trim()) + .filter(m => m && !m.includes("__PURE__")); + + for (const key in commentCounts) { + const count = comments.filter(c => c === key).length; + if (count !== commentCounts[key]) { + throw new Error(`Expected ${commentCounts[key]} comments with "${key}", got ${count}`); + } + } + }, }); itBundled("default/CommentPreservationImportAssertions", { // GENERATED + notImplemented: true, files: { "/entry.jsx": /* jsx */ ` - import 'foo' /* before */ assert { type: 'json' } - import 'foo' assert /* before */ { type: 'json' } - import 'foo' assert { /* before */ type: 'json' } - import 'foo' assert { type: /* before */ 'json' } - import 'foo' assert { type: 'json' /* before */ } + import 'foo' /* a */ assert { type: 'json' } + import 'foo' assert /* b */ { type: 'json' } + import 'foo' assert { /* c */ type: 'json' } + import 'foo' assert { type: /* d */ 'json' } + import 'foo' assert { type: 'json' /* e */ } `, }, + external: ["foo"], }); itBundled("default/CommentPreservationTransformJSX", { // GENERATED + notImplemented: true, files: { "/entry.jsx": /* jsx */ ` console.log( @@ -6518,6 +6471,7 @@ describe("bundler", () => { }); itBundled("default/CommentPreservationPreserveJSX", { // GENERATED + notImplemented: true, files: { "/entry.jsx": /* jsx */ ` console.log( @@ -6545,19 +6499,4 @@ describe("bundler", () => { `, }, }); - itBundled("default/ErrorMessageCrashStdinESBuildIssue2913", { - // GENERATED - files: { - "/project/node_modules/fflate/package.json": `{ "main": "main.js" }`, - "/project/node_modules/fflate/main.js": ``, - }, - stdin: { - contents: `import "node_modules/fflate"`, - cwd: "/project", - }, - platform: "neutral", - /* TODO FIX expectedScanLog: `<stdin>: ERROR: Could not resolve "node_modules/fflate" - NOTE: You can mark the path "node_modules/fflate" as external to exclude it from the bundle, which will remove this error. - `, */ - }); }); diff --git a/test/bundler/esbuild/importstar_ts.test.ts b/test/bundler/esbuild/importstar_ts.test.ts index 5bbb0567e..0e39f0b29 100644 --- a/test/bundler/esbuild/importstar_ts.test.ts +++ b/test/bundler/esbuild/importstar_ts.test.ts @@ -7,7 +7,7 @@ import { RUN_UNCHECKED_TESTS, itBundled } from "../expectBundled"; // For debug, all files are written to $TEMP/bun-bundle-tests/ts describe("bundler", () => { - if (!RUN_UNCHECKED_TESTS) return; + return; itBundled("ts/TSImportStarUnused", { // GENERATED files: { diff --git a/test/bundler/esbuild/loader.test.ts b/test/bundler/esbuild/loader.test.ts index 93a4e5fff..0b946d0b3 100644 --- a/test/bundler/esbuild/loader.test.ts +++ b/test/bundler/esbuild/loader.test.ts @@ -7,8 +7,7 @@ var { describe, test, expect } = testForFile(import.meta.path); // For debug, all files are written to $TEMP/bun-bundle-tests/loader describe("bundler", () => { - itBundled("loader/LoaderJSONCommonJSAndES6", { - // GENERATED + itBundled("loader/JSONCommonJSAndES6", { files: { "/entry.js": /* js */ ` const x_json = require('./x.json') @@ -20,19 +19,19 @@ describe("bundler", () => { "/y.json": `{"y1": true, "y2": false}`, "/z.json": /* json */ ` { - "big": "this is a big long line of text that should be discarded", - "small": "some small text", - "if": "test keyword imports" - } + "big": "this is a big long line of text that should be REMOVED", + "small": "some small text", + "if": "test keyword imports" + } `, }, + dce: true, run: { - stdout: '{"x":true} {} some small text test keyword imports', + stdout: '{"x":true} {"y1":true,"y2":false} some small text test keyword imports', }, }); - itBundled("loader/LoaderJSONSharedWithMultipleEntriesESBuildIssue413", { - // GENERATED + itBundled("loader/JSONSharedWithMultipleEntriesESBuildIssue413", { files: { "/a.js": /* js */ ` import data from './data.json' @@ -54,137 +53,192 @@ describe("bundler", () => { run: [ { file: "/out/a.js", - stdout: 'a: {"test":123} 123 true true true true {"test":123,"default":{"test":123}}', + stdout: 'a: {"test":123} 123 true true true true {"test":123}', }, { file: "/out/b.js", - stdout: 'b: {"test":123} 123 true true true true {"test":123,"default":{"test":123}}', + stdout: 'b: {"test":123} 123 true true true true {"test":123}', }, ], }); - if (!RUN_UNCHECKED_TESTS) return; - itBundled("loader/LoaderFile", { - // GENERATED + itBundled("loader/File", { files: { "/entry.js": `console.log(require('./test.svg'))`, "/test.svg": `<svg></svg>`, }, - outdir: "/out/", + outdir: "/out", + loader: { + // ".svg": "file", + }, + run: { + stdout: /\.\/test-.*\.svg/, + }, }); - itBundled("loader/LoaderFileMultipleNoCollision", { - // GENERATED + itBundled("loader/FileMultipleNoCollision", { files: { "/entry.js": /* js */ ` - console.log( - require('./a/test.txt'), - require('./b/test.txt'), - ) + console.log(require('./a/test.svg')) + console.log(require('./b/test.svg')) `, - "/a/test.txt": `test`, - "/b/test.txt": `test`, + "/a/test.svg": `<svg></svg>`, + "/b/test.svg": `<svg></svg>`, }, - outfile: "/dist/out.js", - }); - itBundled("loader/JSXSyntaxInJSWithJSXLoader", { - // GENERATED - files: { - "/entry.js": `console.log(<div/>)`, + loader: { + ".svg": "file", }, - }); - itBundled("loader/JSXPreserveCapitalLetter", { - // GENERATED - files: { - "/entry.jsx": /* jsx */ ` - import { mustStartWithUpperCaseLetter as Test } from './foo' - console.log(<Test/>) - `, - "/foo.js": `export class mustStartWithUpperCaseLetter {}`, + outdir: "/out", + run: { + stdout: /\.\/test-.*\.svg\n\.\/test-.*\.svg/, }, }); - itBundled("loader/JSXPreserveCapitalLetterMinify", { + itBundled("loader/FileMultipleNoCollisionAssetNames", { files: { - "/entry.jsx": /* jsx */ ` - import { mustStartWithUpperCaseLetter as XYYYY } from './foo' - // This should be named "Y" due to frequency analysis - console.log(<XYYYY YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY />) + "/entry.js": /* js */ ` + console.log(require('./a/test.svg')) + console.log(require('./b/test.svg')) `, - "/foo.js": `export class mustStartWithUpperCaseLetter {}`, + "/a/test.svg": `<svg></svg>`, + "/b/test.svg": `<svg></svg>`, }, - external: ["react"], - minifyIdentifiers: true, - }); - itBundled("loader/JSXPreserveCapitalLetterMinifyNested", { - files: { - "/entry.jsx": /* jsx */ ` - x = () => { - class RENAME_ME {} // This should be named "Y" due to frequency analysis - capture(RENAME_ME) - return <RENAME_ME YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY /> - } - `, + outdir: "/out", + assetNaming: "assets/[name]-[hash].[ext]", + loader: { + ".svg": "file", + }, + run: { + stdout: /\.\/test-.*\.svg \.\/test-.*\.svg/, }, - external: ["react"], - minifyIdentifiers: true, }); + itBundled("loader/JSXSyntaxInJSWithJSXLoader", { + files: { + "/entry.cjs": `console.log(<div/>)`, + }, + loader: { + ".cjs": "jsx", + }, + external: ["*"], + }); + // itBundled("loader/JSXPreserveCapitalLetter", { + // // GENERATED + // files: { + // "/entry.jsx": /* jsx */ ` + // import { mustStartWithUpperCaseLetter as Test } from './foo' + // console.log(<Test/>) + // `, + // "/foo.js": `export class mustStartWithUpperCaseLetter {}`, + // }, + // }); + // itBundled("loader/JSXPreserveCapitalLetterMinify", { + // files: { + // "/entry.jsx": /* jsx */ ` + // import { mustStartWithUpperCaseLetter as XYYYY } from './foo' + // // 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", { + // files: { + // "/entry.jsx": /* jsx */ ` + // x = () => { + // 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", { - // GENERATED files: { "/entry.js": `console.log(require('./test.custom'))`, "/test.custom": `#include <stdio.h>`, }, + loader: { + ".custom": "text", + }, + run: { + stdout: "#include <stdio.h>", + }, }); itBundled("loader/RequireCustomExtensionBase64", { - // GENERATED files: { "/entry.js": `console.log(require('./test.custom'))`, "/test.custom": `a\x00b\x80c\xFFd`, }, + loader: { + ".custom": "base64", + }, + run: { + stdout: "YQBiwoBjw79k", + }, }); itBundled("loader/RequireCustomExtensionDataURL", { - // GENERATED files: { "/entry.js": `console.log(require('./test.custom'))`, "/test.custom": `a\x00b\x80c\xFFd`, }, + loader: { + ".custom": "dataurl", + }, + run: { + stdout: "data:application/octet-stream,a\x00b\x80c\xFFd", + }, }); itBundled("loader/RequireCustomExtensionPreferLongest", { - // GENERATED files: { "/entry.js": `console.log(require('./test.txt'), require('./test.base64.txt'))`, "/test.txt": `test.txt`, "/test.base64.txt": `test.base64.txt`, }, + loader: { + ".txt": "text", + ".base64.txt": "base64", + }, + run: { + stdout: "test.txt dGVzdC5iYXNlNjQudHh0", + }, }); itBundled("loader/AutoDetectMimeTypeFromExtension", { - // GENERATED files: { "/entry.js": `console.log(require('./test.svg'))`, "/test.svg": `a\x00b\x80c\xFFd`, }, + loader: { + ".svg": "dataurl", + }, + run: { + stdout: "data:image/svg+xml,a\x00b\x80c\xFFd", + }, }); - itBundled("loader/LoaderJSONInvalidIdentifierES6", { - // GENERATED + itBundled("loader/JSONInvalidIdentifierES6", { files: { "/entry.js": /* js */ ` import * as ns from './test.json' import * as ns2 from './test2.json' - console.log(ns['invalid-identifier'], ns2) + console.log(ns['invalid-identifier'], JSON.stringify(ns2)) `, "/test.json": `{"invalid-identifier": true}`, "/test2.json": `{"invalid-identifier": true}`, }, + run: { + stdout: 'true {"invalid-identifier":true}', + }, }); - itBundled("loader/LoaderJSONMissingES6", { - // GENERATED + itBundled("loader/JSONMissingES6", { files: { "/entry.js": `import {missing} from './test.json'`, "/test.json": `{"present": true}`, }, - /* TODO FIX expectedCompileLog: `entry.js: ERROR: No matching export in "test.json" for import "missing" - `, */ + bundleErrors: { + "/entry.js": [`No matching export in "test.json" for import "missing"`], + }, }); - itBundled("loader/LoaderTextCommonJSAndES6", { - // GENERATED + itBundled("loader/TextCommonJSAndES6", { files: { "/entry.js": /* js */ ` const x_txt = require('./x.txt') @@ -194,9 +248,11 @@ describe("bundler", () => { "/x.txt": `x`, "/y.txt": `y`, }, + run: { + stdout: "x y", + }, }); - itBundled("loader/LoaderBase64CommonJSAndES6", { - // GENERATED + itBundled("loader/Base64CommonJSAndES6", { files: { "/entry.js": /* js */ ` const x_b64 = require('./x.b64') @@ -206,9 +262,14 @@ describe("bundler", () => { "/x.b64": `x`, "/y.b64": `y`, }, + loader: { + ".b64": "base64", + }, + run: { + stdout: "eA== eQ==", + }, }); - itBundled("loader/LoaderDataURLCommonJSAndES6", { - // GENERATED + itBundled("loader/DataURLCommonJSAndES6", { files: { "/entry.js": /* js */ ` const x_url = require('./x.txt') @@ -218,9 +279,14 @@ describe("bundler", () => { "/x.txt": `x`, "/y.txt": `y`, }, + loader: { + ".txt": "dataurl", + }, + run: { + stdout: "data:text/plain;charset=utf-8,x data:text/plain;charset=utf-8,y", + }, }); - itBundled("loader/LoaderFileCommonJSAndES6", { - // GENERATED + itBundled("loader/FileCommonJSAndES6", { files: { "/entry.js": /* js */ ` const x_url = require('./x.txt') @@ -231,8 +297,7 @@ describe("bundler", () => { "/y.txt": `y`, }, }); - itBundled("loader/LoaderFileRelativePathJS", { - // GENERATED + itBundled("loader/FileRelativePathJS", { files: { "/src/entries/entry.js": /* js */ ` import x from '../images/image.png' @@ -241,20 +306,28 @@ describe("bundler", () => { "/src/images/image.png": `x`, }, outbase: "/src", - }); - itBundled("loader/LoaderFileRelativePathCSS", { - // GENERATED - files: { - "/src/entries/entry.css": /* css */ ` - div { - background: url(../images/image.png); - } - `, - "/src/images/image.png": `x`, + outdir: "/out", + outputPaths: ["/out/entries/entry.js"], + loader: { + ".png": "file", + }, + run: { + stdout: /^..\/image-.*\.png$/, }, - outbase: "/src", }); - itBundled("loader/LoaderFileRelativePathAssetNamesJS", { + // itBundled("loader/FileRelativePathCSS", { + // // GENERATED + // files: { + // "/src/entries/entry.css": /* css */ ` + // div { + // background: url(../images/image.png); + // } + // `, + // "/src/images/image.png": `x`, + // }, + // outbase: "/src", + // }); + itBundled("loader/FileRelativePathAssetNamesJS", { // GENERATED files: { "/src/entries/entry.js": /* js */ ` @@ -264,9 +337,17 @@ describe("bundler", () => { "/src/images/image.png": `x`, }, outbase: "/src", - assetNames: "[dir]/[name]-[hash]", + assetNaming: "[dir]/[name]-[hash]", + outdir: "/out", + outputPaths: ["/out/entries/entry.js"], + loader: { + ".png": "file", + }, + run: { + stdout: /^..\/images\/image-.*\.png$/, + }, }); - itBundled("loader/LoaderFileExtPathAssetNamesJS", { + itBundled("loader/FileExtPathAssetNamesJS", { // GENERATED files: { "/src/entries/entry.js": /* js */ ` @@ -278,9 +359,9 @@ describe("bundler", () => { "/src/uploads/file.txt": `y`, }, outbase: "/src", - assetNames: "[ext]/[name]-[hash]", + assetNaming: "[ext]/[name]-[hash]", }); - itBundled("loader/LoaderFileRelativePathAssetNamesCSS", { + itBundled("loader/FileRelativePathAssetNamesCSS", { // GENERATED files: { "/src/entries/entry.css": /* css */ ` @@ -291,9 +372,9 @@ describe("bundler", () => { "/src/images/image.png": `x`, }, outbase: "/src", - assetNames: "[dir]/[name]-[hash]", + assetNaming: "[dir]/[name]-[hash]", }); - itBundled("loader/LoaderFilePublicPathJS", { + itBundled("loader/FilePublicPathJS", { // GENERATED files: { "/src/entries/entry.js": /* js */ ` @@ -305,7 +386,7 @@ describe("bundler", () => { outbase: "/src", publicPath: "https://example.com", }); - itBundled("loader/LoaderFilePublicPathCSS", { + itBundled("loader/FilePublicPathCSS", { // GENERATED files: { "/src/entries/entry.css": /* css */ ` @@ -318,7 +399,7 @@ describe("bundler", () => { outbase: "/src", publicPath: "https://example.com", }); - itBundled("loader/LoaderFilePublicPathAssetNamesJS", { + itBundled("loader/FilePublicPathAssetNamesJS", { // GENERATED files: { "/src/entries/entry.js": /* js */ ` @@ -329,9 +410,9 @@ describe("bundler", () => { }, outbase: "/src", publicPath: "https://example.com", - assetNames: "[dir]/[name]-[hash]", + assetNaming: "[dir]/[name]-[hash]", }); - itBundled("loader/LoaderFilePublicPathAssetNamesCSS", { + itBundled("loader/FilePublicPathAssetNamesCSS", { // GENERATED files: { "/src/entries/entry.css": /* css */ ` @@ -343,9 +424,9 @@ describe("bundler", () => { }, outbase: "/src", publicPath: "https://example.com", - assetNames: "[dir]/[name]-[hash]", + assetNaming: "[dir]/[name]-[hash]", }); - itBundled("loader/LoaderFileOneSourceTwoDifferentOutputPathsJS", { + itBundled("loader/FileOneSourceTwoDifferentOutputPathsJS", { // GENERATED files: { "/src/entries/entry.js": `import '../shared/common.js'`, @@ -359,7 +440,7 @@ describe("bundler", () => { entryPoints: ["/src/entries/entry.js", "/src/entries/other/entry.js"], outbase: "/src", }); - itBundled("loader/LoaderFileOneSourceTwoDifferentOutputPathsCSS", { + itBundled("loader/FileOneSourceTwoDifferentOutputPathsCSS", { // GENERATED files: { "/src/entries/entry.css": `@import "../shared/common.css";`, @@ -374,14 +455,14 @@ describe("bundler", () => { entryPoints: ["/src/entries/entry.css", "/src/entries/other/entry.css"], outbase: "/src", }); - itBundled("loader/LoaderJSONNoBundle", { + itBundled("loader/JSONNoBundle", { // GENERATED files: { "/test.json": `{"test": 123, "invalid-identifier": true}`, }, mode: "transform", }); - itBundled("loader/LoaderJSONNoBundleES6", { + itBundled("loader/JSONNoBundleES6", { // GENERATED files: { "/test.json": `{"test": 123, "invalid-identifier": true}`, @@ -390,7 +471,7 @@ describe("bundler", () => { unsupportedJSFeatures: "ArbitraryModuleNamespaceNames", mode: "convertformat", }); - itBundled("loader/LoaderJSONNoBundleES6ArbitraryModuleNamespaceNames", { + itBundled("loader/JSONNoBundleES6ArbitraryModuleNamespaceNames", { // GENERATED files: { "/test.json": `{"test": 123, "invalid-identifier": true}`, @@ -398,7 +479,7 @@ describe("bundler", () => { format: "esm", mode: "convertformat", }); - itBundled("loader/LoaderJSONNoBundleCommonJS", { + itBundled("loader/JSONNoBundleCommonJS", { // GENERATED files: { "/test.json": `{"test": 123, "invalid-identifier": true}`, @@ -406,7 +487,7 @@ describe("bundler", () => { format: "cjs", mode: "convertformat", }); - itBundled("loader/LoaderJSONNoBundleIIFE", { + itBundled("loader/JSONNoBundleIIFE", { // GENERATED files: { "/test.json": `{"test": 123, "invalid-identifier": true}`, @@ -414,7 +495,7 @@ describe("bundler", () => { format: "iife", mode: "convertformat", }); - itBundled("loader/LoaderFileWithQueryParameter", { + itBundled("loader/FileWithQueryParameter", { // GENERATED files: { "/entry.js": /* js */ ` @@ -426,7 +507,7 @@ describe("bundler", () => { "/file.txt": `This is some text`, }, }); - itBundled("loader/LoaderFromExtensionWithQueryParameter", { + itBundled("loader/FromExtensionWithQueryParameter", { // GENERATED files: { "/entry.js": /* js */ ` @@ -436,7 +517,7 @@ describe("bundler", () => { "/file.abc": `This should not be base64 encoded`, }, }); - itBundled("loader/LoaderDataURLTextCSS", { + itBundled("loader/DataURLTextCSS", { // GENERATED files: { "/entry.css": /* css */ ` @@ -447,7 +528,7 @@ describe("bundler", () => { `, }, }); - itBundled("loader/LoaderDataURLTextCSSCannotImport", { + itBundled("loader/DataURLTextCSSCannotImport", { // GENERATED files: { "/entry.css": `@import "data:text/css,@import './other.css';";`, @@ -456,7 +537,7 @@ describe("bundler", () => { /* TODO FIX expectedScanLog: `<data:text/css,@import './other.css';>: ERROR: Could not resolve "./other.css" `, */ }); - itBundled("loader/LoaderDataURLTextJavaScript", { + itBundled("loader/DataURLTextJavaScript", { // GENERATED files: { "/entry.js": /* js */ ` @@ -467,7 +548,7 @@ describe("bundler", () => { `, }, }); - itBundled("loader/LoaderDataURLTextJavaScriptCannotImport", { + itBundled("loader/DataURLTextJavaScriptCannotImport", { // GENERATED files: { "/entry.js": `import "data:text/javascript,import './other.js'"`, @@ -476,13 +557,13 @@ describe("bundler", () => { /* TODO FIX expectedScanLog: `<data:text/javascript,import './other.js'>: ERROR: Could not resolve "./other.js" `, */ }); - itBundled("loader/LoaderDataURLTextJavaScriptPlusCharacter", { + itBundled("loader/DataURLTextJavaScriptPlusCharacter", { // GENERATED files: { "/entry.js": `import "data:text/javascript,console.log(1+2)";`, }, }); - itBundled("loader/LoaderDataURLApplicationJSON", { + itBundled("loader/DataURLApplicationJSON", { // GENERATED files: { "/entry.js": /* js */ ` @@ -496,7 +577,7 @@ describe("bundler", () => { `, }, }); - itBundled("loader/LoaderDataURLUnknownMIME", { + itBundled("loader/DataURLUnknownMIME", { // GENERATED files: { "/entry.js": /* js */ ` @@ -506,7 +587,7 @@ describe("bundler", () => { `, }, }); - itBundled("loader/LoaderDataURLExtensionBasedMIME", { + itBundled("loader/DataURLExtensionBasedMIME", { // GENERATED files: { "/entry.foo": /* foo */ ` @@ -555,7 +636,7 @@ describe("bundler", () => { "/example.xml": `xml`, }, }); - itBundled("loader/LoaderDataURLBase64VsPercentEncoding", { + itBundled("loader/DataURLBase64VsPercentEncoding", { // GENERATED files: { "/entry.js": /* js */ ` @@ -576,7 +657,7 @@ describe("bundler", () => { "/shouldUseBase64_2.txt": `\n\n\n\n\n\n`, }, }); - itBundled("loader/LoaderDataURLBase64InvalidUTF8", { + itBundled("loader/DataURLBase64InvalidUTF8", { // GENERATED files: { "/entry.js": /* js */ ` @@ -586,7 +667,7 @@ describe("bundler", () => { "/binary.txt": `\xFF`, }, }); - itBundled("loader/LoaderDataURLEscapePercents", { + itBundled("loader/DataURLEscapePercents", { // GENERATED files: { "/entry.js": /* js */ ` @@ -600,7 +681,7 @@ describe("bundler", () => { `, }, }); - itBundled("loader/LoaderCopyWithBundleFromJS", { + itBundled("loader/CopyWithBundleFromJS", { // GENERATED files: { "/Users/user/project/src/entry.js": /* js */ ` @@ -611,7 +692,7 @@ describe("bundler", () => { }, outbase: "/Users/user/project", }); - itBundled("loader/LoaderCopyWithBundleFromCSS", { + itBundled("loader/CopyWithBundleFromCSS", { // GENERATED files: { "/Users/user/project/src/entry.css": /* css */ ` @@ -623,7 +704,7 @@ describe("bundler", () => { }, outbase: "/Users/user/project", }); - itBundled("loader/LoaderCopyWithBundleEntryPoint", { + itBundled("loader/CopyWithBundleEntryPoint", { // GENERATED files: { "/Users/user/project/src/entry.js": /* js */ ` @@ -644,7 +725,7 @@ describe("bundler", () => { ], outbase: "/Users/user/project", }); - itBundled("loader/LoaderCopyWithTransform", { + itBundled("loader/CopyWithTransform", { // GENERATED files: { "/Users/user/project/src/entry.js": `console.log('entry')`, @@ -654,7 +735,7 @@ describe("bundler", () => { outbase: "/Users/user/project", mode: "passthrough", }); - itBundled("loader/LoaderCopyWithFormat", { + itBundled("loader/CopyWithFormat", { // GENERATED files: { "/Users/user/project/src/entry.js": `console.log('entry')`, @@ -734,7 +815,7 @@ describe("bundler", () => { "/what": `.foo { color: red }`, }, }); - itBundled("loader/LoaderCopyEntryPointAdvanced", { + itBundled("loader/CopyEntryPointAdvanced", { // GENERATED files: { "/project/entry.js": /* js */ ` @@ -757,20 +838,20 @@ describe("bundler", () => { }, }, */ }); - itBundled("loader/LoaderCopyUseIndex", { + itBundled("loader/CopyUseIndex", { // GENERATED files: { "/Users/user/project/src/index.copy": `some stuff`, }, }); - itBundled("loader/LoaderCopyExplicitOutputFile", { + itBundled("loader/CopyExplicitOutputFile", { // GENERATED files: { "/project/TEST FAILED.copy": `some stuff`, }, outfile: "/out/this.worked", }); - itBundled("loader/LoaderCopyStartsWithDotAbsPath", { + itBundled("loader/CopyStartsWithDotAbsPath", { // GENERATED files: { "/project/src/.htaccess": `some stuff`, @@ -779,7 +860,7 @@ describe("bundler", () => { }, entryPoints: ["/project/src/.htaccess", "/project/src/entry.js", "/project/src/.ts"], }); - itBundled("loader/LoaderCopyStartsWithDotRelPath", { + itBundled("loader/CopyStartsWithDotRelPath", { // GENERATED files: { "/project/src/.htaccess": `some stuff`, diff --git a/test/bundler/esbuild/packagejson.test.ts b/test/bundler/esbuild/packagejson.test.ts index c3b241561..f1bb556a2 100644 --- a/test/bundler/esbuild/packagejson.test.ts +++ b/test/bundler/esbuild/packagejson.test.ts @@ -417,7 +417,7 @@ describe("bundler", () => { } `, }, - platform: "browser", + target: "browser", run: { stdout: "345", }, @@ -451,7 +451,7 @@ describe("bundler", () => { } `, }, - platform: "node", + target: "node", run: { stdout: "123", }, @@ -493,7 +493,7 @@ describe("bundler", () => { } `, }, - platform: "browser", + target: "browser", run: { stdout: "456", }, @@ -535,7 +535,7 @@ describe("bundler", () => { } `, }, - platform: "node", + target: "node", run: { stdout: "123", }, @@ -957,65 +957,6 @@ describe("bundler", () => { stdout: "b", }, }); - itBundled("packagejson/NeutralNoDefaultMainFields", { - notImplemented: true, - files: { - "/Users/user/project/src/entry.js": /* js */ ` - import fn from 'demo-pkg' - console.log(fn()) - `, - "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` - { - "main": "./main.js", - "module": "./main.esm.js" - } - `, - "/Users/user/project/node_modules/demo-pkg/main.js": /* js */ ` - module.exports = function() { - return 123 - } - `, - "/Users/user/project/node_modules/demo-pkg/main.esm.js": /* js */ ` - export default function() { - return 123 - } - `, - }, - platform: "neutral", - /* TODO FIX expectedScanLog: `Users/user/project/src/entry.js: ERROR: Could not resolve "demo-pkg" - Users/user/project/node_modules/demo-pkg/package.json: NOTE: The "main" field here was ignored. Main fields must be configured explicitly when using the "neutral" platform. - NOTE: You can mark the path "demo-pkg" as external to exclude it from the bundle, which will remove this error. - `, */ - }); - itBundled("packagejson/NeutralExplicitMainFields", { - files: { - "/Users/user/project/src/entry.js": /* js */ ` - import fn from 'demo-pkg' - console.log(fn()) - `, - "/Users/user/project/node_modules/demo-pkg/package.json": /* json */ ` - { - "hello": "./main.js", - "module": "./main.esm.js" - } - `, - "/Users/user/project/node_modules/demo-pkg/main.js": /* js */ ` - module.exports = function() { - return 123 - } - `, - "/Users/user/project/node_modules/demo-pkg/main.esm.js": /* js */ ` - export default function() { - return 234 - } - `, - }, - platform: "neutral", - mainFields: ["hello"], - run: { - stdout: "123", - }, - }); itBundled("packagejson/ExportsErrorInvalidModuleSpecifier", { files: { "/Users/user/project/src/entry.js": /* js */ ` @@ -1264,7 +1205,7 @@ describe("bundler", () => { "/Users/user/project/node_modules/pkg/node.js": `console.log('FAILURE')`, "/Users/user/project/node_modules/pkg/browser.js": `console.log('SUCCESS')`, }, - platform: "browser", + target: "browser", outfile: "/Users/user/project/out.js", run: { stdout: "SUCCESS", @@ -1286,30 +1227,8 @@ describe("bundler", () => { "/Users/user/project/node_modules/pkg/browser.js": `console.log('FAILURE')`, "/Users/user/project/node_modules/pkg/node.js": `console.log('SUCCESS')`, }, - platform: "node", - outfile: "/Users/user/project/out.js", - run: { - stdout: "SUCCESS", - }, - }); - itBundled("packagejson/ExportsNeutral", { - files: { - "/Users/user/project/src/entry.js": `import 'pkg'`, - "/Users/user/project/node_modules/pkg/package.json": /* json */ ` - { - "exports": { - "node": "./node.js", - "browser": "./browser.js", - "default": "./default.js" - } - } - `, - "/Users/user/project/node_modules/pkg/node.js": `console.log('FAILURE')`, - "/Users/user/project/node_modules/pkg/browser.js": `console.log('FAILURE')`, - "/Users/user/project/node_modules/pkg/default.js": `console.log('SUCCESS')`, - }, + target: "node", outfile: "/Users/user/project/out.js", - platform: "neutral", run: { stdout: "SUCCESS", }, diff --git a/test/bundler/esbuild/splitting.test.ts b/test/bundler/esbuild/splitting.test.ts index 361b51ef6..c10a1a36f 100644 --- a/test/bundler/esbuild/splitting.test.ts +++ b/test/bundler/esbuild/splitting.test.ts @@ -549,7 +549,7 @@ describe("bundler", () => { }, outdir: "/out", splitting: true, - chunkNames: "[dir]/[name]-[hash].[ext]", + chunkNaming: "[dir]/[name]-[hash].[ext]", onAfterBundle(api) { assert( readdirSync(api.outdir + "/output-path/should-contain/this-text").length === 1, @@ -569,7 +569,7 @@ describe("bundler", () => { outdir: "/out", entryPoints: ["/src/index.js"], splitting: true, - platform: "browser", + target: "browser", runtimeFiles: { "/test.js": /* js */ ` import { A, B } from './out/index.js' diff --git a/test/bundler/expectBundled.ts b/test/bundler/expectBundled.ts index e029787c8..f6ad423f9 100644 --- a/test/bundler/expectBundled.ts +++ b/test/bundler/expectBundled.ts @@ -75,13 +75,13 @@ export interface BundlerTestInput { // bundler options alias?: Record<string, string>; - assetNames?: string; + assetNaming?: string; banner?: string; define?: Record<string, string | number>; /** Default is "[name].[ext]" */ - entryNames?: string; + entryNaming?: string; /** Default is "[name]-[hash].[ext]" */ - chunkNames?: string; + chunkNaming?: string; extensionOrder?: string[]; /** Replaces "{{root}}" with the file root */ external?: string[]; @@ -91,11 +91,10 @@ export interface BundlerTestInput { ignoreDCEAnnotations?: boolean; inject?: string[]; jsx?: { - factory?: string; - fragment?: string; - automaticRuntime?: boolean; - development?: boolean; - preserve?: boolean; + runtime?: "automatic" | "classic"; + importSource?: string; // for automatic + factory?: string; // for classic + fragment?: string; // for classic }; outbase?: string; /** Defaults to `/out.js` */ @@ -103,7 +102,7 @@ export interface BundlerTestInput { /** Defaults to `/out` */ outdir?: string; /** Defaults to "browser". "bun" is set to "node" when using esbuild. */ - platform?: "bun" | "node" | "neutral" | "browser"; + target?: "bun" | "node" | "browser"; publicPath?: string; keepNames?: boolean; legalComments?: "none" | "inline" | "eof" | "linked" | "external"; @@ -215,7 +214,7 @@ export interface BundlerTestRunOptions { /** Pass args to bun itself (before the filename) */ bunArgs?: string[]; /** match exact stdout */ - stdout?: string; + stdout?: string | RegExp; /** partial match stdout (toContain()) */ partialStdout?: string; /** match exact error message, example "ReferenceError: Can't find variable: bar" */ @@ -253,21 +252,23 @@ function expectBundled( ignoreFilter = false, ): Promise<BundlerTestRef> | BundlerTestRef { var { expect, it, test } = testForFile(callerSourceOrigin()); - if (!ignoreFilter && FILTER && id !== FILTER) return testRef(id, opts); + if (!ignoreFilter && FILTER && !filterMatches(id)) return testRef(id, opts); let { + notImplemented, bundleWarnings, bundleErrors, banner, backend, assertNotPresent, capture, - chunkNames, + assetNaming, + chunkNaming, cjs2esm, dce, dceKeepMarkerCount, define, - entryNames, + entryNaming, entryPoints, entryPointsRaw, env, @@ -292,7 +293,7 @@ function expectBundled( outdir, outfile, outputPaths, - platform, + target, plugins, publicPath, run, @@ -321,7 +322,7 @@ function expectBundled( // Resolve defaults for options and some related things mode ??= "bundle"; - platform ??= "browser"; + target ??= "browser"; format ??= "esm"; entryPoints ??= entryPointsRaw ? [] : [Object.keys(files)[0]]; if (run === true) run = {}; @@ -333,9 +334,6 @@ function expectBundled( if (!ESBUILD && format !== "esm") { throw new Error("formats besides esm not implemented in bun build"); } - if (!ESBUILD && platform === "neutral") { - throw new Error("platform=neutral not implemented in bun build"); - } if (!ESBUILD && metafile) { throw new Error("metafile not implemented in bun build"); } @@ -357,9 +355,6 @@ function expectBundled( if (!ESBUILD && mainFields) { throw new Error("mainFields not implemented in bun build"); } - if (!ESBUILD && loader) { - throw new Error("loader not implemented in bun build"); - } if (!ESBUILD && sourceMap) { throw new Error("sourceMap not implemented in bun build"); } @@ -369,11 +364,13 @@ 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 && loader) { + const loaderValues = [...new Set(Object.values(loader))]; + const supportedLoaderTypes = ["js", "jsx", "ts", "tsx", "css", "json", "text", "file"]; + const unsupportedLoaderTypes = loaderValues.filter(x => !supportedLoaderTypes.includes(x)); + if (unsupportedLoaderTypes.length) { + throw new Error(`loader '${unsupportedLoaderTypes.join("', '")}' not implemented in bun build`); + } } if (ESBUILD && skipOnEsbuild) { return testRef(id, opts); @@ -413,8 +410,8 @@ function expectBundled( } if (outdir) { - entryNames ??= "[name].[ext]"; - chunkNames ??= "[name]-[hash].[ext]"; + entryNaming ??= "[name].[ext]"; + chunkNaming ??= "[name]-[hash].[ext]"; } // Option validation @@ -469,31 +466,32 @@ function expectBundled( ...(entryPointsRaw ?? []), mode === "bundle" ? [outfile ? `--outfile=${outfile}` : `--outdir=${outdir}`] : [], define && Object.entries(define).map(([k, v]) => ["--define", `${k}=${v}`]), - `--platform=${platform}`, + `--target=${target}`, external && external.map(x => ["--external", x]), minifyIdentifiers && `--minify-identifiers`, minifySyntax && `--minify-syntax`, minifyWhitespace && `--minify-whitespace`, globalName && `--global-name=${globalName}`, // inject && inject.map(x => ["--inject", path.join(root, x)]), - jsx.preserve && "--jsx=preserve", - jsx.automaticRuntime === false && "--jsx=classic", - jsx.factory && `--jsx-factory=${jsx.factory}`, - jsx.fragment && `--jsx-fragment=${jsx.fragment}`, - jsx.development === false && `--jsx-production`, - // metafile && `--metafile=${metafile}`, + // jsx.preserve && "--jsx=preserve", + jsx.runtime && ["--jsx-runtime", jsx.runtime], + jsx.factory && ["--jsx-factory", jsx.factory], + jsx.fragment && ["--jsx-fragment", jsx.fragment], + jsx.importSource && ["--jsx-import-source", jsx.importSource], + // metafile && `--manifest=${metafile}`, // sourceMap && `--sourcemap${sourceMap !== true ? `=${sourceMap}` : ""}`, - entryNames && entryNames !== "[name].[ext]" && [`--entry-names`, entryNames], - // chunkNames && chunkNames !== "[name]-[hash].[ext]" && [`--chunk-names`, chunkNames], + entryNaming && entryNaming !== "[name].[ext]" && [`--entry-naming`, entryNaming], + chunkNaming && chunkNaming !== "[name]-[hash].[ext]" && [`--chunk-naming`, chunkNaming], + assetNaming && assetNaming !== "[name]-[hash].[ext]" && [`--asset-naming`, chunkNaming], // `--format=${format}`, // legalComments && `--legal-comments=${legalComments}`, splitting && `--splitting`, - // treeShaking && `--tree-shaking`, + // treeShaking === false && `--no-tree-shaking`, // ?? // outbase && `--outbase=${outbase}`, // keepNames && `--keep-names`, // mainFields && `--main-fields=${mainFields}`, - // loader && Object.entries(loader).map(([k, v]) => ["--loader", `${k}=${v}`]), - // publicPath && `--public-path=${publicPath}`, + loader && Object.entries(loader).map(([k, v]) => ["--loader", `${k}:${v}`]), + publicPath && `--public-path=${publicPath}`, mode === "transform" && "--transform", ] : [ @@ -501,7 +499,7 @@ function expectBundled( mode === "bundle" && "--bundle", outfile ? `--outfile=${outfile}` : `--outdir=${outdir}`, `--format=${format}`, - `--platform=${platform === "bun" ? "node" : platform}`, + `--platform=${target === "bun" ? "node" : target}`, minifyIdentifiers && `--minify-identifiers`, minifySyntax && `--minify-syntax`, minifyWhitespace && `--minify-whitespace`, @@ -509,15 +507,18 @@ function expectBundled( external && external.map(x => `--external:${x}`), inject && inject.map(x => `--inject:${path.join(root, x)}`), define && Object.entries(define).map(([k, v]) => `--define:${k}=${v}`), - jsx.automaticRuntime && "--jsx=automatic", - jsx.preserve && "--jsx=preserve", + `--jsx=${jsx.runtime ?? "automatic"}`, + // jsx.preserve && "--jsx=preserve", jsx.factory && `--jsx-factory=${jsx.factory}`, 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]$/, "")}`, + env?.NODE_ENV !== "production" && `--jsx-dev`, + entryNaming && entryNaming !== "[name].[ext]" && `--entry-names=${entryNaming.replace(/\.\[ext]$/, "")}`, + chunkNaming && + chunkNaming !== "[name]-[hash].[ext]" && + `--chunk-names=${chunkNaming.replace(/\.\[ext]$/, "")}`, + assetNaming && + assetNaming !== "[name]-[hash].[ext]" && + `--asset-names=${assetNaming.replace(/\.\[ext]$/, "")}`, metafile && `--metafile=${metafile}`, sourceMap && `--sourcemap${sourceMap !== true ? `=${sourceMap}` : ""}`, banner && `--banner:js=${banner}`, @@ -758,33 +759,35 @@ function expectBundled( syntax: minifySyntax, }, naming: { - entrypoint: useOutFile ? path.basename(outfile!) : entryNames, - chunk: chunkNames, + entry: useOutFile ? path.basename(outfile!) : entryNaming, + chunk: chunkNaming, + asset: assetNaming, }, plugins: pluginArray, treeShaking, outdir: buildOutDir, sourcemap: sourceMap === true ? "external" : sourceMap || "none", splitting, - target: platform === "neutral" ? "browser" : platform, + target, + publicPath, } as BuildConfig; if (DEBUG) { if (_referenceFn) { const x = _referenceFn.toString().replace(/^\s*expect\(.*$/gm, "// $&"); const debugFile = `import path from 'path'; - import assert from 'assert'; - const {plugins} = (${x})({ root: ${JSON.stringify(root)} }); - const options = ${JSON.stringify({ ...buildConfig, plugins: undefined }, null, 2)}; - options.plugins = typeof plugins === "function" ? [{ name: "plugin", setup: plugins }] : plugins; - const build = await Bun.build(options); - if (build.logs) { - throw build.logs; - } - for (const blob of build.outputs) { - await Bun.write(path.join(options.outdir, blob.path), blob.result); - } - `; +import assert from 'assert'; +const {plugins} = (${x})({ root: ${JSON.stringify(root)} }); +const options = ${JSON.stringify({ ...buildConfig, plugins: undefined }, null, 2)}; +options.plugins = typeof plugins === "function" ? [{ name: "plugin", setup: plugins }] : plugins; +const build = await Bun.build(options); +if (build.logs) { + throw build.logs; +} +for (const blob of build.outputs) { + await Bun.write(path.join(options.outdir, blob.path), blob.result); +} +`; writeFileSync(path.join(root, "run.js"), debugFile); } else { console.log("TODO: generate run.js, currently only works if options are wrapped in a function"); @@ -795,14 +798,12 @@ function expectBundled( const build = await Bun.build(buildConfig); Bun.gc(true); + console.log(build); + if (build.logs) { console.log(build.logs); throw new Error("TODO: handle build logs, but we should make this api nicer"); } - - for (const blob of build.outputs) { - await Bun.write(path.join(buildOutDir, blob.path), blob.result); - } } else { await esbuild.build({ bundle: true, @@ -896,7 +897,7 @@ function expectBundled( } } else { // entryNames makes it so we cannot predict the output file - if (!entryNames || entryNames === "[name].[ext]") { + if (!entryNaming || entryNaming === "[name].[ext]") { for (const fullpath of outputPaths) { if (!existsSync(fullpath)) { throw new Error("Bundle was not written to disk: " + fullpath); @@ -1027,7 +1028,7 @@ function expectBundled( if (file) { file = path.join(root, file); } else if (entryPaths.length === 1) { - file = outfile; + file = outfile ?? outputPaths[0]; } else { throw new Error(prefix + "run.file is required when there is more than one entrypoint."); } @@ -1042,6 +1043,7 @@ function expectBundled( env: { ...bunEnv, FORCE_COLOR: "0", + IS_TEST_RUNNER: "1", }, stdio: ["ignore", "pipe", "pipe"], }); @@ -1100,18 +1102,34 @@ function expectBundled( if (run.stdout !== undefined) { const result = stdout!.toString("utf-8").trim(); - const expected = dedent(run.stdout).trim(); - if (expected !== result) { - console.log({ file }); + if (typeof run.stdout === "string") { + const expected = dedent(run.stdout).trim(); + if (expected !== result) { + console.log(`runtime failed file=${file}`); + console.log(`reference stdout:`); + console.log(result); + console.log(`---`); + } + expect(result).toBe(expected); + } else { + if (!run.stdout.test(result)) { + console.log(`runtime failed file=${file}`); + console.log(`reference stdout:`); + console.log(result); + console.log(`---`); + } + expect(result).toMatch(run.stdout); } - expect(result).toBe(expected); } if (run.partialStdout !== undefined) { const result = stdout!.toString("utf-8").trim(); const expected = dedent(run.partialStdout).trim(); if (!result.includes(expected)) { - console.log({ file }); + console.log(`runtime failed file=${file}`); + console.log(`reference stdout:`); + console.log(result); + console.log(`---`); } expect(result).toContain(expected); } @@ -1137,7 +1155,7 @@ export function itBundled( const ref = testRef(id, opts); const { it } = testForFile(callerSourceOrigin()); - if (FILTER && id !== FILTER) { + if (FILTER && !filterMatches(id)) { return ref; } else if (!FILTER) { try { @@ -1165,7 +1183,7 @@ export function itBundled( return ref; } itBundled.skip = (id: string, opts: BundlerTestInput) => { - if (FILTER && id !== FILTER) { + if (FILTER && !filterMatches(id)) { return testRef(id, opts); } const { it } = testForFile(callerSourceOrigin()); @@ -1176,3 +1194,7 @@ itBundled.skip = (id: string, opts: BundlerTestInput) => { function formatError(err: { file: string; error: string; line?: string; col?: string }) { return `${err.file}${err.line ? " :" + err.line : ""}${err.col ? ":" + err.col : ""}: ${err.error}`; } + +function filterMatches(id: string) { + return FILTER === id || FILTER + "Dev" === id || FILTER + "Prod" === id; +} diff --git a/test/bundler/transpiler.test.js b/test/bundler/transpiler.test.js index 7af8f2d27..1fc599771 100644 --- a/test/bundler/transpiler.test.js +++ b/test/bundler/transpiler.test.js @@ -2969,4 +2969,12 @@ console.log(foo, array); expectPrinted_('const x = "``" + ``;', 'const x = "``"'); }); }); + + it("scan on empty file does not segfault", () => { + new Bun.Transpiler().scan(""); + }); + + it("scanImports on empty file does not segfault", () => { + new Bun.Transpiler().scanImports(""); + }); }); |