diff --git a/packages/core/src/compiler/htmlBundler.test.ts b/packages/core/src/compiler/htmlBundler.test.ts index d41dfbd1c..0ee9eeaf7 100644 --- a/packages/core/src/compiler/htmlBundler.test.ts +++ b/packages/core/src/compiler/htmlBundler.test.ts @@ -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": ` + +
+
+
+ +`, + "compositions/scene.html": ``, + }); + + 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 // when no runtime URL was configured. An diff --git a/packages/core/src/compiler/htmlBundler.ts b/packages/core/src/compiler/htmlBundler.ts index 538f0632f..62ab48f9c 100644 --- a/packages/core/src/compiler/htmlBundler.ts +++ b/packages/core/src/compiler/htmlBundler.ts @@ -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); @@ -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); @@ -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)