mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-02 19:57:13 +00:00
fix(producer): a mounted composition's local script runs after the inline script before it (#4425)
This commit is contained in:
@@ -2,13 +2,14 @@
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { compileForRender } from "./htmlCompiler.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true });
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
function project(files: Record<string, string>): string {
|
||||
@@ -44,4 +45,58 @@ describe("compileForRender script order", () => {
|
||||
expect(before).toBeLessThan(lib);
|
||||
expect(lib).toBeLessThan(after);
|
||||
});
|
||||
|
||||
it("keeps a mounted composition's local src script between the inline scripts around it", async () => {
|
||||
const dir = project({
|
||||
"index.html": `<!doctype html>
|
||||
<html><body>
|
||||
<div data-composition-id="root" data-width="320" data-height="180">
|
||||
<div id="block" data-composition-id="block" data-composition-src="compositions/block/block.html" data-width="320" data-height="180"></div>
|
||||
</div>
|
||||
</body></html>`,
|
||||
"compositions/block/block.html": `<!doctype html>
|
||||
<html><body>
|
||||
<div data-composition-id="block" data-width="320" data-height="180"></div>
|
||||
<script>window.MARK_BEFORE = 1;</script>
|
||||
<script src="assets/needs-before.js"></script>
|
||||
<script>window.MARK_AFTER = 1;</script>
|
||||
</body></html>`,
|
||||
"compositions/block/assets/needs-before.js": "void 0;",
|
||||
});
|
||||
const { html } = await compileForRender(dir, join(dir, "index.html"), join(dir, ".downloads"), {
|
||||
allowSystemFontCapture: false,
|
||||
});
|
||||
const before = html.indexOf("MARK_BEFORE");
|
||||
const lib = html.indexOf("compositions/block/assets/needs-before.js");
|
||||
const after = html.indexOf("MARK_AFTER");
|
||||
expect(before).toBeGreaterThan(-1);
|
||||
expect(lib).toBeGreaterThan(-1);
|
||||
expect(before).toBeLessThan(lib);
|
||||
expect(lib).toBeLessThan(after);
|
||||
});
|
||||
|
||||
it("still runs a mounted composition's CDN script before its inline scripts", async () => {
|
||||
vi.stubGlobal("fetch", async () => new Response("window.CDN_LIB = 1;"));
|
||||
const dir = project({
|
||||
"index.html": `<!doctype html>
|
||||
<html><body>
|
||||
<div data-composition-id="root" data-width="320" data-height="180">
|
||||
<div id="block" data-composition-id="block" data-composition-src="compositions/block/block.html" data-width="320" data-height="180"></div>
|
||||
</div>
|
||||
</body></html>`,
|
||||
"compositions/block/block.html": `<!doctype html>
|
||||
<html><body>
|
||||
<div data-composition-id="block" data-width="320" data-height="180"></div>
|
||||
<script>window.MARK_BEFORE = window.CDN_LIB;</script>
|
||||
<script src="https://cdn.example/lib.js"></script>
|
||||
</body></html>`,
|
||||
});
|
||||
const { html } = await compileForRender(dir, join(dir, "index.html"), join(dir, ".downloads"), {
|
||||
allowSystemFontCapture: false,
|
||||
});
|
||||
const lib = html.indexOf("window.CDN_LIB = 1");
|
||||
const before = html.indexOf("MARK_BEFORE");
|
||||
expect(lib).toBeGreaterThan(-1);
|
||||
expect(lib).toBeLessThan(before);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1056,12 +1056,16 @@ function inlineSubCompositions(
|
||||
head.appendChild(styleEl);
|
||||
}
|
||||
|
||||
// Inject external CDN scripts before inline scripts so plugins (e.g.
|
||||
// TextPlugin, ScrollTrigger) are registered before composition code runs.
|
||||
// Deduplicate against scripts already present in the document.
|
||||
// CDN and integrity-pinned scripts go first so plugins (e.g. TextPlugin,
|
||||
// ScrollTrigger) register before composition code, as in htmlBundler. A local
|
||||
// src script keeps its authored place among the inline scripts (see below).
|
||||
const isHoisted = (item: { src: string; integrity?: string }) =>
|
||||
Boolean(item.integrity?.trim()) || isNonRelativeUrl(item.src);
|
||||
if (body) {
|
||||
for (const item of result.scriptItems) {
|
||||
if (item.kind === "external") ensureExternalScriptTag(document, item.src, item);
|
||||
if (item.kind === "external" && isHoisted(item)) {
|
||||
ensureExternalScriptTag(document, item.src, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1074,13 +1078,24 @@ function inlineSubCompositions(
|
||||
// text (issue #2064). Same shared builder as the bundler so they stay in
|
||||
// lockstep.
|
||||
const variablesByCompScript = buildVariablesByCompScript(result.variablesByComp);
|
||||
const inlineScripts = variablesByCompScript
|
||||
? [variablesByCompScript, ...result.scripts]
|
||||
: result.scripts;
|
||||
if (inlineScripts.length && body) {
|
||||
const scriptEl = document.createElement("script");
|
||||
scriptEl.textContent = inlineScripts.join("\n;\n");
|
||||
body.appendChild(scriptEl);
|
||||
if (body) {
|
||||
let pending = variablesByCompScript ? [variablesByCompScript] : [];
|
||||
const flushInline = () => {
|
||||
if (!pending.length) return;
|
||||
const scriptEl = document.createElement("script");
|
||||
scriptEl.textContent = pending.join("\n;\n");
|
||||
body.appendChild(scriptEl);
|
||||
pending = [];
|
||||
};
|
||||
for (const item of result.scriptItems) {
|
||||
if (item.kind === "inline") {
|
||||
pending.push(item.content);
|
||||
} else if (!isHoisted(item)) {
|
||||
flushInline();
|
||||
ensureExternalScriptTag(document, item.src, item);
|
||||
}
|
||||
}
|
||||
flushInline();
|
||||
}
|
||||
|
||||
// Compile-time CSS custom properties (mirrors the preview bundler): root
|
||||
|
||||
Reference in New Issue
Block a user