Compare commits

...

3 Commits

Author SHA1 Message Date
Claude Bot
4784ddd69a test: verify referenced image file actually exists in output directory
Improved test assertions to parse the actual src value from HTML and
verify that the specific file exists in the output directory, rather
than just checking that any file with the extension exists.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 21:56:51 +00:00
Claude Bot
d27b7b01a8 test: use Buffer.alloc instead of String.repeat for large test payload
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 21:44:12 +00:00
Claude Bot
730e044c53 fix(bundler): emit image files used by HTML even when inlined in CSS
When an image was referenced from both HTML (via <img src>) and CSS
(via url()) and the image was small enough to be inlined in CSS, the
bundler would incorrectly remove the image file from the output since
it was inlined in CSS. This broke the HTML reference since the expected
file didn't exist.

The fix treats HTML files like JS files when tracking which files are
imported and inlined in CSS, ensuring that files referenced by HTML
are still emitted to the output directory.

Fixes #26575

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 21:34:04 +00:00
2 changed files with 99 additions and 4 deletions

View File

@@ -311,8 +311,11 @@ pub const BundleV2 = struct {
}
}
const is_js = v.all_loaders[source_index.get()].isJavaScriptLike();
const is_css = v.all_loaders[source_index.get()].isCSS();
const loader = v.all_loaders[source_index.get()];
// HTML is included because it can reference files (e.g., <img src>) that may also
// be inlined in CSS, and we need to ensure those files are emitted for HTML.
const is_js_or_html = loader.isJavaScriptLike() or loader == .html;
const is_css = loader.isCSS();
const import_record_list_id = source_index;
// when there are no import records, v index will be invalid
@@ -341,9 +344,9 @@ pub const BundleV2 = struct {
}
}
// Mark if the file is imported by JS and its URL is inlined for CSS
// Mark if the file is imported by JS/HTML and its URL is inlined for CSS
const is_inlined = import_record.source_index.isValid() and v.all_urls_for_css[import_record.source_index.get()].len > 0;
if (is_js and is_inlined) {
if (is_js_or_html and is_inlined) {
v.additional_files_imported_by_js_and_inlined_in_css.set(import_record.source_index.get());
} else if (is_css and is_inlined) {
v.additional_files_imported_by_css_and_inlined.set(import_record.source_index.get());

View File

@@ -0,0 +1,92 @@
import { describe, expect } from "bun:test";
import { readdirSync } from "fs";
import { itBundled } from "../../bundler/expectBundled";
// https://github.com/oven-sh/bun/issues/26575
// When an image is referenced from both HTML (via <img src>) and CSS (via url()),
// and the image is small enough to be inlined in CSS, the image file should still
// be emitted to the output directory for the HTML reference.
describe("issue #26575", () => {
itBundled("html/image-referenced-by-html-and-css-inlined", {
outdir: "out/",
files: {
"/index.html": `<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<img src="./img.webp">
</body>
</html>`,
"/styles.css": `body {
background-image: url("./img.webp");
}`,
// Small image that will be inlined in CSS (under the inlining threshold)
// This is a minimal valid WebP file (34 bytes)
"/img.webp": Buffer.from([
0x52, 0x49, 0x46, 0x46, 0x1a, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50, 0x38, 0x4c, 0x0d, 0x00,
0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]),
},
entryPoints: ["/index.html"],
onAfterBundle(api) {
// The image should be inlined in the CSS (as a data URL)
const htmlContent = api.readFile("out/index.html");
const cssMatch = htmlContent.match(/href="(.*\.css)"/);
expect(cssMatch).not.toBeNull();
const cssContent = api.readFile("out/" + cssMatch![1]);
expect(cssContent).toContain("data:image/webp;base64,");
// The HTML should reference the hashed image file (not inline it)
expect(htmlContent).not.toContain("data:image/webp");
const imgSrcMatch = htmlContent.match(/src="(\.\/[^"]+\.webp)"/);
expect(imgSrcMatch).not.toBeNull();
// Verify the referenced image file actually exists in the output directory
const imgFilename = imgSrcMatch![1].replace("./", "");
const outputFiles = readdirSync(api.outdir);
expect(outputFiles).toContain(imgFilename);
},
});
// Also test with a larger image that won't be inlined
itBundled("html/image-referenced-by-html-and-css-not-inlined", {
outdir: "out/",
files: {
"/index.html": `<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<img src="./img.png">
</body>
</html>`,
"/styles.css": `body {
background-image: url("./img.png");
}`,
// Large image content that won't be inlined (over 128KB threshold)
"/img.png": Buffer.alloc(150000, "x"),
},
entryPoints: ["/index.html"],
onAfterBundle(api) {
// The image should NOT be inlined in the CSS
const htmlContent = api.readFile("out/index.html");
const cssMatch = htmlContent.match(/href="(.*\.css)"/);
expect(cssMatch).not.toBeNull();
const cssContent = api.readFile("out/" + cssMatch![1]);
expect(cssContent).not.toContain("data:image/png;base64,");
expect(cssContent).toMatch(/url\(".*\.png"\)/);
// The HTML should reference the hashed image file
const imgSrcMatch = htmlContent.match(/src="(\.\/[^"]+\.png)"/);
expect(imgSrcMatch).not.toBeNull();
// Verify the referenced image file actually exists in the output directory
const imgFilename = imgSrcMatch![1].replace("./", "");
const outputFiles = readdirSync(api.outdir);
expect(outputFiles).toContain(imgFilename);
},
});
});