0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-30 22:03:56 -05:00

Pass slots to server islands

This commit is contained in:
Matthew Phillips 2024-07-03 16:21:33 -04:00
parent 600cc65335
commit 41f286c3e9
7 changed files with 41 additions and 15 deletions

View file

@ -1,3 +1,4 @@
--- ---
--- ---
<h2 id="island">I am an island</h2> <h2 id="island">I am an island</h2>
<slot />

View file

@ -7,6 +7,8 @@ import Island from '../components/Island.astro';
<!-- Head Stuff --> <!-- Head Stuff -->
</head> </head>
<body> <body>
<Island server:defer /> <Island server:defer>
<h3 id="children">children</h3>
</Island>
</body> </body>
</html> </html>

View file

@ -30,6 +30,13 @@ test.describe('Server islands', () => {
await expect(el, 'element rendered').toBeVisible(); await expect(el, 'element rendered').toBeVisible();
await expect(el, 'should have content').toHaveText('I am an island'); await expect(el, 'should have content').toHaveText('I am an island');
}); });
test('Slots are provided back to the server islands', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
let el = page.locator('#children');
await expect(el, 'element rendered').toBeVisible();
});
}); });
test.describe('Production', () => { test.describe('Production', () => {

View file

@ -1,6 +1,6 @@
import { renderComponent, renderTemplate, type AstroComponentFactory } from '../../runtime/server/index.js'; import { renderComponent, renderTemplate, type AstroComponentFactory, type ComponentSlots } from '../../runtime/server/index.js';
import type { APIRoute, ComponentInstance, ManifestData, RouteData, SSRManifest } from '../../@types/astro.js'; import type { ComponentInstance, ManifestData, RouteData, SSRManifest } from '../../@types/astro.js';
import type { ModuleLoader } from '../module-loader/loader.js'; import { createSlotValueFromString } from '../../runtime/server/render/slot.js';
export const SERVER_ISLAND_ROUTE = '/_server-islands/[name]'; export const SERVER_ISLAND_ROUTE = '/_server-islands/[name]';
export const SERVER_ISLAND_COMPONENT = '_server-islands.astro'; export const SERVER_ISLAND_COMPONENT = '_server-islands.astro';
@ -33,7 +33,7 @@ export function ensureServerIslandRoute(manifest: ManifestData) {
type RenderOptions = { type RenderOptions = {
componentExport: string; componentExport: string;
props: Record<string, any>; props: Record<string, any>;
slots: Record<string, any>; slots: Record<string, string>;
} }
export function createEndpoint(manifest: SSRManifest) { export function createEndpoint(manifest: SSRManifest) {
@ -59,10 +59,14 @@ export function createEndpoint(manifest: SSRManifest) {
} }
const props = data.props; const props = data.props;
const slots = data.slots;
const componentModule = await imp(); const componentModule = await imp();
const Component = (componentModule as any)[data.componentExport]; const Component = (componentModule as any)[data.componentExport];
const slots: ComponentSlots = {};
for(const prop in data.slots) {
slots[prop] = createSlotValueFromString(data.slots[prop]);
}
return renderTemplate`${renderComponent(result, 'Component', Component, props, slots)}`; return renderTemplate`${renderComponent(result, 'Component', Component, props, slots)}`;
} }

View file

@ -494,7 +494,7 @@ export async function renderComponent(
displayName: string, displayName: string,
Component: unknown, Component: unknown,
props: Record<string | number, any>, props: Record<string | number, any>,
slots: any = {} slots: ComponentSlots = {}
): Promise<RenderInstance> { ): Promise<RenderInstance> {
if (isPromise(Component)) { if (isPromise(Component)) {
Component = await Component.catch(handleCancellation); Component = await Component.catch(handleCancellation);

View file

@ -3,7 +3,7 @@ import type {
} from '../../../@types/astro.js'; } from '../../../@types/astro.js';
import { renderChild } from "./any.js"; import { renderChild } from "./any.js";
import type { RenderInstance } from "./common.js"; import type { RenderInstance } from "./common.js";
import { renderSlot, type ComponentSlotValue } from "./slot.js"; import { renderSlotToString, type ComponentSlots } from "./slot.js";
const internalProps = new Set([ const internalProps = new Set([
'server:component-path', 'server:component-path',
@ -18,9 +18,9 @@ export function containsServerDirective(props: Record<string | number, any>,) {
export function renderServerIsland( export function renderServerIsland(
result: SSRResult, result: SSRResult,
displayName: string, _displayName: string,
props: Record<string | number, any>, props: Record<string | number, any>,
slots: Record<string, ComponentSlotValue>, slots: ComponentSlots,
): RenderInstance { ): RenderInstance {
return { return {
async render(destination) { async render(destination) {
@ -42,8 +42,14 @@ export function renderServerIsland(
destination.write('<!--server-island-start-->') destination.write('<!--server-island-start-->')
// Render the slots // Render the slots
if(slots.fallback) { const renderedSlots: Record<string, string> = {};
await renderChild(destination, slots.fallback(result)); for(const name in slots) {
if(name !== 'fallback') {
const content = await renderSlotToString(result, slots[name]);
renderedSlots[name] = content.toString();
} else {
await renderChild(destination, slots.fallback(result));
}
} }
const hostId = crypto.randomUUID(); const hostId = crypto.randomUUID();
@ -55,7 +61,7 @@ let script = document.querySelector('script[data-island-id="${hostId}"]');
let data = { let data = {
componentExport, componentExport,
props: ${JSON.stringify(props)}, props: ${JSON.stringify(props)},
slot: ${JSON.stringify(slots)}, slots: ${JSON.stringify(renderedSlots)},
}; };
let response = await fetch('/_server-islands/${componentId}', { let response = await fetch('/_server-islands/${componentId}', {

View file

@ -1,8 +1,8 @@
import type { SSRResult } from '../../../@types/astro.js'; import type { SSRResult } from '../../../@types/astro.js';
import type { renderTemplate } from './astro/render-template.js'; import { renderTemplate } from './astro/render-template.js';
import type { RenderInstruction } from './instruction.js'; import type { RenderInstruction } from './instruction.js';
import { HTMLString, markHTMLString } from '../escape.js'; import { HTMLString, markHTMLString, unescapeHTML } from '../escape.js';
import { renderChild } from './any.js'; import { renderChild } from './any.js';
import { type RenderDestination, type RenderInstance, chunkToString } from './common.js'; import { type RenderDestination, type RenderInstance, chunkToString } from './common.js';
@ -103,3 +103,9 @@ export async function renderSlots(
} }
return { slotInstructions, children }; return { slotInstructions, children };
} }
export function createSlotValueFromString(content: string): ComponentSlotValue {
return function() {
return renderTemplate`${unescapeHTML(content)}`;
};
}