Skip to content

Commit 88c1bbe

Browse files
authored
Ensure astro-island scripts render when using Astro.slots.render (#5326)
* Ensure astro-island scripts render when using Astro.slots.render * Adding a changeset
1 parent b211ead commit 88c1bbe

7 files changed

Lines changed: 60 additions & 7 deletions

File tree

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+
Fix omitted island hydration scripts in slots

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type {
1010
SSRLoadedRenderer,
1111
SSRResult,
1212
} from '../../@types/astro';
13-
import { renderSlot } from '../../runtime/server/index.js';
13+
import { renderSlot, stringifyChunk } from '../../runtime/server/index.js';
1414
import { renderJSX } from '../../runtime/server/jsx.js';
1515
import { AstroCookies } from '../cookies/index.js';
1616
import { LogOptions, warn } from '../logger/core.js';
@@ -118,11 +118,11 @@ class Slots {
118118
}
119119
}
120120
}
121-
const content = await renderSlot(this.#result, this.#slots[name]).then((res) =>
122-
res != null ? String(res) : res
123-
);
124-
if (cacheable) this.#cache.set(name, content);
125-
return content;
121+
const content = await renderSlot(this.#result, this.#slots[name]);
122+
const outHTML = stringifyChunk(this.#result, content);
123+
124+
if (cacheable) this.#cache.set(name, outHTML);
125+
return outHTML;
126126
}
127127
}
128128

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
getPrescripts,
99
PrescriptType,
1010
} from '../scripts.js';
11+
import { isSlotString, type SlotString } from './slot.js';
1112

1213
export const Fragment = Symbol.for('astro:fragment');
1314
export const Renderer = Symbol.for('astro:renderer');
@@ -18,7 +19,7 @@ export const decoder = new TextDecoder();
1819
// Rendering produces either marked strings of HTML or instructions for hydration.
1920
// These directive instructions bubble all the way up to renderPage so that we
2021
// can ensure they are added only once, and as soon as possible.
21-
export function stringifyChunk(result: SSRResult, chunk: string | RenderInstruction) {
22+
export function stringifyChunk(result: SSRResult, chunk: string | SlotString | RenderInstruction) {
2223
switch ((chunk as any).type) {
2324
case 'directive': {
2425
const { hydration } = chunk as RenderInstruction;
@@ -39,6 +40,18 @@ export function stringifyChunk(result: SSRResult, chunk: string | RenderInstruct
3940
}
4041
}
4142
default: {
43+
if(isSlotString(chunk as string)) {
44+
let out = '';
45+
const c = (chunk as SlotString);
46+
if(c.instructions) {
47+
for(const instr of c.instructions) {
48+
out += stringifyChunk(result, instr);
49+
}
50+
}
51+
out += chunk.toString();
52+
return out;
53+
}
54+
4255
return chunk.toString();
4356
}
4457
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,22 @@ import type { RenderInstruction } from './types.js';
44
import { HTMLString, markHTMLString } from '../escape.js';
55
import { renderChild } from './any.js';
66

7+
const slotString = Symbol.for('astro:slot-string');
8+
79
export class SlotString extends HTMLString {
810
public instructions: null | RenderInstruction[];
11+
public [slotString]: boolean;
912
constructor(content: string, instructions: null | RenderInstruction[]) {
1013
super(content);
1114
this.instructions = instructions;
15+
this[slotString] = true;
1216
}
1317
}
1418

19+
export function isSlotString(str: string): str is any {
20+
return !!(str as any)[slotString];
21+
}
22+
1523
export async function renderSlot(_result: any, slotted: string, fallback?: any): Promise<string> {
1624
if (slotted) {
1725
let iterator = renderChild(slotted);

packages/astro/test/astro-slots-nested.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,10 @@ describe('Nested Slots', () => {
1717
const scriptInTemplate = $($('template')[0].children[0]).find('script');
1818
expect(scriptInTemplate).to.have.a.lengthOf(0, 'script defined outside of the inner template');
1919
});
20+
21+
it('Slots rendered via Astro.slots.render have the hydration script', async () => {
22+
const html = await fixture.readFile('/component-slot/index.html');
23+
const $ = cheerio.load(html);
24+
expect($('script')).to.have.a.lengthOf(1, 'script rendered');
25+
});
2026
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
const content = await Astro.slots.render('default');
3+
---
4+
<Fragment set:html={content} />
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
import SlotRender from '../components/SlotRender.astro'
3+
import Inner from '../components/Inner'
4+
---
5+
6+
<html lang="en">
7+
<head>
8+
<title>Testing</title>
9+
</head>
10+
<body>
11+
<main>
12+
<SlotRender>
13+
<Inner client:load />
14+
</SlotRender>
15+
</main>
16+
</body>
17+
</html>

0 commit comments

Comments
 (0)