diff --git a/packages/core/src/compiler/htmlBundler.ts b/packages/core/src/compiler/htmlBundler.ts index 3512b4b17..1bcbfbbf6 100644 --- a/packages/core/src/compiler/htmlBundler.ts +++ b/packages/core/src/compiler/htmlBundler.ts @@ -813,7 +813,8 @@ export async function bundleToSingleHtml( } for (const link of compExternalLinks) { - if (!document.querySelector(`link[href="${link.href}"]`)) { + const escapedHref = link.href.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); + if (!document.querySelector(`link[href="${escapedHref}"]`)) { const linkEl = document.createElement("link"); linkEl.setAttribute("rel", link.rel); linkEl.setAttribute("href", link.href); diff --git a/packages/core/src/compiler/inlineSubCompositions.test.ts b/packages/core/src/compiler/inlineSubCompositions.test.ts index 7e91f165e..da71c2afe 100644 --- a/packages/core/src/compiler/inlineSubCompositions.test.ts +++ b/packages/core/src/compiler/inlineSubCompositions.test.ts @@ -131,6 +131,72 @@ describe("inlineSubCompositions – #ID selector scoping divergence", () => { expect(scopedCss).toContain('[data-hf-authored-id="intro"]'); }); + it("extracts elements from sub-composition with original rel and crossorigin", () => { + const subCompWithLinks = ` + + + + + +
+ Hello +
+`; + + const document = makeHostDocument("captions"); + const host = document.querySelector('[data-composition-src="intro.html"]')!; + + const result = inlineSubCompositions(document, [host], { + resolveHtml: () => subCompWithLinks, + parseHtml: (html) => parseHTML(html).document, + }); + + expect(result.externalLinks).toHaveLength(3); + expect(result.externalLinks[0]).toEqual({ + href: "https://fonts.googleapis.com", + rel: "preconnect", + crossorigin: undefined, + }); + expect(result.externalLinks[1]).toEqual({ + href: "https://fonts.gstatic.com", + rel: "preconnect", + crossorigin: "", + }); + expect(result.externalLinks[2]).toEqual({ + href: "https://fonts.googleapis.com/css2?family=Montserrat:wght@800&display=swap", + rel: "stylesheet", + crossorigin: undefined, + }); + }); + + it("deduplicates link hrefs across multiple sub-compositions", () => { + const subComp = ` + + + +
A
+`; + + const { document } = parseHTML(` + +
+
+
+
+`); + const hosts = Array.from(document.querySelectorAll("[data-composition-src]")); + + const result = inlineSubCompositions(document, hosts, { + resolveHtml: () => subComp, + parseHtml: (html) => parseHTML(html).document, + }); + + expect(result.externalLinks).toHaveLength(1); + expect(result.externalLinks[0]!.href).toBe( + "https://fonts.googleapis.com/css2?family=Montserrat:wght@800", + ); + }); + it("producer path propagates data-hf-authored-id to host when inner root has id", () => { const document = makeHostDocument("intro"); const host = document.querySelector('[data-composition-src="intro.html"]')!; diff --git a/packages/producer/src/services/htmlCompiler.ts b/packages/producer/src/services/htmlCompiler.ts index 06dab2ebc..8108eab59 100644 --- a/packages/producer/src/services/htmlCompiler.ts +++ b/packages/producer/src/services/htmlCompiler.ts @@ -614,7 +614,8 @@ function inlineSubCompositions( if (result.externalLinks.length && head) { for (const link of result.externalLinks) { - if (document.querySelector(`link[href="${link.href}"]`)) continue; + const escapedHref = link.href.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); + if (document.querySelector(`link[href="${escapedHref}"]`)) continue; const el = document.createElement("link"); el.setAttribute("rel", link.rel); el.setAttribute("href", link.href);