Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* for local mode. Override via `--webkit-version=<hash>` 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.
Expand Down
35 changes: 35 additions & 0 deletions test/bundler/bundler_compile_prelinked.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
},
})),
});
}
});
78 changes: 76 additions & 2 deletions test/js/bun/typescript/type-export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});

Expand All @@ -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<string, string>, 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"]) {
Expand Down
Loading