Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/core/src/compiler/htmlBundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,15 +266,15 @@ function rewriteCssUrlsWithInlinedAssets(cssText: string, projectDir: string): s
);
}

function cssAttributeSelector(attr: string, value: string): string {
export function cssAttributeSelector(attr: string, value: string): string {
return `[${attr}="${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]`;
}

function uniqueCompositionId(baseId: string, index: number): string {
return `${baseId}__hf${index}`;
}

type BundledHostCompositionIdentity = {
export type BundledHostCompositionIdentity = {
authoredCompositionId: string | null;
runtimeCompositionId: string | null;
};
Expand Down Expand Up @@ -320,7 +320,7 @@ function countBundledAuthoredCompositionIds(hosts: Element[]): Map<string, numbe
return counts;
}

function assignBundledRuntimeCompositionIds(
export function assignBundledRuntimeCompositionIds(
hosts: Element[],
counts: Map<string, number> = countBundledAuthoredCompositionIds(hosts),
): Map<Element, BundledHostCompositionIdentity> {
Expand Down Expand Up @@ -366,7 +366,7 @@ function assignBundledRuntimeCompositionIds(
return identities;
}

function parseHostVariableValues(host: Element): Record<string, unknown> {
export function parseHostVariableValues(host: Element): Record<string, unknown> {
const raw = host.getAttribute("data-variable-values");
if (!raw) return {};
let parsed: unknown;
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/compiler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@ export { compileHtml, type MediaDurationProber } from "./htmlCompiler";

// HTML bundler (Node.js — requires fs, linkedom, esbuild)
export {
assignBundledRuntimeCompositionIds,
bundleToSingleHtml,
cssAttributeSelector,
type BundleOptions,
type BundledHostCompositionIdentity,
prepareFlattenedInnerRoot,
FLATTENED_INNER_ROOT_STRIP_ATTRS,
parseHostVariableValues,
} from "./htmlBundler";
export { readDeclaredDefaults } from "../runtime/getVariables";

export {
RUNTIME_BOOTSTRAP_ATTR,
Expand Down
60 changes: 60 additions & 0 deletions packages/producer/src/services/htmlCompiler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,66 @@ describe("template-wrapped sub-composition media offsets", () => {
expect(compiled.html).toContain("__hfNormalizeSelector");
});

it("emits per-instance scoped variables for external sub-composition hosts", async () => {
const projectDir = mkdtempSync(join(tmpdir(), "hf-render-vars-"));
const compositionsDir = join(projectDir, "compositions");
mkdirSync(compositionsDir, { recursive: true });
const indexPath = join(projectDir, "index.html");

writeFileSync(
indexPath,
`<!DOCTYPE html>
<html>
<body>
<div id="root" data-composition-id="root" data-width="640" data-height="360" data-duration="4">
<div
id="card-a"
data-composition-id="card"
data-composition-src="compositions/card.html"
data-variable-values='{"title":"Pro"}'></div>
<div
id="card-b"
data-composition-id="card"
data-composition-src="compositions/card.html"
data-variable-values='{"title":"Enterprise"}'></div>
</div>
</body>
</html>`,
);
writeFileSync(
join(compositionsDir, "card.html"),
`<!DOCTYPE html>
<html data-composition-variables='[
{"id":"title","type":"string","label":"Title","default":"Default Title"},
{"id":"theme","type":"string","label":"Theme","default":"light"}
]'>
<body>
<div id="card-root" data-composition-id="card" data-width="640" data-height="360" data-duration="4">
<script>
window.__captured = window.__captured || [];
window.__captured.push(__hyperframes.getVariables());
</script>
</div>
</body>
</html>`,
);

const compiled = await compileForRender(projectDir, indexPath, projectDir);
const { document } = parseHTML(compiled.html);
const cardA = document.querySelector("#card-a");
const cardB = document.querySelector("#card-b");

expect(cardA?.getAttribute("data-composition-id")).toBe("card__hf1");
expect(cardB?.getAttribute("data-composition-id")).toBe("card__hf2");
expect(cardA?.getAttribute("data-hf-original-composition-id")).toBe("card");
expect(cardB?.getAttribute("data-hf-original-composition-id")).toBe("card");
expect(compiled.html).toContain("window.__hfVariablesByComp");
expect(compiled.html).toContain('"card__hf1":{"title":"Pro","theme":"light"}');
expect(compiled.html).toContain('"card__hf2":{"title":"Enterprise","theme":"light"}');
expect(compiled.html).toContain('var __hfTimelineCompId = "card__hf1"');
expect(compiled.html).toContain('var __hfTimelineCompId = "card__hf2"');
});

it("preserves the inferred composition boundary when the host has no composition id", async () => {
const projectDir = mkdtempSync(join(tmpdir(), "hf-anonymous-host-"));
const compositionsDir = join(projectDir, "compositions");
Expand Down
25 changes: 22 additions & 3 deletions packages/producer/src/services/htmlCompiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ import {
type ResolvedDuration,
type UnresolvedElement,
} from "@hyperframes/core";
import { inlineSubCompositions as inlineSubCompositionsShared } from "@hyperframes/core/compiler";
import {
assignBundledRuntimeCompositionIds,
cssAttributeSelector,
inlineSubCompositions as inlineSubCompositionsShared,
parseHostVariableValues,
readDeclaredDefaults,
} from "@hyperframes/core/compiler";
import { extractMediaMetadata, extractAudioMetadata } from "../utils/ffprobe.js";
import { isPathInside, toExternalAssetKey } from "../utils/paths.js";
import {
Expand Down Expand Up @@ -573,6 +579,7 @@ function coalesceHeadStylesAndBodyScripts(html: string): string {
* compositions from the pre-compiled map or disk, and setting explicit
* pixel dimensions on host elements for headless rendering.
*/
// fallow-ignore-next-line complexity
function inlineSubCompositions(
html: string,
subCompositions: Map<string, string>,
Expand All @@ -584,6 +591,7 @@ function inlineSubCompositions(
const hosts = Array.from(document.querySelectorAll("[data-composition-src]"));

if (!hosts.length) return html;
const hostIdentityByElement = assignBundledRuntimeCompositionIds(hosts);

const result = inlineSubCompositionsShared(
document as unknown as Document,
Expand All @@ -600,8 +608,12 @@ function inlineSubCompositions(
return compHtml;
},
parseHtml: (htmlStr: string) => parseHTML(htmlStr).document as unknown as Document,
hostIdentityMap: hostIdentityByElement,
scriptErrorLabel: "[Compiler] Composition script failed",
compoundAuthoredRoot: true,
readVariableDefaults: readDeclaredDefaults,
parseHostVariables: parseHostVariableValues,
buildScopeSelector: (compId: string) => cssAttributeSelector("data-composition-id", compId),
},
);

Expand Down Expand Up @@ -677,10 +689,17 @@ function inlineSubCompositions(
}
}

const inlineScripts = [...result.scripts];
if (Object.keys(result.variablesByComp).length > 0) {
inlineScripts.unshift(
`window.__hfVariablesByComp = Object.assign({}, window.__hfVariablesByComp || {}, ${JSON.stringify(result.variablesByComp)});`,
);
}

// Append collected inline scripts to <body>
if (result.scripts.length && body) {
if (inlineScripts.length && body) {
const scriptEl = document.createElement("script");
scriptEl.textContent = result.scripts.join("\n;\n");
scriptEl.textContent = inlineScripts.join("\n;\n");
body.appendChild(scriptEl);
}

Expand Down
Loading