mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(core): skip empty sub-composition files instead of aborting render (#1678)
This commit is contained in:
@@ -38,18 +38,27 @@ function makeHostDocument(compId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("inlineSubCompositions – #ID selector scoping divergence", () => {
|
describe("inlineSubCompositions – #ID selector scoping divergence", () => {
|
||||||
it("throws an actionable error when a resolved sub-composition file is empty", () => {
|
it.each([
|
||||||
|
{ label: "empty", html: "" },
|
||||||
|
{ label: "whitespace-only", html: " \n \t " },
|
||||||
|
{
|
||||||
|
label: "valid-parse-empty-body",
|
||||||
|
html: "<!doctype html><html><head></head><body></body></html>",
|
||||||
|
},
|
||||||
|
])("skips $label sub-composition files gracefully", ({ html }) => {
|
||||||
const document = makeHostDocument("intro");
|
const document = makeHostDocument("intro");
|
||||||
const host = document.querySelector('[data-composition-src="intro.html"]')!;
|
const host = document.querySelector('[data-composition-src="intro.html"]')!;
|
||||||
|
const missing: string[] = [];
|
||||||
|
|
||||||
expect(() =>
|
const result = inlineSubCompositions(document, [host], {
|
||||||
inlineSubCompositions(document, [host], {
|
resolveHtml: () => html,
|
||||||
resolveHtml: () => "",
|
parseHtml: (h) => parseHTML(h).document,
|
||||||
parseHtml: (html) => parseHTML(html).document,
|
onMissingComposition: (src) => missing.push(src),
|
||||||
}),
|
});
|
||||||
).toThrow(
|
|
||||||
"Composition HTML is empty or could not be parsed: intro.html. Check that the file referenced by data-composition-src contains valid HTML.",
|
expect(missing).toEqual(["intro.html"]);
|
||||||
);
|
expect(result.styles).toHaveLength(0);
|
||||||
|
expect(result.scripts).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("producer path (no flattenInnerRoot): strips inner root, losing #id attribute", () => {
|
it("producer path (no flattenInnerRoot): strips inner root, losing #id attribute", () => {
|
||||||
|
|||||||
@@ -125,24 +125,6 @@ function defaultBuildScopeSelector(compId: string): string {
|
|||||||
return `[data-composition-id="${escaped}"]`;
|
return `[data-composition-id="${escaped}"]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function emptyCompositionHtmlError(src: string): Error {
|
|
||||||
return new Error(
|
|
||||||
`Composition HTML is empty or could not be parsed: ${src}. Check that the file referenced by data-composition-src contains valid HTML.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertNonEmptyCompositionHtml(html: string, src: string): void {
|
|
||||||
if (!html.trim()) {
|
|
||||||
throw emptyCompositionHtmlError(src);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertParsedCompositionDocument(doc: Document, src: string): void {
|
|
||||||
if (!doc.documentElement) {
|
|
||||||
throw emptyCompositionHtmlError(src);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Core implementation
|
// Core implementation
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -194,16 +176,16 @@ export function inlineSubCompositions(
|
|||||||
if (!src) continue;
|
if (!src) continue;
|
||||||
|
|
||||||
const compHtml = resolveHtml(src);
|
const compHtml = resolveHtml(src);
|
||||||
if (compHtml == null) {
|
if (compHtml == null || !compHtml.trim()) {
|
||||||
if (onMissingComposition) {
|
onMissingComposition?.(src);
|
||||||
onMissingComposition(src);
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
assertNonEmptyCompositionHtml(compHtml, src);
|
|
||||||
const compDoc = parseHtml(compHtml);
|
const compDoc = parseHtml(compHtml);
|
||||||
assertParsedCompositionDocument(compDoc, src);
|
if (!compDoc.documentElement) {
|
||||||
|
onMissingComposition?.(src);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Determine composition IDs
|
// Determine composition IDs
|
||||||
let compId: string | null;
|
let compId: string | null;
|
||||||
@@ -220,9 +202,15 @@ export function inlineSubCompositions(
|
|||||||
// Find content: prefer <template>, fall back to <body>
|
// Find content: prefer <template>, fall back to <body>
|
||||||
const contentRoot = compDoc.querySelector("template");
|
const contentRoot = compDoc.querySelector("template");
|
||||||
const contentHtml = contentRoot ? contentRoot.innerHTML || "" : compDoc.body?.innerHTML || "";
|
const contentHtml = contentRoot ? contentRoot.innerHTML || "" : compDoc.body?.innerHTML || "";
|
||||||
assertNonEmptyCompositionHtml(contentHtml, src);
|
if (!contentHtml.trim()) {
|
||||||
|
onMissingComposition?.(src);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const contentDoc = parseHtml(contentHtml);
|
const contentDoc = parseHtml(contentHtml);
|
||||||
assertParsedCompositionDocument(contentDoc, src);
|
if (!contentDoc.documentElement) {
|
||||||
|
onMissingComposition?.(src);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Find the inner composition root
|
// Find the inner composition root
|
||||||
const innerRoot = compId
|
const innerRoot = compId
|
||||||
|
|||||||
@@ -492,7 +492,7 @@ describe("detectRenderModeHints", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("compileForRender reports empty sub-composition HTML with an actionable error", async () => {
|
it("compileForRender skips empty sub-composition files instead of aborting", async () => {
|
||||||
const projectDir = mkdtempSync(join(tmpdir(), "hf-empty-subcomp-"));
|
const projectDir = mkdtempSync(join(tmpdir(), "hf-empty-subcomp-"));
|
||||||
const compositionsDir = join(projectDir, "compositions");
|
const compositionsDir = join(projectDir, "compositions");
|
||||||
mkdirSync(compositionsDir, { recursive: true });
|
mkdirSync(compositionsDir, { recursive: true });
|
||||||
@@ -514,11 +514,8 @@ describe("detectRenderModeHints", () => {
|
|||||||
);
|
);
|
||||||
writeFileSync(join(compositionsDir, "intro.html"), "");
|
writeFileSync(join(compositionsDir, "intro.html"), "");
|
||||||
|
|
||||||
await expect(
|
const result = await compileForRender(projectDir, join(projectDir, "index.html"), projectDir);
|
||||||
compileForRender(projectDir, join(projectDir, "index.html"), projectDir),
|
expect(result.html).toContain("data-composition-id");
|
||||||
).rejects.toThrow(
|
|
||||||
"Composition HTML is empty or could not be parsed: compositions/intro.html. Check that the file referenced by data-composition-src contains valid HTML.",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -604,6 +604,9 @@ function inlineSubCompositions(
|
|||||||
parseHtml: (htmlStr: string) => parseHTML(htmlStr).document as unknown as Document,
|
parseHtml: (htmlStr: string) => parseHTML(htmlStr).document as unknown as Document,
|
||||||
scriptErrorLabel: "[Compiler] Composition script failed",
|
scriptErrorLabel: "[Compiler] Composition script failed",
|
||||||
compoundAuthoredRoot: true,
|
compoundAuthoredRoot: true,
|
||||||
|
onMissingComposition: (srcPath: string) => {
|
||||||
|
console.warn(`[Compiler] Composition file missing or empty: ${srcPath}`);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user