mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 23:00:03 +00:00
fix(core): consolidate external asset and dependency preservation (#2410)
## Summary - preserve external SVG fragment references during bundling - preserve external module scripts and serve `.mjs` with a JavaScript MIME type - retain template-head stylesheets when mounting sub-compositions - add regression coverage across compiler runtime and file-server paths Consolidates and replaces #2390, #2297, and #2375. ## Verification - core compiler/runtime tests: 89 passed - producer file-server tests: 48 passed - core, producer, engine, and CLI typechecks passed - `git diff --check`
This commit is contained in:
@@ -95,6 +95,34 @@ describe("bundleToSingleHtml", () => {
|
||||
expect(bundled).not.toContain("./bg.svg");
|
||||
});
|
||||
|
||||
it("preserves external SVG fragment references used by <use>", async () => {
|
||||
const spriteSvg = `<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="patch-head" viewBox="0 0 10 10"><circle cx="5" cy="5" r="4" /></symbol>
|
||||
</svg>`;
|
||||
const dir = makeTempProject({
|
||||
"index.html": `<!doctype html><html><body>
|
||||
<div data-composition-id="main" data-width="320" data-height="180" data-start="0" data-duration="1">
|
||||
<svg>
|
||||
<use id="href-use" href="assets/patch.svg#patch-head"></use>
|
||||
<use id="xlink-use" xlink:href="assets/patch.svg#patch-head"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<script>window.__timelines = window.__timelines || {}; window.__timelines.main = {}</script>
|
||||
</body></html>`,
|
||||
"assets/patch.svg": spriteSvg,
|
||||
});
|
||||
|
||||
const bundled = await bundleToSingleHtml(dir);
|
||||
const { document } = parseHTML(bundled);
|
||||
expect(document.getElementById("href-use")?.getAttribute("href")).toBe(
|
||||
"assets/patch.svg#patch-head",
|
||||
);
|
||||
expect(document.getElementById("xlink-use")?.getAttribute("xlink:href")).toBe(
|
||||
"assets/patch.svg#patch-head",
|
||||
);
|
||||
expect(bundled).not.toContain("data:image/svg+xml;base64");
|
||||
});
|
||||
|
||||
it("does not merge author scripts into the runtime bootstrap placeholder", async () => {
|
||||
const dir = makeTempProject({
|
||||
"index.html": `<!doctype html>
|
||||
@@ -405,6 +433,22 @@ describe("bundleToSingleHtml", () => {
|
||||
expect(bundled).not.toContain('src="vendor/effect-plugin.js"');
|
||||
});
|
||||
|
||||
it("preserves local module scripts and their import base URL", async () => {
|
||||
const dir = makeTempProject({
|
||||
"index.html": `<!doctype html><html><body>
|
||||
<div data-composition-id="main" data-start="0" data-duration="1"></div>
|
||||
<script type="module" src="./module.js"></script>
|
||||
</body></html>`,
|
||||
"module.js": `import { value } from "./value.js"; window.result = value;`,
|
||||
"value.js": `export const value = "loaded";`,
|
||||
});
|
||||
|
||||
const bundled = await bundleToSingleHtml(dir);
|
||||
|
||||
expect(bundled).toMatch(/<script\b[^>]*\btype="module"[^>]*\bsrc="\.\/module\.js"/);
|
||||
expect(bundled).not.toContain('import { value } from "./value.js"');
|
||||
});
|
||||
|
||||
it("preserves local sub-composition script order before inline scene scripts", async () => {
|
||||
const dir = makeTempProject({
|
||||
"index.html": `<!doctype html>
|
||||
|
||||
@@ -310,6 +310,16 @@ function maybeInlineRelativeAssetUrl(urlValue: string, projectDir: string): stri
|
||||
return appendSuffixToUrl(dataUrl, suffix);
|
||||
}
|
||||
|
||||
function isExternalSvgFragmentUse(el: Element, attr: string, urlValue: string): boolean {
|
||||
if (el.tagName.toLowerCase() !== "use") return false;
|
||||
if (attr !== "href" && attr !== "xlink:href") return false;
|
||||
if (!isRelativeUrl(urlValue)) return false;
|
||||
const hashIdx = urlValue.indexOf("#");
|
||||
if (hashIdx <= 0) return false;
|
||||
const pathBeforeFragment = urlValue.slice(0, hashIdx).split("?", 1)[0] ?? "";
|
||||
return pathBeforeFragment.toLowerCase().endsWith(".svg");
|
||||
}
|
||||
|
||||
function warnColorGradingLutNotInlined(lutSrc: string): void {
|
||||
const trimmed = lutSrc.trim();
|
||||
if (!isRelativeUrl(trimmed)) return;
|
||||
@@ -843,6 +853,10 @@ export async function bundleToSingleHtml(
|
||||
for (const el of [...document.querySelectorAll("script[src]")]) {
|
||||
const src = el.getAttribute("src");
|
||||
if (!src || !isRelativeUrl(src)) continue;
|
||||
// Module scripts can contain static imports whose resolution is relative
|
||||
// to the script URL. Folding their source into a classic inline script
|
||||
// both drops module semantics and changes the import base URL.
|
||||
if ((el.getAttribute("type") || "").trim().toLowerCase() === "module") continue;
|
||||
const jsPath = resolveEntryPath(src);
|
||||
const js = jsPath ? safeReadFile(jsPath) : null;
|
||||
if (js == null) continue;
|
||||
@@ -1070,6 +1084,11 @@ export async function bundleToSingleHtml(
|
||||
for (const attr of ["src", "href", "poster", "xlink:href"] as const) {
|
||||
const value = el.getAttribute(attr);
|
||||
if (!value) continue;
|
||||
// Chromium requires external SVG <use> fragments to be same-origin with
|
||||
// the document. Converting the sprite to a data: URL makes it an opaque
|
||||
// origin and triggers "Unsafe attempt to load URL ... from frame".
|
||||
// Keep the project-relative URL; render/check servers already expose it.
|
||||
if (isExternalSvgFragmentUse(el, attr, value)) continue;
|
||||
const inlined = maybeInlineRelativeAssetUrl(value, projectDir);
|
||||
if (inlined) el.setAttribute(attr, inlined);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user