Skip to content

Commit 7dbcbc8

Browse files
authored
Track added links/styles/scripts to prevent duplicates (#7843)
* temporary change * Track rendered styles and links to prevent duplicates * Adding a changeset * Fix names
1 parent 73eb4df commit 7dbcbc8

File tree

8 files changed

+62
-4
lines changed

8 files changed

+62
-4
lines changed

.changeset/yellow-olives-sing.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes head propagation regression

packages/astro/src/@types/astro.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2033,6 +2033,8 @@ export interface SSRMetadata {
20332033
headInTree: boolean;
20342034
extraHead: string[];
20352035
propagators: Map<AstroComponentFactory, AstroComponentInstance>;
2036+
// Used to key track of unique content; links and script tags
2037+
contentKeys: Set<string>;
20362038
}
20372039

20382040
/* Preview server stuff */

packages/astro/src/content/runtime.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
createComponent,
77
createHeadAndContent,
88
renderComponent,
9-
renderScriptElement,
9+
renderUniqueScriptElement,
1010
renderTemplate,
1111
renderUniqueStylesheet,
1212
unescapeHTML,
@@ -303,7 +303,7 @@ async function render({
303303
.join('');
304304
}
305305
if (Array.isArray(collectedScripts)) {
306-
scripts = collectedScripts.map((script: any) => renderScriptElement(script)).join('');
306+
scripts = collectedScripts.map((script: any) => renderUniqueScriptElement(result, script)).join('');
307307
}
308308

309309
let props = baseProps;

packages/astro/src/core/render/result.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ export function createResult(args: CreateResultArgs): SSRResult {
258258
headInTree: false,
259259
extraHead: [],
260260
propagators: new Map(),
261+
contentKeys: new Set(),
261262
},
262263
};
263264

packages/astro/src/runtime/server/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export {
2828
renderTemplate,
2929
renderToString,
3030
renderUniqueStylesheet,
31+
renderUniqueScriptElement,
3132
voidElementNames,
3233
} from './render/index.js';
3334
export type {

packages/astro/src/runtime/server/render/astro/render.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ export async function renderToReadableStream(
7171
// If the Astro component returns a Response on init, return that response
7272
if (templateResult instanceof Response) return templateResult;
7373

74+
if (isPage) {
75+
await bufferHeadContent(result);
76+
}
77+
7478
let renderedFirstPageChunk = false;
7579

7680
return new ReadableStream({
@@ -144,3 +148,19 @@ async function callComponentAsTemplateResultOrResponse(
144148

145149
return isHeadAndContent(factoryResult) ? factoryResult.content : factoryResult;
146150
}
151+
152+
// Recursively calls component instances that might have head content
153+
// to be propagated up.
154+
async function bufferHeadContent(result: SSRResult) {
155+
const iterator = result._metadata.propagators.values();
156+
while (true) {
157+
const { value, done } = iterator.next();
158+
if (done) {
159+
break;
160+
}
161+
const returnValue = await value.init(result);
162+
if (isHeadAndContent(returnValue)) {
163+
result._metadata.extraHead.push(returnValue.head);
164+
}
165+
}
166+
}

packages/astro/src/runtime/server/render/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ export { renderHTMLElement } from './dom.js';
66
export { maybeRenderHead, renderHead } from './head.js';
77
export { renderPage } from './page.js';
88
export { renderSlot, renderSlotToString, type ComponentSlots } from './slot.js';
9-
export { renderScriptElement, renderUniqueStylesheet } from './tags.js';
9+
export { renderScriptElement, renderUniqueScriptElement, renderUniqueStylesheet } from './tags.js';
1010
export type { RenderInstruction } from './types';
1111
export { addAttribute, defineScriptVars, voidElementNames } from './util.js';

packages/astro/src/runtime/server/render/tags.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,43 @@ export function renderScriptElement({ props, children }: SSRElement) {
99
});
1010
}
1111

12+
export function renderUniqueScriptElement(result: SSRResult, { props, children }: SSRElement) {
13+
if(Array.from(result.scripts).some(s => {
14+
if(s.props.type === props.type && s.props.src === props.src) {
15+
return true;
16+
}
17+
if(!props.src && s.children === children) return true;
18+
})) return '';
19+
const key = `script-${props.type}-${props.src}-${children}`;
20+
if(checkOrAddContentKey(result, key)) return '';
21+
return renderScriptElement({ props, children });
22+
23+
}
24+
1225
export function renderUniqueStylesheet(result: SSRResult, sheet: StylesheetAsset) {
1326
if (sheet.type === 'external') {
1427
if (Array.from(result.styles).some((s) => s.props.href === sheet.src)) return '';
15-
return renderElement('link', { props: { rel: 'stylesheet', href: sheet.src }, children: '' });
28+
const key = 'link-external-' + sheet.src;
29+
if(checkOrAddContentKey(result, key)) return '';
30+
return renderElement('link', {
31+
props: {
32+
rel: 'stylesheet',
33+
href: sheet.src
34+
},
35+
children: ''
36+
});
1637
}
1738

1839
if (sheet.type === 'inline') {
1940
if (Array.from(result.styles).some((s) => s.children.includes(sheet.content))) return '';
41+
const key = `link-inline-` + sheet.content;
42+
if(checkOrAddContentKey(result, key)) return '';
2043
return renderElement('style', { props: { type: 'text/css' }, children: sheet.content });
2144
}
2245
}
46+
47+
function checkOrAddContentKey(result: SSRResult, key: string): boolean {
48+
if(result._metadata.contentKeys.has(key)) return true;
49+
result._metadata.contentKeys.add(key);
50+
return false;
51+
}

0 commit comments

Comments
 (0)