Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions packages/core/src/compiler/htmlBundler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,35 @@ describe("bundleToSingleHtml", () => {
expect(bundled).not.toContain("SECRET_MARKER_LEAKED");
});

it("bundles a sub-composition external script whose src contains a double quote", async () => {
// The external-script dedup interpolated src into a `script[src="..."]`
// selector unescaped, so a `"` in the URL (a quoted query param) built a
// malformed selector that threw in css-select and aborted the whole bundle.
// The sibling link[href] path already escapes; align the script path.
const quotedSrc = `https://cdn.example.com/lib.js?cb="x`;
const dir = makeTempProject({
"index.html": `<!doctype html>
<html><body>
<div id="root" data-composition-id="main" data-width="1920" data-height="1080">
<div id="scene-host"
data-composition-id="scene"
data-composition-src="compositions/scene.html"
data-start="0" data-duration="5"></div>
</div>
<script>window.__timelines={};</script>
</body></html>`,
"compositions/scene.html": `<template id="scene-template">
<div data-composition-id="scene" data-width="1920" data-height="1080">
<script src='${quotedSrc}'></script>
</div>
</template>`,
});

const bundled = await bundleToSingleHtml(dir);
// Does not throw, and the external script survives into the bundle.
expect(bundled).toContain(`cb=`);
});

it("produces a self-contained runtime script when no HYPERFRAME_RUNTIME_URL is set", async () => {
// Regression guard: hf#XXX. The bundler used to emit
// <script ... src=""></script> when no runtime URL was configured. An
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/compiler/htmlBundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ export interface BundleOptions {
*/

function ensureExternalScriptTag(doc: Document, src: string): void {
if (doc.querySelector(`script[src="${src}"]`)) return;
if (doc.querySelector(`script${cssAttributeSelector("src", src)}`)) return;
const el = doc.createElement("script");
el.setAttribute("src", src);
doc.body.appendChild(el);
Expand Down Expand Up @@ -825,7 +825,7 @@ export async function bundleToSingleHtml(
continue;
}
}
if (!document.querySelector(`script[src="${extSrc}"]`)) {
if (!document.querySelector(`script${cssAttributeSelector("src", extSrc)}`)) {
const extScript = document.createElement("script");
extScript.setAttribute("src", extSrc);
document.body.appendChild(extScript);
Expand Down Expand Up @@ -857,7 +857,7 @@ export async function bundleToSingleHtml(
const hostIdentity = hostIdentityByElement.get(host);
const runtimeCompId = hostIdentity?.runtimeCompositionId || compId;
const innerDoc = parseHTMLContent(templateHtml);
const innerRoot = innerDoc.querySelector(`[data-composition-id="${compId}"]`);
const innerRoot = innerDoc.querySelector(cssAttributeSelector("data-composition-id", compId));
const authoredRootId = innerRoot?.getAttribute("id")?.trim() || null;
const runtimeScope = runtimeCompId
? cssAttributeSelector("data-composition-id", runtimeCompId)
Expand Down
Loading