Ensure astro-island scripts render when using Astro.slots.render (#5326)

* Ensure astro-island scripts render when using Astro.slots.render

* Adding a changeset
This commit is contained in:
Matthew Phillips 2022-11-08 12:15:32 -05:00 committed by GitHub
parent b211eadeff
commit 88c1bbe3a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 60 additions and 7 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fix omitted island hydration scripts in slots

View file

@ -10,7 +10,7 @@ import type {
SSRLoadedRenderer,
SSRResult,
} from '../../@types/astro';
import { renderSlot } from '../../runtime/server/index.js';
import { renderSlot, stringifyChunk } from '../../runtime/server/index.js';
import { renderJSX } from '../../runtime/server/jsx.js';
import { AstroCookies } from '../cookies/index.js';
import { LogOptions, warn } from '../logger/core.js';
@ -118,11 +118,11 @@ class Slots {
}
}
}
const content = await renderSlot(this.#result, this.#slots[name]).then((res) =>
res != null ? String(res) : res
);
if (cacheable) this.#cache.set(name, content);
return content;
const content = await renderSlot(this.#result, this.#slots[name]);
const outHTML = stringifyChunk(this.#result, content);
if (cacheable) this.#cache.set(name, outHTML);
return outHTML;
}
}

View file

@ -8,6 +8,7 @@ import {
getPrescripts,
PrescriptType,
} from '../scripts.js';
import { isSlotString, type SlotString } from './slot.js';
export const Fragment = Symbol.for('astro:fragment');
export const Renderer = Symbol.for('astro:renderer');
@ -18,7 +19,7 @@ export const decoder = new TextDecoder();
// Rendering produces either marked strings of HTML or instructions for hydration.
// These directive instructions bubble all the way up to renderPage so that we
// can ensure they are added only once, and as soon as possible.
export function stringifyChunk(result: SSRResult, chunk: string | RenderInstruction) {
export function stringifyChunk(result: SSRResult, chunk: string | SlotString | RenderInstruction) {
switch ((chunk as any).type) {
case 'directive': {
const { hydration } = chunk as RenderInstruction;
@ -39,6 +40,18 @@ export function stringifyChunk(result: SSRResult, chunk: string | RenderInstruct
}
}
default: {
if(isSlotString(chunk as string)) {
let out = '';
const c = (chunk as SlotString);
if(c.instructions) {
for(const instr of c.instructions) {
out += stringifyChunk(result, instr);
}
}
out += chunk.toString();
return out;
}
return chunk.toString();
}
}

View file

@ -4,14 +4,22 @@ import type { RenderInstruction } from './types.js';
import { HTMLString, markHTMLString } from '../escape.js';
import { renderChild } from './any.js';
const slotString = Symbol.for('astro:slot-string');
export class SlotString extends HTMLString {
public instructions: null | RenderInstruction[];
public [slotString]: boolean;
constructor(content: string, instructions: null | RenderInstruction[]) {
super(content);
this.instructions = instructions;
this[slotString] = true;
}
}
export function isSlotString(str: string): str is any {
return !!(str as any)[slotString];
}
export async function renderSlot(_result: any, slotted: string, fallback?: any): Promise<string> {
if (slotted) {
let iterator = renderChild(slotted);

View file

@ -17,4 +17,10 @@ describe('Nested Slots', () => {
const scriptInTemplate = $($('template')[0].children[0]).find('script');
expect(scriptInTemplate).to.have.a.lengthOf(0, 'script defined outside of the inner template');
});
it('Slots rendered via Astro.slots.render have the hydration script', async () => {
const html = await fixture.readFile('/component-slot/index.html');
const $ = cheerio.load(html);
expect($('script')).to.have.a.lengthOf(1, 'script rendered');
});
});

View file

@ -0,0 +1,4 @@
---
const content = await Astro.slots.render('default');
---
<Fragment set:html={content} />

View file

@ -0,0 +1,17 @@
---
import SlotRender from '../components/SlotRender.astro'
import Inner from '../components/Inner'
---
<html lang="en">
<head>
<title>Testing</title>
</head>
<body>
<main>
<SlotRender>
<Inner client:load />
</SlotRender>
</main>
</body>
</html>