mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
### What does this PR do? ### How did you verify your code works? --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Claude Bot <claude-bot@bun.sh>
373 lines
10 KiB
TypeScript
373 lines
10 KiB
TypeScript
import { describe, expect } from "bun:test";
|
|
import { itBundled } from "./expectBundled";
|
|
|
|
describe.concurrent("bundler", () => {
|
|
// Test HTML import manifest with enhanced metadata
|
|
itBundled("html-import/manifest-with-metadata", {
|
|
outdir: "out/",
|
|
files: {
|
|
"/server.js": `
|
|
import html from "./client.html";
|
|
|
|
if (!html.files.find(a => a.path === html.index)) {
|
|
throw new Error("Bad file");
|
|
}
|
|
|
|
console.log(JSON.stringify(html, null, 2));
|
|
|
|
`,
|
|
"/client.html": `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<link rel="stylesheet" href="./styles.css">
|
|
<script src="./client.js"></script>
|
|
</head>
|
|
<body>
|
|
<h1>Client HTML</h1>
|
|
</body>
|
|
</html>`,
|
|
"/styles.css": `
|
|
body {
|
|
background-color: #f0f0f0;
|
|
margin: 0;
|
|
padding: 20px;
|
|
}
|
|
h1 {
|
|
color: #333;
|
|
}`,
|
|
"/client.js": `
|
|
import favicon from './favicon.png';
|
|
console.log("Client script loaded");
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
console.log('DOM ready');
|
|
});
|
|
console.log(favicon);
|
|
`,
|
|
"/favicon.png": Buffer.from([
|
|
0x89,
|
|
0x50,
|
|
0x4e,
|
|
0x47,
|
|
0x0d,
|
|
0x0a,
|
|
0x1a,
|
|
0x0a, // PNG header
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x0d,
|
|
0x49,
|
|
0x48,
|
|
0x44,
|
|
0x52, // IHDR chunk
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x10,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x10, // 16x16
|
|
0x08,
|
|
0x02,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x90,
|
|
0x91,
|
|
0x68, // 8-bit RGB
|
|
0x36,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x49,
|
|
0x45,
|
|
0x4e, // IEND chunk
|
|
0x44,
|
|
0xae,
|
|
0x42,
|
|
0x60,
|
|
0x82,
|
|
]),
|
|
},
|
|
entryPoints: ["/server.js"],
|
|
target: "bun",
|
|
|
|
run: {
|
|
validate({ stdout, stderr }) {
|
|
expect(stdout).toMatchInlineSnapshot(`
|
|
"{
|
|
"index": "./client.html",
|
|
"files": [
|
|
{
|
|
"input": "client.html",
|
|
"path": "./client-5y90hwq3.js",
|
|
"loader": "js",
|
|
"isEntry": true,
|
|
"headers": {
|
|
"etag": "xGxKikG0dN0",
|
|
"content-type": "text/javascript;charset=utf-8"
|
|
}
|
|
},
|
|
{
|
|
"input": "client.html",
|
|
"path": "./client.html",
|
|
"loader": "html",
|
|
"isEntry": true,
|
|
"headers": {
|
|
"etag": "hZ3u5t2Rmuo",
|
|
"content-type": "text/html;charset=utf-8"
|
|
}
|
|
},
|
|
{
|
|
"input": "client.html",
|
|
"path": "./client-0z58sk45.css",
|
|
"loader": "css",
|
|
"isEntry": true,
|
|
"headers": {
|
|
"etag": "0k_h5oYVQlA",
|
|
"content-type": "text/css;charset=utf-8"
|
|
}
|
|
},
|
|
{
|
|
"input": "favicon.png",
|
|
"path": "./favicon-wjepk3hq.png",
|
|
"loader": "file",
|
|
"isEntry": false,
|
|
"headers": {
|
|
"etag": "fFLOVvPDEZc",
|
|
"content-type": "image/png"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
"
|
|
`);
|
|
},
|
|
},
|
|
});
|
|
|
|
// Test manifest with multiple HTML imports
|
|
itBundled("html-import/multiple-manifests", {
|
|
outdir: "out/",
|
|
backend: "cli",
|
|
files: {
|
|
"/server.js": `
|
|
import homeHtml from "./home.html";
|
|
import aboutHtml from "./about.html";
|
|
console.log("Home manifest:", homeHtml);
|
|
console.log("About manifest:", aboutHtml);
|
|
`,
|
|
"/home.html": `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<link rel="stylesheet" href="./home.css">
|
|
<script src="./home.js"></script>
|
|
</head>
|
|
<body>
|
|
<h1>Home Page</h1>
|
|
</body>
|
|
</html>`,
|
|
"/about.html": `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<link rel="stylesheet" href="./about.css">
|
|
<script src="./about.js"></script>
|
|
</head>
|
|
<body>
|
|
<h1>About Page</h1>
|
|
</body>
|
|
</html>`,
|
|
"/home.css": "body { background: #fff; }",
|
|
"/home.js": "console.log('Home page');",
|
|
"/about.css": "body { background: #f0f0f0; }",
|
|
"/about.js": "console.log('About page');",
|
|
},
|
|
entryPoints: ["/server.js"],
|
|
target: "bun",
|
|
|
|
onAfterBundle(api) {
|
|
const serverCode = api.readFile("out/server.js");
|
|
|
|
// The manifests are embedded as escaped JSON strings in __jsonParse calls
|
|
const manifestMatches = [...serverCode.matchAll(/__jsonParse\("(.+?)"\)/gs)];
|
|
expect(manifestMatches.length).toBe(2);
|
|
let manifests = [];
|
|
for (const match of manifestMatches) {
|
|
// The captured group contains the escaped JSON string
|
|
const escapedJson = match[1];
|
|
// Parse the escaped JSON string
|
|
const manifest = JSON.parse(JSON.parse('"' + escapedJson + '"'));
|
|
manifests.push(manifest);
|
|
expect(manifest.index).toBeDefined();
|
|
expect(manifest.files).toBeDefined();
|
|
expect(Array.isArray(manifest.files)).toBe(true);
|
|
|
|
// Each manifest should have HTML, JS, and CSS
|
|
const loaders = manifest.files.map((f: any) => f.loader);
|
|
expect(loaders).toContain("html");
|
|
expect(loaders).toContain("js");
|
|
expect(loaders).toContain("css");
|
|
|
|
// All files should have enhanced metadata
|
|
for (const file of manifest.files) {
|
|
expect(file).toHaveProperty("headers");
|
|
expect(file).toHaveProperty("isEntry");
|
|
expect(file.headers).toHaveProperty("etag");
|
|
expect(file.headers).toHaveProperty("content-type");
|
|
}
|
|
}
|
|
|
|
expect(manifests).toMatchInlineSnapshot(`
|
|
[
|
|
{
|
|
"files": [
|
|
{
|
|
"headers": {
|
|
"content-type": "text/javascript;charset=utf-8",
|
|
"etag": "DLJP98vzFzQ",
|
|
},
|
|
"input": "home.html",
|
|
"isEntry": true,
|
|
"loader": "js",
|
|
"path": "./home-5f8tg1jd.js",
|
|
},
|
|
{
|
|
"headers": {
|
|
"content-type": "text/html;charset=utf-8",
|
|
"etag": "_Qy4EtlcGvs",
|
|
},
|
|
"input": "home.html",
|
|
"isEntry": true,
|
|
"loader": "html",
|
|
"path": "./home.html",
|
|
},
|
|
{
|
|
"headers": {
|
|
"content-type": "text/css;charset=utf-8",
|
|
"etag": "6qg2qb7a2qo",
|
|
},
|
|
"input": "home.html",
|
|
"isEntry": true,
|
|
"loader": "css",
|
|
"path": "./home-5pdcqqze.css",
|
|
},
|
|
],
|
|
"index": "./home.html",
|
|
},
|
|
{
|
|
"files": [
|
|
{
|
|
"headers": {
|
|
"content-type": "text/javascript;charset=utf-8",
|
|
"etag": "t8rrkgPylZo",
|
|
},
|
|
"input": "about.html",
|
|
"isEntry": true,
|
|
"loader": "js",
|
|
"path": "./about-e59abjgr.js",
|
|
},
|
|
{
|
|
"headers": {
|
|
"content-type": "text/html;charset=utf-8",
|
|
"etag": "igL7YEH9e0I",
|
|
},
|
|
"input": "about.html",
|
|
"isEntry": true,
|
|
"loader": "html",
|
|
"path": "./about.html",
|
|
},
|
|
{
|
|
"headers": {
|
|
"content-type": "text/css;charset=utf-8",
|
|
"etag": "DE8kdBXWhVg",
|
|
},
|
|
"input": "about.html",
|
|
"isEntry": true,
|
|
"loader": "css",
|
|
"path": "./about-7apjgk42.css",
|
|
},
|
|
],
|
|
"index": "./about.html",
|
|
},
|
|
]
|
|
`);
|
|
},
|
|
});
|
|
|
|
// Test that import with {type: 'file'} still works as a file import
|
|
itBundled("html-import/with-type-file-attribute", {
|
|
outdir: "out/",
|
|
backend: "cli",
|
|
files: {
|
|
"/entry.js": `
|
|
import htmlUrl from "./page.html" with { type: 'file' };
|
|
import htmlManifest from "./index.html";
|
|
|
|
// Test that htmlUrl is a string (file path)
|
|
if (typeof htmlUrl !== 'string') {
|
|
throw new Error("Expected htmlUrl to be a string, got " + typeof htmlUrl);
|
|
}
|
|
|
|
// Test that htmlManifest is an object with expected properties
|
|
if (typeof htmlManifest !== 'object' || !htmlManifest.index || !Array.isArray(htmlManifest.files)) {
|
|
throw new Error("Expected htmlManifest to be an object with index and files array");
|
|
}
|
|
|
|
console.log("✓ File import returned URL:", htmlUrl);
|
|
console.log("✓ HTML import returned manifest with", htmlManifest.files.length, "files");
|
|
console.log("✓ Both import types work correctly");
|
|
`,
|
|
"/page.html": `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Page imported as file</title>
|
|
</head>
|
|
<body>
|
|
<h1>This HTML is imported with type: 'file'</h1>
|
|
</body>
|
|
</html>`,
|
|
"/index.html": `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<link rel="stylesheet" href="./styles.css">
|
|
</head>
|
|
<body>
|
|
<h1>Test Page</h1>
|
|
</body>
|
|
</html>`,
|
|
"/styles.css": `body { background: #fff; }`,
|
|
},
|
|
entryPoints: ["/entry.js"],
|
|
target: "bun",
|
|
|
|
run: {
|
|
validate({ stdout }) {
|
|
expect(stdout).toContain("✓ File import returned URL:");
|
|
expect(stdout).toContain("✓ HTML import returned manifest with");
|
|
expect(stdout).toContain("✓ Both import types work correctly");
|
|
},
|
|
},
|
|
|
|
onAfterBundle(api) {
|
|
// Check that the generated code correctly handles both import types
|
|
const entryCode = api.readFile("out/entry.js");
|
|
|
|
// Should have a file import for page.html
|
|
expect(entryCode).toContain('var page_default = "./page-');
|
|
expect(entryCode).toContain('.html";');
|
|
|
|
// Should have a manifest import for index.html
|
|
expect(entryCode).toContain('__jsonParse("');
|
|
expect(entryCode).toContain('\\\"index\\\":\\\"./index.html\\\"');
|
|
expect(entryCode).toContain('\\\"files\\\":[');
|
|
},
|
|
});
|
|
});
|