* perf(core): a project with many composition variables builds without a style scan per variable * perf(core): a build checks each project path once, however often a scene is mounted * perf(core): collecting a page's characters for fonts no longer copies the page into arrays * perf(core): a warm font cache builds the page without asking Google Fonts again The woff2 files were cached on disk, but the CSS lookup that names them was only cached in memory. Each new process asked fonts.googleapis.com first, and offline the lookup failed before the cached woff2 files were ever reached. The lookup is now kept on disk beside them, keyed by its request URL. Only a lookup that named faces is kept, written aside and renamed into place. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * perf(core): a build resolves and reads each inlined asset once, however often a scene is mounted Inlining a relative asset as a data: URL ran isSafePath on it, which realpaths the path and every ancestor, then statted, read and base64-encoded the file. It did so for every url() of every mount, so a scene mounted 160 times paid for the same few files 160 times. The answers are now kept for the build: a path's safety is shared with the entry-path checks, and an asset's data: URL (or its too-large warning) is kept by file path. The bundler also tells onRead about each path once per build. The preview's read tracker realpaths and readlinks every path it is told about, and it was told again for every mount. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * perf(core): a scene mounted many times is read, parsed and scoped once per build Every mount of a sub-composition re-read its file, re-stamped its hf ids, parsed it four times (twice to check it is usable, twice to mount it) and re-ran postcss over each of its stylesheets, though only the scope selector, ids and variables differ between mounts. - The bundler reads and stamps each scene file once per build. - inlineSubCompositions parses each file once, answers the usability check from that parse, and gives each mount its own clone to edit. - createCompositionCssScoper scopes a sheet once around a placeholder scope shaped like the real ones, then substitutes each mount's scope. It falls back to scopeCssToComposition for any scope or id outside [\w.:-], a scope equal to the authored id, a sheet holding the placeholder, or a selector kept raw for a comment. A golden test pins the preview and render pages of a representative project (goldens written by the build before this change), and a fuzz test compares the scoper with scopeCssToComposition over random sheets and scopes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@hyperframes/core
Types, parsers, generators, compiler, linter, runtime, and frame adapters for the Hyperframes video framework.
Install
npm install @hyperframes/core
Most users don't need to install core directly — the CLI, producer, and studio packages depend on it internally.
What's inside
| Module | Description |
|---|---|
| Types | TimelineElement, CompositionSpec, Asset, canvas dimensions, defaults |
| Parsers | parseHtml — extract timeline elements from HTML; parseGsapScript — parse GSAP animations |
| Generators | generateHyperframesHtml — produce valid Hyperframes HTML from a composition spec |
| Compiler | compileTimingAttrs — resolve data-start / data-duration into absolute times |
| Linter | lintHyperframeHtml — validate Hyperframes HTML (missing attributes, overlapping tracks, etc.) |
| Runtime | IIFE script injected into the browser — manages seek, media playback, and the window.__hf protocol |
| Frame Adapters | Pluggable animation drivers (GSAP, Lottie, CSS, or custom) |
Generated composition trust
Composition generators require trusted authors for code-bearing inputs. styles and
generateHyperframesStyles preserve authored CSS, which can load external resources.
animations may contain __raw: values that are emitted as JavaScript;
includeScripts: true includes executable timeline code. serializeGsapAnimations
also accepts raw preamble, postamble, and a code-bearing timelineVar. Never fill
these inputs with untrusted data. Attribute encoding and closing-tag containment
are not a sandbox; render untrusted compositions in an appropriately isolated
execution environment and never serve them on a privileged origin.
Text content retains the supported inline-formatting sanitizer contract. The clip parser intentionally flattens inner formatting to text, so parse/generate is not a lossless replacement for editing the source HTML.
Frame Adapters
A frame adapter tells the engine how to seek your animation to a specific frame:
import { createGSAPFrameAdapter } from "@hyperframes/core";
const adapter = createGSAPFrameAdapter({
getTimeline: () => gsap.timeline(),
compositionId: "my-video",
});
Implement FrameAdapter for custom animation runtimes:
import type { FrameAdapter } from "@hyperframes/core";
const myAdapter: FrameAdapter = {
id: "my-adapter",
getDurationFrames: () => 300,
seekFrame: (frame) => {
/* seek your animation */
},
};
Parsing and generating HTML
import { parseHtml, generateHyperframesHtml } from "@hyperframes/core";
const { elements, metadata } = parseHtml(htmlString);
const html = generateHyperframesHtml(spec);
Linting
import { lintHyperframeHtml } from "@hyperframes/core/lint";
const result = lintHyperframeHtml(htmlString);
// result.findings: { severity, message, elementId }[]
Documentation
Full documentation: hyperframes.heygen.com/packages/core
Related packages
@hyperframes/engine— rendering engine that drives the browser@hyperframes/producer— full render pipeline (capture + encode)hyperframes— CLI