fix(producer): extract head assets from non-template sub-comps + fix postcss ESM (#220)

## Summary

Two fixes in the producer:

1. **Head styles/scripts extraction**: mirrors the runtime fix from PR #219. The producer's `inlineSubCompositions()` parsed only `bodyEl.innerHTML` from non-template sub-compositions, discarding all `<head>` content.
2. **Externalize postcss**: postcss is a CJS module with `require("path")` — bundling it into ESM output caused "Dynamic require of path is not supported" at runtime, breaking `npx tsx cli render` and `npx tsx cli preview` from the local dev build.

## Verified

Re-rendered the iris-wipe composition (eval prompt #25, previously scored 1.0/5 — entirely black):

| Frame | Before fix | After fix |
| --- | --- | --- |
| 0\.5s | Black | Red background + "HELLO" text |
| File size | 16\.9 KB (all black) | 64\.8 KB (actual content) |

Scene 1 now renders correctly. Scene 2's clip-path animation has a separate GSAP issue (the lint already warns about it via `scene_layer_missing_visibility_kill`).

## Test plan

- [x] `pnpm --filter @hyperframes/producer build` succeeds
- [x] `node --input-type=module -e "import './dist/index.js'"` loads without error
- [x] Re-render iris-wipe produces visible content (64.8 KB vs 16.9 KB)
- [x] Frame extraction confirms red "HELLO" scene renders correctly
This commit is contained in:
Miguel Ángel
2026-04-07 17:37:26 +02:00
committed by GitHub
parent f56b4c8620
commit ef2f646c90
2 changed files with 27 additions and 2 deletions
@@ -525,6 +525,31 @@ function inlineSubCompositions(
: contentDoc.querySelector("[data-composition-id]");
const inferredCompId = innerRoot?.getAttribute("data-composition-id")?.trim() || null;
// When a sub-composition is a full HTML document (no <template>), styles
// and scripts in <head> are not part of contentDoc (which only has body
// content). Extract them separately so backgrounds, positioning, fonts,
// and library scripts (e.g. GSAP CDN) are not silently dropped.
if (!templateEl) {
const compHead = compDoc.querySelector("head");
if (compHead) {
for (const styleEl of compHead.querySelectorAll("style")) {
const css = rewriteCssAssetUrls(styleEl.textContent || "", srcPath);
const scopeId = compId || inferredCompId;
if (scopeId && css.trim()) {
collectedStyles.push(scopeCssToComposition(css, scopeId));
} else {
collectedStyles.push(css);
}
}
for (const scriptEl of compHead.querySelectorAll("script")) {
const src = (scriptEl.getAttribute("src") || "").trim();
if (src && !collectedExternalScriptSrcs.includes(src)) {
collectedExternalScriptSrcs.push(src);
}
}
}
}
for (const styleEl of contentDoc.querySelectorAll("style")) {
const css = rewriteCssAssetUrls(styleEl.textContent || "", srcPath);
const scopeId = compId || inferredCompId;