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);