diff --git a/packages/core/src/compiler/compositionScoping.test.ts b/packages/core/src/compiler/compositionScoping.test.ts
index 07c7faf01..5a0a0cd3a 100644
--- a/packages/core/src/compiler/compositionScoping.test.ts
+++ b/packages/core/src/compiler/compositionScoping.test.ts
@@ -1,6 +1,10 @@
import { describe, expect, it, vi } from "vitest";
import { parseHTML } from "linkedom";
-import { scopeCssToComposition, wrapScopedCompositionScript } from "./compositionScoping";
+import {
+ scopeCssToComposition,
+ wrapInlineScriptWithErrorBoundary,
+ wrapScopedCompositionScript,
+} from "./compositionScoping";
describe("composition scoping", () => {
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');
});
+ it("wraps scoped composition script source as a string literal", () => {
+ const wrapped = wrapScopedCompositionScript(
+ 'window.payload = "";',
+ "scene",
+ );
+
+ expect(wrapped).toContain('Function("document", "gsap", "window", "__hyperframes", ');
+ expect(wrapped).toContain('\\"\\"');
+ });
+
+ it("wraps unscoped composition script source as a string literal", () => {
+ const wrapped = wrapInlineScriptWithErrorBoundary(
+ 'window.payload = "";',
+ "[HyperFrames] composition script error:",
+ );
+
+ expect(wrapped).toContain("Function(");
+ expect(wrapped).toContain('\\"\\"');
+ });
+
it("rewrites #id CSS selectors to [data-hf-authored-id] when authoredRootId is provided", () => {
const scoped = scopeCssToComposition(
`#intro { background: #111; }
diff --git a/packages/core/src/compiler/compositionScoping.ts b/packages/core/src/compiler/compositionScoping.ts
index 280fe5df8..72e9cbbf8 100644
--- a/packages/core/src/compiler/compositionScoping.ts
+++ b/packages/core/src/compiler/compositionScoping.ts
@@ -216,6 +216,7 @@ export function wrapScopedCompositionScript(
const authoredRootIdFormsLiteral = JSON.stringify(
getAuthoredRootIdSelectorForms(authoredRootId?.trim() || ""),
);
+ const sourceLiteral = JSON.stringify(source);
return `(function(){
var __hfCompId = ${compositionIdLiteral};
var __hfTimelineCompId = ${timelineCompositionIdLiteral};
@@ -485,9 +486,8 @@ export function wrapScopedCompositionScript(
});
var __hfRun = function() {
try {
- (function(document, gsap, window, __hyperframes) {
-${source}
- }).call(window, __hfScopedDocument, __hfScopedGsap, __hfScopedWindow, __hfScopedHyperframes);
+ var __hfScript = Function("document", "gsap", "window", "__hyperframes", ${sourceLiteral});
+ __hfScript.call(window, __hfScopedDocument, __hfScopedGsap, __hfScopedWindow, __hfScopedHyperframes);
} catch (_err) {
console.error(__hfErrorLabel, __hfCompId, _err);
}
@@ -496,3 +496,7 @@ ${source}
__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); } })();`;
+}
diff --git a/packages/core/src/compiler/htmlBundler.test.ts b/packages/core/src/compiler/htmlBundler.test.ts
index 8981c1ea5..f955ef5ea 100644
--- a/packages/core/src/compiler/htmlBundler.test.ts
+++ b/packages/core/src/compiler/htmlBundler.test.ts
@@ -758,7 +758,8 @@ describe("bundleToSingleHtml", () => {
expect(bundled).toContain('[data-composition-id="scene"] .title { color: red; }');
expect(bundled).toContain("new Proxy(window.document");
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 () => {
diff --git a/packages/core/src/compiler/htmlBundler.ts b/packages/core/src/compiler/htmlBundler.ts
index f4442a955..de590c850 100644
--- a/packages/core/src/compiler/htmlBundler.ts
+++ b/packages/core/src/compiler/htmlBundler.ts
@@ -8,7 +8,11 @@ import {
stripEmbeddedRuntimeScripts,
} from "./htmlDocument";
// rewriteSubCompPaths functions are used by inlineSubCompositions (shared module)
-import { scopeCssToComposition, wrapScopedCompositionScript } from "./compositionScoping";
+import {
+ scopeCssToComposition,
+ wrapInlineScriptWithErrorBoundary,
+ wrapScopedCompositionScript,
+} from "./compositionScoping";
import { validateHyperframeHtmlContract } from "./staticGuard";
import { getHyperframeRuntimeScript } from "../generated/runtime-inline";
import { readDeclaredDefaults } from "../runtime/getVariables";
@@ -824,7 +828,10 @@ export async function bundleToSingleHtml(
runtimeCompId || compId,
authoredRootId,
)
- : `(function(){ try { ${scriptEl.textContent || ""} } catch (_err) { console.error('[HyperFrames] composition script error:', _err); } })();`,
+ : wrapInlineScriptWithErrorBoundary(
+ scriptEl.textContent || "",
+ "[HyperFrames] composition script error:",
+ ),
);
}
scriptEl.remove();
@@ -875,7 +882,10 @@ export async function bundleToSingleHtml(
runtimeScope,
runtimeCompId || compId,
)
- : `(function(){ try { ${scriptEl.textContent || ""} } catch (_err) { console.error('[HyperFrames] composition script error:', _err); } })();`,
+ : wrapInlineScriptWithErrorBoundary(
+ scriptEl.textContent || "",
+ "[HyperFrames] composition script error:",
+ ),
);
}
scriptEl.remove();
diff --git a/packages/core/src/compiler/inlineSubCompositions.ts b/packages/core/src/compiler/inlineSubCompositions.ts
index 647e51988..00f09f268 100644
--- a/packages/core/src/compiler/inlineSubCompositions.ts
+++ b/packages/core/src/compiler/inlineSubCompositions.ts
@@ -13,7 +13,11 @@ import {
rewriteCssAssetUrls,
rewriteInlineStyleAssetUrls,
} from "./rewriteSubCompPaths";
-import { scopeCssToComposition, wrapScopedCompositionScript } from "./compositionScoping";
+import {
+ scopeCssToComposition,
+ wrapInlineScriptWithErrorBoundary,
+ wrapScopedCompositionScript,
+} from "./compositionScoping";
// ---------------------------------------------------------------------------
// Public interface
@@ -287,7 +291,7 @@ export function inlineSubCompositions(
runtimeCompId || scopeCompId,
authoredRootId,
)
- : `(function(){ try { ${s.textContent || ""} } catch (_err) { console.error(${JSON.stringify(scriptErrorLabel)}, _err); } })();`;
+ : wrapInlineScriptWithErrorBoundary(s.textContent || "", scriptErrorLabel);
scripts.push(wrappedScript);
scriptItems.push({ kind: "inline", content: wrappedScript });
}