mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
## Summary - `cheerio` pulls `encoding-sniffer` → `whatwg-encoding@3.1.1` (deprecated), causing a warning on every `npm install -g hyperframes` - `linkedom` was already bundled into the CLI via tsup `noExternal` and has zero deprecated transitive deps - Rewrote `htmlBundler.ts` and `subComposition.ts` to use standard DOM APIs via `linkedom` - Added a `parseHTMLContent` helper that wraps HTML fragments in a full document structure (required for `linkedom` to populate `document.body`) - Removed `cheerio` from `cli` dependencies and tsup `external` list - Replaced `cheerio` with `linkedom` in `core` `optionalDependencies` ## Test plan - [x] All 411 tests pass (`bun run test` in `packages/core`) - [x] Full monorepo build succeeds (`bun run build`) - [x] TypeScript typecheck passes
84 lines
2.7 KiB
TypeScript
84 lines
2.7 KiB
TypeScript
import { existsSync, readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { parseHTML } from "linkedom";
|
|
import { rewriteAssetPaths, rewriteCssAssetUrls } from "../../compiler/rewriteSubCompPaths.js";
|
|
|
|
/**
|
|
* Build a standalone HTML page for a sub-composition.
|
|
*
|
|
* Uses the project's own index.html `<head>` so all dependencies (GSAP, fonts,
|
|
* Lottie, reset styles, runtime) are preserved — instead of building a minimal
|
|
* page from scratch that would miss important scripts/styles.
|
|
*/
|
|
export function buildSubCompositionHtml(
|
|
projectDir: string,
|
|
compPath: string,
|
|
runtimeUrl: string,
|
|
baseHref?: string,
|
|
): string | null {
|
|
const compFile = join(projectDir, compPath);
|
|
if (!existsSync(compFile)) return null;
|
|
|
|
const rawComp = readFileSync(compFile, "utf-8");
|
|
|
|
// Extract content from <template> wrapper (compositions are always templates)
|
|
const templateMatch = rawComp.match(/<template[^>]*>([\s\S]*)<\/template>/i);
|
|
const content = templateMatch?.[1] ?? rawComp;
|
|
const { document: contentDoc } = parseHTML(
|
|
`<!DOCTYPE html><html><head></head><body>${content}</body></html>`,
|
|
);
|
|
|
|
rewriteAssetPaths(
|
|
contentDoc.querySelectorAll("[src], [href]"),
|
|
compPath,
|
|
(el: Element, attr: string) => el.getAttribute(attr),
|
|
(el: Element, attr: string, value: string) => {
|
|
el.setAttribute(attr, value);
|
|
},
|
|
);
|
|
for (const styleEl of contentDoc.querySelectorAll("style")) {
|
|
styleEl.textContent = rewriteCssAssetUrls(styleEl.textContent || "", compPath);
|
|
}
|
|
|
|
const rewrittenContent = contentDoc.body.innerHTML || content;
|
|
|
|
// Use the project's index.html <head> to preserve all dependencies
|
|
const indexPath = join(projectDir, "index.html");
|
|
let headContent = "";
|
|
|
|
if (existsSync(indexPath)) {
|
|
const indexHtml = readFileSync(indexPath, "utf-8");
|
|
const headMatch = indexHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
|
|
headContent = headMatch?.[1] ?? "";
|
|
}
|
|
|
|
// Inject <base> for relative asset resolution (before other tags)
|
|
if (baseHref && !headContent.includes("<base")) {
|
|
headContent = `<base href="${baseHref}">\n${headContent}`;
|
|
}
|
|
|
|
// Ensure runtime is present (might differ from the one in index.html)
|
|
if (
|
|
!headContent.includes("hyperframe.runtime") &&
|
|
!headContent.includes("hyperframes-preview-runtime")
|
|
) {
|
|
headContent += `\n<script data-hyperframes-preview-runtime="1" src="${runtimeUrl}"></script>`;
|
|
}
|
|
|
|
// Fallback: if no index.html head was found, add minimal deps
|
|
if (!headContent.includes("gsap")) {
|
|
headContent += `\n<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>`;
|
|
}
|
|
|
|
return `<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
${headContent}
|
|
</head>
|
|
<body>
|
|
<script>window.__timelines=window.__timelines||{};</script>
|
|
${rewrittenContent}
|
|
</body>
|
|
</html>`;
|
|
}
|