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:
parent
600cc65335
commit
41f286c3e9
7 changed files with 41 additions and 15 deletions
|
@ -1,3 +1,4 @@
|
||||||
---
|
---
|
||||||
---
|
---
|
||||||
<h2 id="island">I am an island</h2>
|
<h2 id="island">I am an island</h2>
|
||||||
|
<slot />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
|
@ -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)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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}', {
|
||||||
|
|
|
@ -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)}`;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue