diff --git a/scripts/build/deps/webkit.ts b/scripts/build/deps/webkit.ts index 359f8a466e32..5d2eb3880690 100644 --- a/scripts/build/deps/webkit.ts +++ b/scripts/build/deps/webkit.ts @@ -3,7 +3,7 @@ * for local mode. Override via `--webkit-version=` to test a branch. * From https://github.com/oven-sh/WebKit releases. */ -export const WEBKIT_VERSION = "cf1b36ec8703d8e87436094d21d478d358c7d886"; +export const WEBKIT_VERSION = "autobuild-preview-pr-581-e075a38d"; /** * WebKit (JavaScriptCore) — the JS engine. diff --git a/test/bundler/bundler_compile_prelinked.test.ts b/test/bundler/bundler_compile_prelinked.test.ts index f33b6d0820c1..a8e6503921a9 100644 --- a/test/bundler/bundler_compile_prelinked.test.ts +++ b/test/bundler/bundler_compile_prelinked.test.ts @@ -550,4 +550,39 @@ describe.concurrent("bundler", () => { // Without splitting everything is the entry module itself, which require.cache does not list. stdout: splitting => (splitting ? "0\ntrue 1\nfresh 1" : "0\nfalse 1\nsingle 1"), }); + + // A re-export of the entry whose target is outside the bundle stays unresolved in the graph, so the loader resolves + // it by name when the entry links. When that fails, the error names the requested binding (not the alias it is + // re-exported under) and the file the external resolved to, the same in all three loader modes. + for (const [id, reexport, message] of [ + [ + "Default", + `export { default as x } from "./ext/p.mjs";`, + /^SyntaxError: Missing 'default' export in module '[^']*[\\/]ext[\\/]p\.mjs'\.$/m, + ], + [ + "Named", + `export { nope as y } from "./ext/p.mjs";`, + /^SyntaxError: Export named 'nope' not found in module '[^']*[\\/]ext[\\/]p\.mjs'\.$/m, + ], + ] as const) { + itBundled(`compile/prelinked/ReExportMissingFromExternal${id}`, { + compile: true, + bytecode: true, + format: "esm", + files: { + "/entry.mjs": reexport, + "/ext/p.mjs": `export const a = 1;`, + }, + external: ["./ext/p.mjs"], + run: eachMode(() => ({ + setCwd: true, // a relative external specifier resolves against the working directory + exitCode: 1, + stdout: "", + validate({ stderr }) { + expect(stderr).toMatch(message); + }, + })), + }); + } }); diff --git a/test/js/bun/typescript/type-export.test.ts b/test/js/bun/typescript/type-export.test.ts index 4ed9e0036638..59a7e355271e 100644 --- a/test/js/bun/typescript/type-export.test.ts +++ b/test/js/bun/typescript/type-export.test.ts @@ -228,7 +228,9 @@ test.concurrent("js file with through export", async () => { const result = await run([bunExe(), "b.js"], dir); - expect(result.stderr.trim()).toInclude("SyntaxError: export 'type_only' not found in './ts.ts'"); + expect(result.stderr.trim()).toMatch( + /^SyntaxError: Export named 'type_only' not found in module '[^']*[\\/]ts\.ts'\.$/m, + ); expect(result.exitCode).toBe(1); }); @@ -240,10 +242,82 @@ test.concurrent("js file with through export 2", async () => { const result = await run([bunExe(), "b.js"], dir); - expect(result.stderr.trim()).toInclude("SyntaxError: export 'type_only' not found in './ts.ts'"); + expect(result.stderr.trim()).toMatch( + /^SyntaxError: Export named 'type_only' not found in module '[^']*[\\/]ts\.ts'\.$/m, + ); expect(result.exitCode).toBe(1); }); +// The link-time check for `export { a as b } from "./m"` used to name the alias +// (`b`) and, for a missing `default`, print "export default cannot be used with +// export *". It now uses the same messages as `import { a } from "./m"`: the +// requested binding and the resolved path of the requested module. +describe("re-export of a binding the requested module does not provide", () => { + const cases: [name: string, files: Record, message: RegExp][] = [ + [ + "export { default } from", + { + "p.mjs": `export const a = 1;`, + "mid.mjs": `export { default } from "./p.mjs";`, + "main.mjs": `import d from "./mid.mjs"; console.log(d);`, + }, + /^SyntaxError: Missing 'default' export in module '[^']*[\\/]p\.mjs'\.$/m, + ], + [ + "export { default as x } from", + { + "p.mjs": `export const a = 1;`, + "mid.mjs": `export { default as x } from "./p.mjs";`, + "main.mjs": `import { x } from "./mid.mjs"; console.log(x);`, + }, + /^SyntaxError: Missing 'default' export in module '[^']*[\\/]p\.mjs'\.$/m, + ], + [ + "import d from; export { d as x }", + { + "p.mjs": `export const a = 1;`, + "mid.mjs": `import d from "./p.mjs"; export { d as x };`, + "main.mjs": `import { x } from "./mid.mjs"; console.log(x);`, + }, + /^SyntaxError: Missing 'default' export in module '[^']*[\\/]p\.mjs'\.$/m, + ], + [ + "export { nope as y } from", + { + "p.mjs": `export const a = 1;`, + "mid.mjs": `export { nope as y } from "./p.mjs";`, + "main.mjs": `import { y } from "./mid.mjs"; console.log(y);`, + }, + /^SyntaxError: Export named 'nope' not found in module '[^']*[\\/]p\.mjs'\.$/m, + ], + [ + "export { dup as z } from a module with conflicting export *", + { + "a.mjs": `export const dup = "a";`, + "b.mjs": `export const dup = "b";`, + "root.mjs": `export * from "./a.mjs"; export * from "./b.mjs";`, + "mid.mjs": `export { dup as z } from "./root.mjs";`, + "main.mjs": `import { z } from "./mid.mjs"; console.log(z);`, + }, + /^SyntaxError: Export named 'dup' cannot be resolved due to ambiguous multiple bindings in module '[^']*[\\/]root\.mjs'\.$/m, + ], + ]; + + describe.each(cases)("%s", (_name, files, message) => { + // The check runs when mid.mjs links, so it fires the same way whether + // mid.mjs is a dependency or the entry point. + test.concurrent.each(["main.mjs", "mid.mjs"])("entry %s", async entry => { + await using dir = tempDir("reexport-missing", files); + + const result = await run([bunExe(), entry], String(dir)); + + expect(result.stderr.trim()).toMatch(message); + expect(result.stdout).toBe(""); + expect(result.exitCode).toBe(1); + }); + }); +}); + describe("through export merge", () => { // this isn't allowed, even in typescript (tsc emits "Duplicate identifier 'value'.") for (const fmt of ["js", "ts"]) {