Skip to content

Commit defad33

Browse files
P4tt4teematipico
andauthoredMar 18, 2025··
fix: renderToString function not render properly nested slots when they are components (#13432)
* Fix/render to string slots (#1) * add force HTMLString with local tests * remove local test & logs * remove test counter in basic exemple * re-add test & create SlotString instead of HTMLString * add test of a nested rendered component inside renderToString slots function * add changeset on astro package * Apply suggestions from code review --------- Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> Co-authored-by: ematipico <602478+ematipico@users.noreply.github.com>
1 parent 7783dbf commit defad33

File tree

4 files changed

+41
-0
lines changed

4 files changed

+41
-0
lines changed
 

‎.changeset/wide-carrots-dream.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fix an issue in the Container API, where the `renderToString` function doesn't render adequately nested slots when they are components.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
interface Props {
3+
count?: number;
4+
}
5+
6+
let { count = 0 } = Astro.props;
7+
---
8+
9+
<p id="counter">{count}</p>

‎examples/container-with-vitest/test/Card.test.ts

+14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
22
import { expect, test } from 'vitest';
33
import Card from '../src/components/Card.astro';
4+
import CounterLight from '../src/components/CounterLight.astro';
45

56
test('Card with slots', async () => {
67
const container = await AstroContainer.create();
@@ -13,3 +14,16 @@ test('Card with slots', async () => {
1314
expect(result).toContain('This is a card');
1415
expect(result).toContain('Card content');
1516
});
17+
18+
test('Card with nested CounterLight', async () => {
19+
const container = await AstroContainer.create();
20+
const counterLight = await container.renderToString(CounterLight, { props: { count: 1 } });
21+
const result = await container.renderToString(Card, {
22+
slots: {
23+
default: counterLight,
24+
},
25+
});
26+
27+
expect(result).toContain('This is a card');
28+
expect(result).toContain(counterLight);
29+
});

‎packages/astro/src/container/index.ts

+13
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type {
2626
SSRResult,
2727
} from '../types/public/internal.js';
2828
import { ContainerPipeline } from './pipeline.js';
29+
import { SlotString } from '../runtime/server/render/slot.js';
2930

3031
/** Public type, used for integrations to define a renderer for the container API */
3132
export type ContainerRenderer = {
@@ -474,6 +475,10 @@ export class experimental_AstroContainer {
474475
component: AstroComponentFactory,
475476
options: ContainerRenderOptions = {},
476477
): Promise<string> {
478+
if (options.slots) {
479+
options.slots = markAllSlotsAsSlotString(options.slots);
480+
}
481+
477482
const response = await this.renderToResponse(component, options);
478483
return await response.text();
479484
}
@@ -588,3 +593,11 @@ export class experimental_AstroContainer {
588593
function isNamedRenderer(renderer: any): renderer is NamedSSRLoadedRendererValue {
589594
return !!renderer?.name;
590595
}
596+
597+
function markAllSlotsAsSlotString(slots: Record<string, any>): Record<string, any> {
598+
const markedSlots: Record<string, any> = {};
599+
for (const slotName in slots) {
600+
markedSlots[slotName] = new SlotString(slots[slotName], null);
601+
}
602+
return markedSlots;
603+
}

0 commit comments

Comments
 (0)
Please sign in to comment.