Compare commits

...

2 Commits

Author SHA1 Message Date
Claude Bot
3414629b88 Merge remote-tracking branch 'origin/main' into claude/fix-html-import-nested-routes-23431
# Conflicts:
#	src/bundler/linker_context/generateChunksInParallel.zig
#	src/bundler/linker_context/writeOutputFilesToDisk.zig
2026-02-18 00:16:48 +00:00
Claude Bot
a98097dc77 fix(bundler): use absolute paths for HTML asset references in compile mode (#23431)
When serving HTML imports via Bun.serve() at nested routes (e.g. /foo/bar),
JS/CSS chunks failed to load because the bundled HTML used relative paths
(./chunk-xyz.js) instead of absolute paths (/chunk-xyz.js). The browser
resolved relative paths against the current URL, causing requests to wrong
paths like /foo/chunk-xyz.js.

Fix: set the client transpiler's public_path to "/" in compile mode so that
all browser asset references in HTML use absolute paths from the server root.
Also remove the is_browser_chunk_from_server_build exclusion from the
force_absolute_path flag, since all chunks in compile mode should use
absolute paths.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-14 23:30:50 +00:00
4 changed files with 78 additions and 5 deletions

View File

@@ -210,9 +210,11 @@ pub const BundleV2 = struct {
client_transpiler.options.chunk_naming = bun.options.PathTemplate.chunk.data;
client_transpiler.options.entry_naming = "./[name]-[hash].[ext]";
// Avoid setting a public path for --compile since all the assets
// will be served relative to the server root.
client_transpiler.options.public_path = "";
// Use "/" as the public path for --compile since all the browser
// assets are served from the server root. This ensures HTML chunks
// reference JS/CSS with absolute paths (e.g. "/chunk-xyz.js") so
// they resolve correctly regardless of the current URL path.
client_transpiler.options.public_path = "/";
}
client_transpiler.setLog(this_transpiler.log);

View File

@@ -490,7 +490,7 @@ pub fn generateChunksInParallel(
chunk,
chunks,
&display_size,
c.resolver.opts.compile and !chunk.flags.is_browser_chunk_from_server_build,
c.resolver.opts.compile,
chunk.content.sourcemap(c.options.source_maps) != .none,
);
var code_result = _code_result catch |err| bun.handleOom(err);

View File

@@ -111,7 +111,7 @@ pub fn writeOutputFilesToDisk(
chunk,
chunks,
&display_size,
c.resolver.opts.compile and !chunk.flags.is_browser_chunk_from_server_build,
c.resolver.opts.compile,
chunk.content.sourcemap(c.options.source_maps) != .none,
) catch |err| bun.Output.panic("Failed to create output chunk: {s}", .{@errorName(err)});

View File

@@ -121,5 +121,76 @@ describe("bundler", () => {
stdout: "Home status: 200\nHome has content: true\nAbout status: 200\nAbout has content: true",
},
});
// Regression test for https://github.com/oven-sh/bun/issues/23431
// HTML imports at nested paths should use absolute paths for JS/CSS chunks
itBundled(`compile/${backend}/HTMLServerNestedRouteAbsolutePaths`, {
compile: true,
backend: backend,
files: {
"/entry.ts": /* js */ `
import index from "./index.html";
using server = Bun.serve({
port: 0,
routes: {
"/*": index,
},
fetch(req) {
return new Response("Not found", { status: 404 });
},
});
// Fetch the HTML served at a nested path
const res = await fetch(new URL("/foo/bar", server.url));
const html = await res.text();
// Verify the HTML contains script/link tags with absolute paths (starting with "/")
// not relative paths (starting with "./")
const scriptMatch = html.match(/src="([^"]+)"/);
const linkMatch = html.match(/href="([^"]+)"/);
const scriptSrc = scriptMatch?.[1] ?? "";
const linkHref = linkMatch?.[1] ?? "";
console.log("Script src starts with /:", scriptSrc.startsWith("/"));
console.log("Link href starts with /:", linkHref.startsWith("/"));
// Verify the JS chunk is actually fetchable at the absolute path
const jsRes = await fetch(new URL(scriptSrc, server.url));
console.log("JS chunk status:", jsRes.status);
console.log("JS chunk content-type:", jsRes.headers.get("content-type")?.includes("javascript"));
// Verify the CSS chunk is actually fetchable at the absolute path
const cssRes = await fetch(new URL(linkHref, server.url));
console.log("CSS chunk status:", cssRes.status);
console.log("CSS chunk content-type:", cssRes.headers.get("content-type")?.includes("css"));
`,
"/index.html": /* html */ `
<!DOCTYPE html>
<html>
<head>
<title>Test Page</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<h1>Hello</h1>
<script type="module" src="./client.ts" async></script>
</body>
</html>
`,
"/styles.css": /* css */ `
body {
background: blue;
}
`,
"/client.ts": /* js */ `
console.log("client code loaded");
`,
},
run: {
stdout:
"Script src starts with /: true\nLink href starts with /: true\nJS chunk status: 200\nJS chunk content-type: true\nCSS chunk status: 200\nCSS chunk content-type: true",
},
});
}
});