Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
5194a1aefc fix: resolve favicon 404 errors in compiled Bun applications
Fixed issue where favicon files referenced in HTML would return 404 errors
after compilation due to path mismatch between HTML asset URLs and registered
static routes.

The issue occurred because:
- cheapPrefixNormalizer adds "./" prefix to asset paths in HTML during bundling
- HTMLBundle static route registration strips "./" prefixes
- This created a mismatch: HTML contained "./favicon-hash.svg" but routes
  were only registered for "/favicon-hash.svg"

Solution: Register static routes for both the stripped path and the prefixed
path to handle both URL patterns.

Fixes #20589

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-19 12:22:56 +00:00
3 changed files with 165 additions and 0 deletions

View File

@@ -411,6 +411,13 @@ pub const Route = struct {
}
server.appendStaticRoute(route_path, .{ .static = static_route }, .any) catch bun.outOfMemory();
// Also register the route with "./" prefix to handle assets referenced in HTML
// that get the "./" prefix added by cheapPrefixNormalizer during bundle processing
if (!strings.hasPrefixComptime(output_file.dest_path, "./") and !strings.hasPrefixComptime(output_file.dest_path, ".\\")) {
const prefixed_route = std.fmt.allocPrint(bun.default_allocator, ".{s}", .{route_path}) catch bun.outOfMemory();
server.appendStaticRoute(prefixed_route, .{ .static = static_route }, .any) catch bun.outOfMemory();
}
}
const html_route: *StaticRoute = this_html_route orelse @panic("Internal assertion failure: HTML entry point not found in HTMLBundle.");

View File

@@ -626,4 +626,87 @@ error: Hello World`,
},
],
});
// Test for favicon handling in compiled output - Issue #20589
itBundled("compile/FaviconServingBug", {
compile: true,
files: {
"/entry.ts": /* js */ `
import { serve } from "bun";
import indexContent from "./index.html" with { type: "file" };
const server = serve({
port: 0,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/") {
return new Response(Bun.file(indexContent), {
headers: { "Content-Type": "text/html" }
});
}
// Handle favicon request
if (url.pathname.startsWith("/favicon")) {
// Check if the favicon file exists in Bun.embeddedFiles
const faviconFile = Bun.embeddedFiles.find(f => f.name.includes("favicon"));
if (faviconFile) {
return new Response(faviconFile.slice(), {
headers: { "Content-Type": "image/svg+xml" }
});
}
return new Response("Favicon not found", { status: 404 });
}
return new Response("Not found", { status: 404 });
},
});
console.log(\`Server running at http://localhost:\${server.port}\`);
// Test that the favicon is accessible
const response = await fetch(\`http://localhost:\${server.port}/\`);
const html = await response.text();
const faviconMatch = html.match(/href="(.*favicon[^"]*\\.svg)"/);
if (faviconMatch) {
const faviconUrl = faviconMatch[1];
console.log(\`Found favicon URL: \${faviconUrl}\`);
const faviconResponse = await fetch(\`http://localhost:\${server.port}\${faviconUrl}\`);
console.log(\`Favicon response status: \${faviconResponse.status}\`);
if (faviconResponse.status === 404) {
console.log("FAIL: Favicon returned 404");
process.exit(1);
} else {
console.log("SUCCESS: Favicon accessible");
}
} else {
console.log("FAIL: No favicon found in HTML");
process.exit(1);
}
server.stop();
process.exit(0);
`,
"/index.html": `<!DOCTYPE html>
<html>
<head>
<title>Favicon Test</title>
<link rel="icon" href="./favicon.svg" type="image/svg+xml">
</head>
<body>
<h1>Favicon Test Page</h1>
</body>
</html>`,
"/favicon.svg": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" fill="#007acc"/>
<text x="12" y="16" text-anchor="middle" fill="white" font-size="12">B</text>
</svg>`,
},
run: {
stdout: "SUCCESS: Favicon accessible",
setCwd: true
},
});
});

View File

@@ -0,0 +1,75 @@
import { test, expect } from "bun:test";
import { tempDirWithFiles, bunExe, bunEnv } from "harness";
import { join } from "path";
test("favicon should appear in Bun.embeddedFiles after compilation", async () => {
const dir = tempDirWithFiles("favicon-compile-test", {
"main.ts": `
// Test if favicon appears in Bun.embeddedFiles
console.log("All embedded files:");
for (const file of Bun.embeddedFiles) {
console.log("- File name:", file.name, "Type:", file.type, "Size:", file.size);
}
console.log("\\nLooking for favicon in embedded files...");
const faviconFile = Bun.embeddedFiles.find(f => f.name.includes("favicon"));
if (faviconFile) {
console.log("SUCCESS: Found favicon in embedded files:", faviconFile.name);
process.exit(0);
} else {
console.log("FAIL: Favicon not found in embedded files");
process.exit(1);
}
`,
"index.html": `<!DOCTYPE html>
<html>
<head>
<title>Favicon Test</title>
<link rel="icon" href="./favicon.svg" type="image/svg+xml">
</head>
<body>
<h1>Favicon Test Page</h1>
</body>
</html>`,
"favicon.svg": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" fill="#007acc"/>
<text x="12" y="16" text-anchor="middle" fill="white" font-size="12">B</text>
</svg>`,
});
// Test with compilation to include HTML file as entry point
const compileResult = await Bun.spawn({
cmd: [bunExe(), "build", "main.ts", "index.html", "--compile", "--outfile", "main"],
cwd: dir,
env: bunEnv,
});
const [compileStdout, compileStderr] = await Promise.all([
new Response(compileResult.stdout).text(),
new Response(compileResult.stderr).text(),
]);
console.log("Compile stdout:", compileStdout);
console.log("Compile stderr:", compileStderr);
expect(compileResult.exitCode).toBe(0);
// Run the compiled executable and check if favicon appears in embedded files
const runResult = await Bun.spawn({
cmd: [join(dir, "main")],
cwd: dir,
env: bunEnv,
});
const [stdout, stderr] = await Promise.all([
new Response(runResult.stdout).text(),
new Response(runResult.stderr).text(),
]);
console.log("Run stdout:", stdout);
console.log("Run stderr:", stderr);
// The test should pass if the favicon is found in embedded files
expect(stdout).toContain("SUCCESS: Found favicon in embedded files");
expect(runResult.exitCode).toBe(0);
});