fix(core): wrap bundled composition scripts as string literals

This commit is contained in:
func25
2026-05-25 15:41:13 +07:00
parent e101b47be4
commit 3bb0d1efd9
5 changed files with 53 additions and 10 deletions
@@ -1,6 +1,10 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { parseHTML } from "linkedom"; import { parseHTML } from "linkedom";
import { scopeCssToComposition, wrapScopedCompositionScript } from "./compositionScoping"; import {
scopeCssToComposition,
wrapInlineScriptWithErrorBoundary,
wrapScopedCompositionScript,
} from "./compositionScoping";
describe("composition scoping", () => { describe("composition scoping", () => {
it("scopes regular selectors while preserving global at-rules", () => { it("scopes regular selectors while preserving global at-rules", () => {
@@ -568,6 +572,26 @@ window.__afterTimeline = window.__timelines.scene;
expect(scoped).toContain('[data-composition-id="chrome-overlay"] .child-element'); expect(scoped).toContain('[data-composition-id="chrome-overlay"] .child-element');
}); });
it("wraps scoped composition script source as a string literal", () => {
const wrapped = wrapScopedCompositionScript(
'window.payload = "</script><script>window.pwned = true;</script>";',
"scene",
);
expect(wrapped).toContain('Function("document", "gsap", "window", "__hyperframes", ');
expect(wrapped).toContain('\\"</script><script>window.pwned = true;</script>\\"');
});
it("wraps unscoped composition script source as a string literal", () => {
const wrapped = wrapInlineScriptWithErrorBoundary(
'window.payload = "</script><script>window.pwned = true;</script>";',
"[HyperFrames] composition script error:",
);
expect(wrapped).toContain("Function(");
expect(wrapped).toContain('\\"</script><script>window.pwned = true;</script>\\"');
});
it("rewrites #id CSS selectors to [data-hf-authored-id] when authoredRootId is provided", () => { it("rewrites #id CSS selectors to [data-hf-authored-id] when authoredRootId is provided", () => {
const scoped = scopeCssToComposition( const scoped = scopeCssToComposition(
`#intro { background: #111; } `#intro { background: #111; }
@@ -216,6 +216,7 @@ export function wrapScopedCompositionScript(
const authoredRootIdFormsLiteral = JSON.stringify( const authoredRootIdFormsLiteral = JSON.stringify(
getAuthoredRootIdSelectorForms(authoredRootId?.trim() || ""), getAuthoredRootIdSelectorForms(authoredRootId?.trim() || ""),
); );
const sourceLiteral = JSON.stringify(source);
return `(function(){ return `(function(){
var __hfCompId = ${compositionIdLiteral}; var __hfCompId = ${compositionIdLiteral};
var __hfTimelineCompId = ${timelineCompositionIdLiteral}; var __hfTimelineCompId = ${timelineCompositionIdLiteral};
@@ -485,9 +486,8 @@ export function wrapScopedCompositionScript(
}); });
var __hfRun = function() { var __hfRun = function() {
try { try {
(function(document, gsap, window, __hyperframes) { var __hfScript = Function("document", "gsap", "window", "__hyperframes", ${sourceLiteral});
${source} __hfScript.call(window, __hfScopedDocument, __hfScopedGsap, __hfScopedWindow, __hfScopedHyperframes);
}).call(window, __hfScopedDocument, __hfScopedGsap, __hfScopedWindow, __hfScopedHyperframes);
} catch (_err) { } catch (_err) {
console.error(__hfErrorLabel, __hfCompId, _err); console.error(__hfErrorLabel, __hfCompId, _err);
} }
@@ -496,3 +496,7 @@ ${source}
__hfRun(); __hfRun();
})();`; })();`;
} }
export function wrapInlineScriptWithErrorBoundary(source: string, errorLabel: string): string {
return `(function(){ try { Function(${JSON.stringify(source)}).call(window); } catch (_err) { console.error(${JSON.stringify(errorLabel)}, _err); } })();`;
}
@@ -758,7 +758,8 @@ describe("bundleToSingleHtml", () => {
expect(bundled).toContain('[data-composition-id="scene"] .title { color: red; }'); expect(bundled).toContain('[data-composition-id="scene"] .title { color: red; }');
expect(bundled).toContain("new Proxy(window.document"); expect(bundled).toContain("new Proxy(window.document");
expect(bundled).toContain("new Proxy(__hfBaseGsap"); expect(bundled).toContain("new Proxy(__hfBaseGsap");
expect(bundled).toContain('tl.to(".title"'); expect(bundled).toContain('Function("document", "gsap", "window", "__hyperframes",');
expect(bundled).toContain("tl.to('.title'");
}); });
it("isolates sibling instances of the same external sub-composition", async () => { it("isolates sibling instances of the same external sub-composition", async () => {
+13 -3
View File
@@ -8,7 +8,11 @@ import {
stripEmbeddedRuntimeScripts, stripEmbeddedRuntimeScripts,
} from "./htmlDocument"; } from "./htmlDocument";
// rewriteSubCompPaths functions are used by inlineSubCompositions (shared module) // rewriteSubCompPaths functions are used by inlineSubCompositions (shared module)
import { scopeCssToComposition, wrapScopedCompositionScript } from "./compositionScoping"; import {
scopeCssToComposition,
wrapInlineScriptWithErrorBoundary,
wrapScopedCompositionScript,
} from "./compositionScoping";
import { validateHyperframeHtmlContract } from "./staticGuard"; import { validateHyperframeHtmlContract } from "./staticGuard";
import { getHyperframeRuntimeScript } from "../generated/runtime-inline"; import { getHyperframeRuntimeScript } from "../generated/runtime-inline";
import { readDeclaredDefaults } from "../runtime/getVariables"; import { readDeclaredDefaults } from "../runtime/getVariables";
@@ -824,7 +828,10 @@ export async function bundleToSingleHtml(
runtimeCompId || compId, runtimeCompId || compId,
authoredRootId, authoredRootId,
) )
: `(function(){ try { ${scriptEl.textContent || ""} } catch (_err) { console.error('[HyperFrames] composition script error:', _err); } })();`, : wrapInlineScriptWithErrorBoundary(
scriptEl.textContent || "",
"[HyperFrames] composition script error:",
),
); );
} }
scriptEl.remove(); scriptEl.remove();
@@ -875,7 +882,10 @@ export async function bundleToSingleHtml(
runtimeScope, runtimeScope,
runtimeCompId || compId, runtimeCompId || compId,
) )
: `(function(){ try { ${scriptEl.textContent || ""} } catch (_err) { console.error('[HyperFrames] composition script error:', _err); } })();`, : wrapInlineScriptWithErrorBoundary(
scriptEl.textContent || "",
"[HyperFrames] composition script error:",
),
); );
} }
scriptEl.remove(); scriptEl.remove();
@@ -13,7 +13,11 @@ import {
rewriteCssAssetUrls, rewriteCssAssetUrls,
rewriteInlineStyleAssetUrls, rewriteInlineStyleAssetUrls,
} from "./rewriteSubCompPaths"; } from "./rewriteSubCompPaths";
import { scopeCssToComposition, wrapScopedCompositionScript } from "./compositionScoping"; import {
scopeCssToComposition,
wrapInlineScriptWithErrorBoundary,
wrapScopedCompositionScript,
} from "./compositionScoping";
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Public interface // Public interface
@@ -287,7 +291,7 @@ export function inlineSubCompositions(
runtimeCompId || scopeCompId, runtimeCompId || scopeCompId,
authoredRootId, authoredRootId,
) )
: `(function(){ try { ${s.textContent || ""} } catch (_err) { console.error(${JSON.stringify(scriptErrorLabel)}, _err); } })();`; : wrapInlineScriptWithErrorBoundary(s.textContent || "", scriptErrorLabel);
scripts.push(wrappedScript); scripts.push(wrappedScript);
scriptItems.push({ kind: "inline", content: wrappedScript }); scriptItems.push({ kind: "inline", content: wrappedScript });
} }