Enable named slots in renderers (#3652)
* feat: pass all slots to renderers * refactor: pass `slots` as top-level props * test: add named slot test for frameworks * fix: nested hydration, slots that are not initially rendered * test: add nested-recursive e2e test * fix: render unmatched custom element children * chore: update lockfile * fix: unrendered slots for client:only * fix(lit): ensure lit integration uses new slots API * chore: add changeset * chore: add changesets * fix: lit slots * feat: convert dash-case or snake_case slots to camelCase for JSX * feat: remove tmpl special logic * test: add slot components-in-markdown test * refactor: prefer Object.entries.map() to for/of loop Co-authored-by: Nate Moore <nate@astro.build>
This commit is contained in:
parent
19cd962d0b
commit
7373d61cdc
60 changed files with 826 additions and 156 deletions
7
.changeset/clever-pumpkins-begin.md
Normal file
7
.changeset/clever-pumpkins-begin.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
'@astrojs/lit': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Adds support for passing named slots from `.astro` => Lit components.
|
||||||
|
|
||||||
|
All slots are treated as Light DOM content.
|
29
.changeset/lovely-bulldogs-admire.md
Normal file
29
.changeset/lovely-bulldogs-admire.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
'@astrojs/preact': minor
|
||||||
|
'@astrojs/react': minor
|
||||||
|
'@astrojs/solid-js': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add support for passing named slots from `.astro` => framework components.
|
||||||
|
|
||||||
|
Each `slot` is be passed as a top-level prop. For example:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// From .astro
|
||||||
|
<Component>
|
||||||
|
<h2 slot="title">Hello world!</h2>
|
||||||
|
<h2 slot="slot-with-dash">Dash</h2>
|
||||||
|
<div>Default</div>
|
||||||
|
</Component>
|
||||||
|
|
||||||
|
// For .jsx
|
||||||
|
export default function Component({ title, slotWithDash, children }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div id="title">{title}</div>
|
||||||
|
<div id="slot-with-dash">{slotWithDash}</div>
|
||||||
|
<div id="main">{children}</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
7
.changeset/mean-ears-mate.md
Normal file
7
.changeset/mean-ears-mate.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add renderer support for passing named slots to framework components.
|
||||||
|
|
||||||
|
**BREAKING**: integrations using the `addRenderer()` API are now passed all named slots via `Record<string, string>` rather than `string`. Previously only the default slot was passed.
|
8
.changeset/tough-ants-rest.md
Normal file
8
.changeset/tough-ants-rest.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
'@astrojs/svelte': minor
|
||||||
|
'@astrojs/vue': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Adds support for passing named slots from `.astro` => framework components.
|
||||||
|
|
||||||
|
Inside your components, use the built-in `slot` API as you normally would.
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import preact from '@astrojs/preact';
|
||||||
|
import react from '@astrojs/react';
|
||||||
|
import svelte from '@astrojs/svelte';
|
||||||
|
import vue from '@astrojs/vue';
|
||||||
|
import solid from '@astrojs/solid-js';
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
export default defineConfig({
|
||||||
|
// Enable many frameworks to support all different kinds of components.
|
||||||
|
integrations: [preact(), react(), svelte(), vue(), solid()],
|
||||||
|
});
|
24
packages/astro/e2e/fixtures/nested-recursive/package.json
Normal file
24
packages/astro/e2e/fixtures/nested-recursive/package.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "@e2e/nested-recursive",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@astrojs/preact": "workspace:*",
|
||||||
|
"@astrojs/react": "workspace:*",
|
||||||
|
"@astrojs/solid-js": "workspace:*",
|
||||||
|
"@astrojs/svelte": "workspace:*",
|
||||||
|
"@astrojs/vue": "workspace:*",
|
||||||
|
"astro": "workspace:*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"preact": "^10.7.3",
|
||||||
|
"react": "^18.1.0",
|
||||||
|
"react-dom": "^18.1.0",
|
||||||
|
"solid-js": "^1.4.3",
|
||||||
|
"svelte": "^3.48.0",
|
||||||
|
"vue": "^3.2.36"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { useState } from 'preact/hooks';
|
||||||
|
|
||||||
|
/** a counter written in Preact */
|
||||||
|
export default function PreactCounter({ children, id }) {
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
const add = () => setCount((i) => i + 1);
|
||||||
|
const subtract = () => setCount((i) => i - 1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id={id} class="counter">
|
||||||
|
<button class="decrement" onClick={subtract}>-</button>
|
||||||
|
<pre id={`${id}-count`}>{count}</pre>
|
||||||
|
<button id={`${id}-increment`} class="increment" onClick={add}>+</button>
|
||||||
|
<div class="children">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
/** a counter written in React */
|
||||||
|
export default function ReactCounter({ children, id }) {
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
const add = () => setCount((i) => i + 1);
|
||||||
|
const subtract = () => setCount((i) => i - 1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id={id} className="counter">
|
||||||
|
<button className="decrement" onClick={subtract}>-</button>
|
||||||
|
<pre id={`${id}-count`}>{count}</pre>
|
||||||
|
<button id={`${id}-increment`} className="increment" onClick={add}>+</button>
|
||||||
|
<div className="children">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { createSignal } from 'solid-js';
|
||||||
|
|
||||||
|
/** a counter written with Solid */
|
||||||
|
export default function SolidCounter({ children, id }) {
|
||||||
|
const [count, setCount] = createSignal(0);
|
||||||
|
const add = () => setCount(count() + 1);
|
||||||
|
const subtract = () => setCount(count() - 1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id={id} class="counter">
|
||||||
|
<button class="decrement" onClick={subtract}>-</button>
|
||||||
|
<pre id={`${id}-count`}>{count()}</pre>
|
||||||
|
<button id={`${id}-increment`} class="increment" onClick={add}>+</button>
|
||||||
|
<div class="children">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export let id;
|
||||||
|
let children;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
function add() {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function subtract() {
|
||||||
|
count -= 1;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div {id} class="counter">
|
||||||
|
<button class="decrement" on:click={subtract}>-</button>
|
||||||
|
<pre id={`${id}-count`}>{ count }</pre>
|
||||||
|
<button id={`${id}-increment`} class="increment" on:click={add}>+</button>
|
||||||
|
<div class="children">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.counter {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,34 @@
|
||||||
|
<template>
|
||||||
|
<div :id="id" class="counter">
|
||||||
|
<button class="decrement" @click="subtract()">-</button>
|
||||||
|
<pre :id="`${id}-count`">{{ count }}</pre>
|
||||||
|
<button :id="`${id}-increment`" class="increment" @click="add()">+</button>
|
||||||
|
<div class="children">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const count = ref(0);
|
||||||
|
const add = () => (count.value = count.value + 1);
|
||||||
|
const subtract = () => (count.value = count.value - 1);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: props.id,
|
||||||
|
count,
|
||||||
|
add,
|
||||||
|
subtract,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,28 @@
|
||||||
|
---
|
||||||
|
import ReactCounter from '../components/ReactCounter.jsx';
|
||||||
|
import PreactCounter from '../components/PreactCounter.tsx';
|
||||||
|
import SolidCounter from '../components/SolidCounter.tsx';
|
||||||
|
import VueCounter from '../components/VueCounter.vue';
|
||||||
|
import SvelteCounter from '../components/SvelteCounter.svelte';
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<ReactCounter id="react-counter" client:idle>
|
||||||
|
<PreactCounter id="preact-counter" client:idle>
|
||||||
|
<SolidCounter id="solid-counter" client:idle>
|
||||||
|
<SvelteCounter id="svelte-counter" client:idle>
|
||||||
|
<VueCounter id="vue-counter" client:idle />
|
||||||
|
</SvelteCounter>
|
||||||
|
</SolidCounter>
|
||||||
|
</PreactCounter>
|
||||||
|
</ReactCounter>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
96
packages/astro/e2e/nested-recursive.test.js
Normal file
96
packages/astro/e2e/nested-recursive.test.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import { test as base, expect } from '@playwright/test';
|
||||||
|
import { loadFixture } from './test-utils.js';
|
||||||
|
|
||||||
|
const test = base.extend({
|
||||||
|
astro: async ({}, use) => {
|
||||||
|
const fixture = await loadFixture({ root: './fixtures/nested-recursive/' });
|
||||||
|
await use(fixture);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let devServer;
|
||||||
|
|
||||||
|
test.beforeEach(async ({ astro }) => {
|
||||||
|
devServer = await astro.startDevServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterEach(async () => {
|
||||||
|
await devServer.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Recursive Nested Frameworks', () => {
|
||||||
|
test('React counter', async ({ astro, page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
const counter = await page.locator('#react-counter');
|
||||||
|
await expect(counter, 'component is visible').toBeVisible();
|
||||||
|
|
||||||
|
const count = await counter.locator('#react-counter-count');
|
||||||
|
await expect(count, 'initial count is 0').toHaveText('0');
|
||||||
|
|
||||||
|
const increment = await counter.locator('#react-counter-increment');
|
||||||
|
await increment.click();
|
||||||
|
|
||||||
|
await expect(count, 'count incremented by 1').toHaveText('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Preact counter', async ({ astro, page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
const counter = await page.locator('#preact-counter');
|
||||||
|
await expect(counter, 'component is visible').toBeVisible();
|
||||||
|
|
||||||
|
const count = await counter.locator('#preact-counter-count');
|
||||||
|
await expect(count, 'initial count is 0').toHaveText('0');
|
||||||
|
|
||||||
|
const increment = await counter.locator('#preact-counter-increment');
|
||||||
|
await increment.click();
|
||||||
|
|
||||||
|
await expect(count, 'count incremented by 1').toHaveText('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Solid counter', async ({ astro, page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
const counter = await page.locator('#solid-counter');
|
||||||
|
await expect(counter, 'component is visible').toBeVisible();
|
||||||
|
|
||||||
|
const count = await counter.locator('#solid-counter-count');
|
||||||
|
await expect(count, 'initial count is 0').toHaveText('0');
|
||||||
|
|
||||||
|
const increment = await counter.locator('#solid-counter-increment');
|
||||||
|
await increment.click();
|
||||||
|
|
||||||
|
await expect(count, 'count incremented by 1').toHaveText('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Vue counter', async ({ astro, page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
const counter = await page.locator('#vue-counter');
|
||||||
|
await expect(counter, 'component is visible').toBeVisible();
|
||||||
|
|
||||||
|
const count = await counter.locator('#vue-counter-count');
|
||||||
|
await expect(count, 'initial count is 0').toHaveText('0');
|
||||||
|
|
||||||
|
const increment = await counter.locator('#vue-counter-increment');
|
||||||
|
await increment.click();
|
||||||
|
|
||||||
|
await expect(count, 'count incremented by 1').toHaveText('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Svelte counter', async ({ astro, page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
const counter = await page.locator('#svelte-counter');
|
||||||
|
await expect(counter, 'component is visible').toBeVisible();
|
||||||
|
|
||||||
|
const count = await counter.locator('#svelte-counter-count');
|
||||||
|
await expect(count, 'initial count is 0').toHaveText('0');
|
||||||
|
|
||||||
|
const increment = await counter.locator('#svelte-counter-increment');
|
||||||
|
await increment.click();
|
||||||
|
|
||||||
|
await expect(count, 'count incremented by 1').toHaveText('1');
|
||||||
|
});
|
||||||
|
});
|
|
@ -737,7 +737,7 @@ export interface AstroConfig extends z.output<typeof AstroConfigSchema> {
|
||||||
export type AsyncRendererComponentFn<U> = (
|
export type AsyncRendererComponentFn<U> = (
|
||||||
Component: any,
|
Component: any,
|
||||||
props: any,
|
props: any,
|
||||||
children: string | undefined,
|
slots: Record<string, string>,
|
||||||
metadata?: AstroComponentMetadata
|
metadata?: AstroComponentMetadata
|
||||||
) => Promise<U>;
|
) => Promise<U>;
|
||||||
|
|
||||||
|
|
|
@ -64,23 +64,24 @@ declare const Astro: {
|
||||||
if (!this.hydrator || this.parentElement?.closest('astro-island[ssr]')) {
|
if (!this.hydrator || this.parentElement?.closest('astro-island[ssr]')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let innerHTML: string | null = null;
|
const slotted = this.querySelectorAll('astro-slot');
|
||||||
let fragment = this.querySelector('astro-fragment');
|
const slots: Record<string, string> = {};
|
||||||
if (fragment == null && this.hasAttribute('tmpl')) {
|
// Always check to see if there are templates.
|
||||||
// If there is no child fragment, check to see if there is a template.
|
// This happens if slots were passed but the client component did not render them.
|
||||||
// This happens if children were passed but the client component did not render any.
|
const templates = this.querySelectorAll('template[data-astro-template]');
|
||||||
let template = this.querySelector('template[data-astro-template]');
|
for (const template of templates) {
|
||||||
if (template) {
|
if (!template.closest(this.tagName)?.isSameNode(this)) continue;
|
||||||
innerHTML = template.innerHTML;
|
slots[template.getAttribute('data-astro-template') || 'default'] = template.innerHTML;
|
||||||
template.remove();
|
template.remove();
|
||||||
}
|
}
|
||||||
} else if (fragment) {
|
for (const slot of slotted) {
|
||||||
innerHTML = fragment.innerHTML;
|
if (!slot.closest(this.tagName)?.isSameNode(this)) continue;
|
||||||
|
slots[slot.getAttribute('name') || 'default'] = slot.innerHTML;
|
||||||
}
|
}
|
||||||
const props = this.hasAttribute('props')
|
const props = this.hasAttribute('props')
|
||||||
? JSON.parse(this.getAttribute('props')!, reviver)
|
? JSON.parse(this.getAttribute('props')!, reviver)
|
||||||
: {};
|
: {};
|
||||||
this.hydrator(this)(this.Component, props, innerHTML, {
|
this.hydrator(this)(this.Component, props, slots, {
|
||||||
client: this.getAttribute('client'),
|
client: this.getAttribute('client'),
|
||||||
});
|
});
|
||||||
this.removeAttribute('ssr');
|
this.removeAttribute('ssr');
|
||||||
|
|
|
@ -208,7 +208,16 @@ Did you mean to add ${formatList(probableRendererNames.map((r) => '`' + r + '`')
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
const children = await renderSlot(result, slots?.default);
|
const children: Record<string, string> = {};
|
||||||
|
if (slots) {
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(slots).map(([key, value]) =>
|
||||||
|
renderSlot(result, value as string).then((output) => {
|
||||||
|
children[key] = output;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
// Call the renderers `check` hook to see if any claim this component.
|
// Call the renderers `check` hook to see if any claim this component.
|
||||||
let renderer: SSRLoadedRenderer | undefined;
|
let renderer: SSRLoadedRenderer | undefined;
|
||||||
if (metadata.hydrate !== 'only') {
|
if (metadata.hydrate !== 'only') {
|
||||||
|
@ -307,11 +316,12 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
|
||||||
// This is a custom element without a renderer. Because of that, render it
|
// This is a custom element without a renderer. Because of that, render it
|
||||||
// as a string and the user is responsible for adding a script tag for the component definition.
|
// as a string and the user is responsible for adding a script tag for the component definition.
|
||||||
if (!html && typeof Component === 'string') {
|
if (!html && typeof Component === 'string') {
|
||||||
|
const childSlots = Object.values(children).join('');
|
||||||
html = await renderAstroComponent(
|
html = await renderAstroComponent(
|
||||||
await render`<${Component}${internalSpreadAttributes(props)}${markHTMLString(
|
await render`<${Component}${internalSpreadAttributes(props)}${markHTMLString(
|
||||||
(children == null || children == '') && voidElementNames.test(Component)
|
childSlots === '' && voidElementNames.test(Component)
|
||||||
? `/>`
|
? `/>`
|
||||||
: `>${children == null ? '' : children}</${Component}>`
|
: `>${childSlots}</${Component}>`
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -320,7 +330,7 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
|
||||||
if (isPage) {
|
if (isPage) {
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
return markHTMLString(html.replace(/\<\/?astro-fragment\>/g, ''));
|
return markHTMLString(html.replace(/\<\/?astro-slot\>/g, ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include componentExport name, componentUrl, and props in hash to dedupe identical islands
|
// Include componentExport name, componentUrl, and props in hash to dedupe identical islands
|
||||||
|
@ -336,13 +346,30 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
|
||||||
);
|
);
|
||||||
result._metadata.needsHydrationStyles = true;
|
result._metadata.needsHydrationStyles = true;
|
||||||
|
|
||||||
// Render a template if no fragment is provided.
|
// Render template if not all astro fragments are provided.
|
||||||
const needsAstroTemplate = children && !/<\/?astro-fragment\>/.test(html);
|
let unrenderedSlots: string[] = [];
|
||||||
const template = needsAstroTemplate ? `<template data-astro-template>${children}</template>` : '';
|
if (html) {
|
||||||
|
if (Object.keys(children).length > 0) {
|
||||||
if (needsAstroTemplate) {
|
for (const key of Object.keys(children)) {
|
||||||
island.props.tmpl = '';
|
if (!html.includes(key === 'default' ? `<astro-slot>` : `<astro-slot name="${key}">`)) {
|
||||||
|
unrenderedSlots.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unrenderedSlots = Object.keys(children);
|
||||||
}
|
}
|
||||||
|
const template =
|
||||||
|
unrenderedSlots.length > 0
|
||||||
|
? unrenderedSlots
|
||||||
|
.map(
|
||||||
|
(key) =>
|
||||||
|
`<template data-astro-template${key !== 'default' ? `="${key}"` : ''}>${
|
||||||
|
children[key]
|
||||||
|
}</template>`
|
||||||
|
)
|
||||||
|
.join('')
|
||||||
|
: '';
|
||||||
|
|
||||||
island.children = `${html ?? ''}${template}`;
|
island.children = `${html ?? ''}${template}`;
|
||||||
|
|
||||||
|
@ -652,7 +679,7 @@ export async function renderHead(result: SSRResult): Promise<string> {
|
||||||
styles.push(
|
styles.push(
|
||||||
renderElement('style', {
|
renderElement('style', {
|
||||||
props: {},
|
props: {},
|
||||||
children: 'astro-island, astro-fragment { display: contents; }',
|
children: 'astro-island, astro-slot { display: contents; }',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,10 @@ export class MyElement extends LitElement {
|
||||||
<div id="str">${this.str}</div>
|
<div id="str">${this.str}</div>
|
||||||
<div id="data">data: ${this.obj.data}</div>
|
<div id="data">data: ${this.obj.data}</div>
|
||||||
<div id="win">${typeofwindow}</div>
|
<div id="win">${typeofwindow}</div>
|
||||||
|
|
||||||
|
<!-- Slots -->
|
||||||
|
<div id="default"><slot /></div>
|
||||||
|
<div id="named"><slot name="named" /></div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
15
packages/astro/test/fixtures/lit-element/src/pages/slots.astro
vendored
Normal file
15
packages/astro/test/fixtures/lit-element/src/pages/slots.astro
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
import {MyElement} from '../components/my-element.js';
|
||||||
|
---
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>LitElement | Slot</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<MyElement>
|
||||||
|
<div>default</div>
|
||||||
|
<div slot="named">named</div>
|
||||||
|
</MyElement>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,7 +1,7 @@
|
||||||
import { h, Fragment } from 'preact';
|
import { h, Fragment } from 'preact';
|
||||||
import { useState } from 'preact/hooks'
|
import { useState } from 'preact/hooks'
|
||||||
|
|
||||||
export default function Counter({ children, count: initialCount, case: id }) {
|
export default function Counter({ named, dashCase, children, count: initialCount, case: id }) {
|
||||||
const [count, setCount] = useState(initialCount);
|
const [count, setCount] = useState(initialCount);
|
||||||
const add = () => setCount((i) => i + 1);
|
const add = () => setCount((i) => i + 1);
|
||||||
const subtract = () => setCount((i) => i - 1);
|
const subtract = () => setCount((i) => i - 1);
|
||||||
|
@ -15,6 +15,8 @@ export default function Counter({ children, count: initialCount, case: id }) {
|
||||||
</div>
|
</div>
|
||||||
<div id={id} className="counter-message">
|
<div id={id} className="counter-message">
|
||||||
{children || <h1>Fallback</h1>}
|
{children || <h1>Fallback</h1>}
|
||||||
|
{named}
|
||||||
|
{dashCase}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,4 +8,6 @@ import Counter from '../components/Counter.jsx'
|
||||||
<Counter case="false" client:visible>{false}</Counter>
|
<Counter case="false" client:visible>{false}</Counter>
|
||||||
<Counter case="string" client:visible>{''}</Counter>
|
<Counter case="string" client:visible>{''}</Counter>
|
||||||
<Counter case="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
<Counter case="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
||||||
|
<Counter case="named" client:visible><h1 slot="named"> / Named</h1></Counter>
|
||||||
|
<Counter case="dash-case" client:visible><h1 slot="dash-case"> / Dash Case</h1></Counter>
|
||||||
</main>
|
</main>
|
||||||
|
|
9
packages/astro/test/fixtures/slots-preact/src/pages/markdown.md
vendored
Normal file
9
packages/astro/test/fixtures/slots-preact/src/pages/markdown.md
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
setup: import Counter from '../components/Counter.jsx'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Slots: Preact
|
||||||
|
|
||||||
|
<Counter case="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
||||||
|
<Counter case="named" client:visible><h1 slot="named"> / Named</h1></Counter>
|
||||||
|
<Counter case="dash-case" client:visible><h1 slot="dash-case"> / Dash Case</h1></Counter>
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
export default function Counter({ children, count: initialCount, case: id }) {
|
export default function Counter({ named, dashCase, children, count: initialCount, case: id }) {
|
||||||
const [count, setCount] = useState(initialCount);
|
const [count, setCount] = useState(initialCount);
|
||||||
const add = () => setCount((i) => i + 1);
|
const add = () => setCount((i) => i + 1);
|
||||||
const subtract = () => setCount((i) => i - 1);
|
const subtract = () => setCount((i) => i - 1);
|
||||||
|
@ -14,6 +14,8 @@ export default function Counter({ children, count: initialCount, case: id }) {
|
||||||
</div>
|
</div>
|
||||||
<div id={id} className="counter-message">
|
<div id={id} className="counter-message">
|
||||||
{children || <h1>Fallback</h1>}
|
{children || <h1>Fallback</h1>}
|
||||||
|
{named}
|
||||||
|
{dashCase}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,4 +8,6 @@ import Counter from '../components/Counter.jsx'
|
||||||
<Counter case="false" client:visible>{false}</Counter>
|
<Counter case="false" client:visible>{false}</Counter>
|
||||||
<Counter case="string" client:visible>{''}</Counter>
|
<Counter case="string" client:visible>{''}</Counter>
|
||||||
<Counter case="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
<Counter case="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
||||||
|
<Counter case="named" client:visible><h1 slot="named"> / Named</h1></Counter>
|
||||||
|
<Counter case="dash-case" client:visible><h1 slot="dash-case"> / Dash Case</h1></Counter>
|
||||||
</main>
|
</main>
|
||||||
|
|
9
packages/astro/test/fixtures/slots-react/src/pages/markdown.md
vendored
Normal file
9
packages/astro/test/fixtures/slots-react/src/pages/markdown.md
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
setup: import Counter from '../components/Counter.jsx'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Slots: React
|
||||||
|
|
||||||
|
<Counter case="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
||||||
|
<Counter case="named" client:visible><h1 slot="named"> / Named</h1></Counter>
|
||||||
|
<Counter case="dash-case" client:visible><h1 slot="dash-case"> / Dash Case</h1></Counter>
|
|
@ -1,6 +1,6 @@
|
||||||
import { createSignal } from 'solid-js';
|
import { createSignal } from 'solid-js';
|
||||||
|
|
||||||
export default function Counter({ children, count: initialCount, case: id }) {
|
export default function Counter({ named, dashCase, children, count: initialCount, case: id }) {
|
||||||
const [count] = createSignal(0);
|
const [count] = createSignal(0);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -9,6 +9,8 @@ export default function Counter({ children, count: initialCount, case: id }) {
|
||||||
</div>
|
</div>
|
||||||
<div id={id} className="counter-message">
|
<div id={id} className="counter-message">
|
||||||
{children || <h1>Fallback</h1>}
|
{children || <h1>Fallback</h1>}
|
||||||
|
{named}
|
||||||
|
{dashCase}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,4 +8,6 @@ import Counter from '../components/Counter.jsx'
|
||||||
<Counter case="false" client:visible>{false}</Counter>
|
<Counter case="false" client:visible>{false}</Counter>
|
||||||
<Counter case="string" client:visible>{''}</Counter>
|
<Counter case="string" client:visible>{''}</Counter>
|
||||||
<Counter case="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
<Counter case="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
||||||
|
<Counter case="named" client:visible><h1 slot="named"> / Named</h1></Counter>
|
||||||
|
<Counter case="dash-case" client:visible><h1 slot="dash-case"> / Dash Case</h1></Counter>
|
||||||
</main>
|
</main>
|
||||||
|
|
9
packages/astro/test/fixtures/slots-solid/src/pages/markdown.md
vendored
Normal file
9
packages/astro/test/fixtures/slots-solid/src/pages/markdown.md
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
setup: import Counter from '../components/Counter.jsx'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Slots: Solid
|
||||||
|
|
||||||
|
<Counter case="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
||||||
|
<Counter case="named" client:visible><h1 slot="named"> / Named</h1></Counter>
|
||||||
|
<Counter case="dash-case" client:visible><h1 slot="dash-case"> / Dash Case</h1></Counter>
|
|
@ -17,9 +17,7 @@
|
||||||
<button on:click={add}>+</button>
|
<button on:click={add}>+</button>
|
||||||
</div>
|
</div>
|
||||||
<div id={id}>
|
<div id={id}>
|
||||||
<slot>
|
<slot><h1 id="fallback">Fallback</h1></slot><slot name="named" /><slot name="dash-case" />
|
||||||
<h1 id="fallback">Fallback</h1>
|
|
||||||
</slot>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -4,8 +4,10 @@ import Counter from '../components/Counter.svelte'
|
||||||
<main>
|
<main>
|
||||||
<Counter id="default-self-closing" client:visible/>
|
<Counter id="default-self-closing" client:visible/>
|
||||||
<Counter id="default-empty" client:visible></Counter>
|
<Counter id="default-empty" client:visible></Counter>
|
||||||
<Counter case="zero" client:visible>{0}</Counter>
|
<Counter id="zero" client:visible>{0}</Counter>
|
||||||
<Counter case="false" client:visible>{false}</Counter>
|
<Counter id="false" client:visible>{false}</Counter>
|
||||||
<Counter case="string" client:visible>{''}</Counter>
|
<Counter id="string" client:visible>{''}</Counter>
|
||||||
<Counter id="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
<Counter id="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
||||||
|
<Counter id="named" client:visible><h1 slot="named"> / Named</h1></Counter>
|
||||||
|
<Counter id="dash-case" client:visible><h1 slot="dash-case"> / Dash Case</h1></Counter>
|
||||||
</main>
|
</main>
|
||||||
|
|
9
packages/astro/test/fixtures/slots-svelte/src/pages/markdown.md
vendored
Normal file
9
packages/astro/test/fixtures/slots-svelte/src/pages/markdown.md
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
setup: import Counter from '../components/Counter.svelte'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Slots: Svelte
|
||||||
|
|
||||||
|
<Counter id="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
||||||
|
<Counter id="named" client:visible><h1 slot="named"> / Named</h1></Counter>
|
||||||
|
<Counter id="dash-case" client:visible><h1 slot="dash-case"> / Dash Case</h1></Counter>
|
|
@ -8,6 +8,8 @@
|
||||||
<slot>
|
<slot>
|
||||||
<h1>Fallback</h1>
|
<h1>Fallback</h1>
|
||||||
</slot>
|
</slot>
|
||||||
|
<slot name="named" />
|
||||||
|
<slot name="dash-case"></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -8,4 +8,6 @@ import Counter from '../components/Counter.vue'
|
||||||
<Counter case="false" client:visible>{false}</Counter>
|
<Counter case="false" client:visible>{false}</Counter>
|
||||||
<Counter case="string" client:visible>{''}</Counter>
|
<Counter case="string" client:visible>{''}</Counter>
|
||||||
<Counter case="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
<Counter case="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
||||||
|
<Counter case="named" client:visible><h1 slot="named"> / Named</h1></Counter>
|
||||||
|
<Counter case="dash-case" client:visible><h1 slot="dash-case"> / Dash Case</h1></Counter>
|
||||||
</main>
|
</main>
|
||||||
|
|
9
packages/astro/test/fixtures/slots-vue/src/pages/markdown.md
vendored
Normal file
9
packages/astro/test/fixtures/slots-vue/src/pages/markdown.md
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
setup: import Counter from '../components/Counter.vue'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Slots: Vue
|
||||||
|
|
||||||
|
<Counter case="content" client:visible><h1 id="slotted">Hello world!</h1></Counter>
|
||||||
|
<Counter case="named" client:visible><h1 slot="named"> / Named</h1></Counter>
|
||||||
|
<Counter case="dash-case" client:visible><h1 slot="dash-case"> / Dash Case</h1></Counter>
|
|
@ -61,4 +61,23 @@ describe('LitElement test', function () {
|
||||||
expect($('my-element').attr('reflected-str')).to.equal('default reflected string');
|
expect($('my-element').attr('reflected-str')).to.equal('default reflected string');
|
||||||
expect($('my-element').attr('reflected-str-prop')).to.equal('initialized reflected');
|
expect($('my-element').attr('reflected-str-prop')).to.equal('initialized reflected');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Correctly passes child slots', async () => {
|
||||||
|
// @lit-labs/ssr/ requires Node 13.9 or higher
|
||||||
|
if (NODE_VERSION < 13.9) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const html = await fixture.readFile('/slots/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
expect($('my-element').length).to.equal(1);
|
||||||
|
|
||||||
|
const [defaultSlot, namedSlot] = $('template').siblings().toArray();
|
||||||
|
|
||||||
|
// has default slot content in lightdom
|
||||||
|
expect($(defaultSlot).text()).to.equal('default');
|
||||||
|
|
||||||
|
// has named slot content in lightdom
|
||||||
|
expect($(namedSlot).text()).to.equal('named');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,4 +21,36 @@ describe('Slots: Preact', () => {
|
||||||
expect($('#string').text().trim()).to.equal('');
|
expect($('#string').text().trim()).to.equal('');
|
||||||
expect($('#content').text().trim()).to.equal('Hello world!');
|
expect($('#content').text().trim()).to.equal('Hello world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Renders named slot', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#named').text().trim()).to.equal('Fallback / Named');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Converts dash-case slot to camelCase', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#dash-case').text().trim()).to.equal('Fallback / Dash Case');
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('For Markdown Pages', () => {
|
||||||
|
it('Renders default slot', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#content').text().trim()).to.equal('Hello world!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders named slot', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#named').text().trim()).to.equal('Fallback / Named');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Converts dash-case slot to camelCase', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#dash-case').text().trim()).to.equal('Fallback / Dash Case');
|
||||||
|
})
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,4 +21,36 @@ describe('Slots: React', () => {
|
||||||
expect($('#string').text().trim()).to.equal('');
|
expect($('#string').text().trim()).to.equal('');
|
||||||
expect($('#content').text().trim()).to.equal('Hello world!');
|
expect($('#content').text().trim()).to.equal('Hello world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Renders named slot', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#named').text().trim()).to.equal('Fallback / Named');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Converts dash-case slot to camelCase', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#dash-case').text().trim()).to.equal('Fallback / Dash Case');
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('For Markdown Pages', () => {
|
||||||
|
it('Renders default slot', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#content').text().trim()).to.equal('Hello world!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders named slot', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#named').text().trim()).to.equal('Fallback / Named');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Converts dash-case slot to camelCase', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#dash-case').text().trim()).to.equal('Fallback / Dash Case');
|
||||||
|
})
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,4 +21,36 @@ describe('Slots: Solid', () => {
|
||||||
expect($('#string').text().trim()).to.equal('');
|
expect($('#string').text().trim()).to.equal('');
|
||||||
expect($('#content').text().trim()).to.equal('Hello world!');
|
expect($('#content').text().trim()).to.equal('Hello world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Renders named slot', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#named').text().trim()).to.equal('Fallback / Named');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Converts dash-case slot to camelCase', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#dash-case').text().trim()).to.equal('Fallback / Dash Case');
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('For Markdown Pages', () => {
|
||||||
|
it('Renders default slot', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#content').text().trim()).to.equal('Hello world!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders named slot', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#named').text().trim()).to.equal('Fallback / Named');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Converts dash-case slot to camelCase', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#dash-case').text().trim()).to.equal('Fallback / Dash Case');
|
||||||
|
})
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,9 +16,41 @@ describe('Slots: Svelte', () => {
|
||||||
|
|
||||||
expect($('#default-self-closing').text().trim()).to.equal('Fallback');
|
expect($('#default-self-closing').text().trim()).to.equal('Fallback');
|
||||||
expect($('#default-empty').text().trim()).to.equal('Fallback');
|
expect($('#default-empty').text().trim()).to.equal('Fallback');
|
||||||
expect($('#zero').text().trim()).to.equal('');
|
expect($('#zero').text().trim()).to.equal('0');
|
||||||
expect($('#false').text().trim()).to.equal('');
|
expect($('#false').text().trim()).to.equal('');
|
||||||
expect($('#string').text().trim()).to.equal('');
|
expect($('#string').text().trim()).to.equal('');
|
||||||
expect($('#content').text().trim()).to.equal('Hello world!');
|
expect($('#content').text().trim()).to.equal('Hello world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Renders named slot', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#named').text().trim()).to.equal('Fallback / Named');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Preserves dash-case slot', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#dash-case').text().trim()).to.equal('Fallback / Dash Case');
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('For Markdown Pages', () => {
|
||||||
|
it('Renders default slot', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#content').text().trim()).to.equal('Hello world!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders named slot', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#named').text().trim()).to.equal('Fallback / Named');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Converts dash-case slot to camelCase', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#dash-case').text().trim()).to.equal('Fallback / Dash Case');
|
||||||
|
})
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,4 +21,36 @@ describe('Slots: Vue', () => {
|
||||||
expect($('#string').text().trim()).to.equal('');
|
expect($('#string').text().trim()).to.equal('');
|
||||||
expect($('#content').text().trim()).to.equal('Hello world!');
|
expect($('#content').text().trim()).to.equal('Hello world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Renders named slot', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#named').text().trim()).to.equal('Fallback / Named');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Preserves dash-case slot', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#dash-case').text().trim()).to.equal('Fallback / Dash Case');
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('For Markdown Pages', () => {
|
||||||
|
it('Renders default slot', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#content').text().trim()).to.equal('Hello world!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders named slot', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#named').text().trim()).to.equal('Fallback / Named');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Converts dash-case slot to camelCase', async () => {
|
||||||
|
const html = await fixture.readFile('/markdown/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#dash-case').text().trim()).to.equal('Fallback / Dash Case');
|
||||||
|
})
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,7 +26,7 @@ async function check(Component, _props, _children) {
|
||||||
return !!(await isLitElement(Component));
|
return !!(await isLitElement(Component));
|
||||||
}
|
}
|
||||||
|
|
||||||
function* render(Component, attrs, children) {
|
function* render(Component, attrs, slots) {
|
||||||
let tagName = Component;
|
let tagName = Component;
|
||||||
if (typeof tagName !== 'string') {
|
if (typeof tagName !== 'string') {
|
||||||
tagName = Component[Symbol.for('tagName')];
|
tagName = Component[Symbol.for('tagName')];
|
||||||
|
@ -57,15 +57,23 @@ function* render(Component, attrs, children) {
|
||||||
yield* shadowContents;
|
yield* shadowContents;
|
||||||
yield '</template>';
|
yield '</template>';
|
||||||
}
|
}
|
||||||
yield children || ''; // don’t print “undefined” as string
|
if (slots) {
|
||||||
|
for (const [slot, value] of Object.entries(slots)) {
|
||||||
|
if (slot === 'default') {
|
||||||
|
yield `<astro-slot>${value || ''}</astro-slot>`;
|
||||||
|
} else {
|
||||||
|
yield `<astro-slot slot="${slot}">${value || ''}</astro-slot>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
yield `</${tagName}>`;
|
yield `</${tagName}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderToStaticMarkup(Component, props, children) {
|
async function renderToStaticMarkup(Component, props, slots) {
|
||||||
let tagName = Component;
|
let tagName = Component;
|
||||||
|
|
||||||
let out = '';
|
let out = '';
|
||||||
for (let chunk of render(tagName, props, children)) {
|
for (let chunk of render(tagName, props, slots)) {
|
||||||
out += chunk;
|
out += chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { h, render } from 'preact';
|
import { h, render } from 'preact';
|
||||||
import StaticHtml from './static-html.js';
|
import StaticHtml from './static-html.js';
|
||||||
|
|
||||||
export default (element) => (Component, props, children) => {
|
export default (element) => (Component, props, { default: children, ...slotted }) => {
|
||||||
if (!element.hasAttribute('ssr')) return;
|
if (!element.hasAttribute('ssr')) return;
|
||||||
|
for (const [key, value] of Object.entries(slotted)) {
|
||||||
|
props[key] = h(StaticHtml, { value, name: key });
|
||||||
|
}
|
||||||
render(
|
render(
|
||||||
h(Component, props, children != null ? h(StaticHtml, { value: children }) : children),
|
h(Component, props, children != null ? h(StaticHtml, { value: children }) : children),
|
||||||
element
|
element
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { h, Component as BaseComponent } from 'preact';
|
||||||
import render from 'preact-render-to-string';
|
import render from 'preact-render-to-string';
|
||||||
import StaticHtml from './static-html.js';
|
import StaticHtml from './static-html.js';
|
||||||
|
|
||||||
|
const slotName = str => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
|
||||||
|
|
||||||
function check(Component, props, children) {
|
function check(Component, props, children) {
|
||||||
if (typeof Component !== 'function') return false;
|
if (typeof Component !== 'function') return false;
|
||||||
|
|
||||||
|
@ -24,9 +26,16 @@ function check(Component, props, children) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderToStaticMarkup(Component, props, children) {
|
function renderToStaticMarkup(Component, props, { default: children, ...slotted }) {
|
||||||
|
const slots = {};
|
||||||
|
for (const [key, value] of Object.entries(slotted)) {
|
||||||
|
const name = slotName(key);
|
||||||
|
slots[name] = h(StaticHtml, { value, name });
|
||||||
|
}
|
||||||
|
// Note: create newProps to avoid mutating `props` before they are serialized
|
||||||
|
const newProps = { ...props, ...slots }
|
||||||
const html = render(
|
const html = render(
|
||||||
h(Component, props, children != null ? h(StaticHtml, { value: children }) : children)
|
h(Component, newProps, children != null ? h(StaticHtml, { value: children }) : children)
|
||||||
);
|
);
|
||||||
return { html };
|
return { html };
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ import { h } from 'preact';
|
||||||
* As a bonus, we can signal to Preact that this subtree is
|
* As a bonus, we can signal to Preact that this subtree is
|
||||||
* entirely static and will never change via `shouldComponentUpdate`.
|
* entirely static and will never change via `shouldComponentUpdate`.
|
||||||
*/
|
*/
|
||||||
const StaticHtml = ({ value }) => {
|
const StaticHtml = ({ value, name }) => {
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
return h('astro-fragment', { dangerouslySetInnerHTML: { __html: value } });
|
return h('astro-slot', { name, dangerouslySetInnerHTML: { __html: value } });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,7 +3,10 @@ import { render, hydrate } from 'react-dom';
|
||||||
import StaticHtml from './static-html.js';
|
import StaticHtml from './static-html.js';
|
||||||
|
|
||||||
export default (element) =>
|
export default (element) =>
|
||||||
(Component, props, children, { client }) => {
|
(Component, props, { default: children, ...slotted }, { client }) => {
|
||||||
|
for (const [key, value] of Object.entries(slotted)) {
|
||||||
|
props[key] = createElement(StaticHtml, { value, name: key });
|
||||||
|
}
|
||||||
const componentEl = createElement(
|
const componentEl = createElement(
|
||||||
Component,
|
Component,
|
||||||
props,
|
props,
|
||||||
|
|
|
@ -11,8 +11,11 @@ function isAlreadyHydrated(element) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (element) =>
|
export default (element) =>
|
||||||
(Component, props, children, { client }) => {
|
(Component, props, { default: children, ...slotted }, { client }) => {
|
||||||
if (!element.hasAttribute('ssr')) return;
|
if (!element.hasAttribute('ssr')) return;
|
||||||
|
for (const [key, value] of Object.entries(slotted)) {
|
||||||
|
props[key] = createElement(StaticHtml, { value, name: key });
|
||||||
|
}
|
||||||
const componentEl = createElement(
|
const componentEl = createElement(
|
||||||
Component,
|
Component,
|
||||||
props,
|
props,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import ReactDOM from 'react-dom/server.js';
|
import ReactDOM from 'react-dom/server.js';
|
||||||
import StaticHtml from './static-html.js';
|
import StaticHtml from './static-html.js';
|
||||||
|
|
||||||
|
const slotName = str => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
|
||||||
const reactTypeof = Symbol.for('react.element');
|
const reactTypeof = Symbol.for('react.element');
|
||||||
|
|
||||||
function errorIsComingFromPreactComponent(err) {
|
function errorIsComingFromPreactComponent(err) {
|
||||||
|
@ -50,12 +51,20 @@ function check(Component, props, children) {
|
||||||
return isReactComponent;
|
return isReactComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderToStaticMarkup(Component, props, children, metadata) {
|
function renderToStaticMarkup(Component, props, { default: children, ...slotted }, metadata) {
|
||||||
delete props['class'];
|
delete props['class'];
|
||||||
const vnode = React.createElement(Component, {
|
const slots = {};
|
||||||
|
for (const [key, value] of Object.entries(slotted)) {
|
||||||
|
const name = slotName(key);
|
||||||
|
slots[name] = React.createElement(StaticHtml, { value, name });
|
||||||
|
}
|
||||||
|
// Note: create newProps to avoid mutating `props` before they are serialized
|
||||||
|
const newProps = {
|
||||||
...props,
|
...props,
|
||||||
|
...slots,
|
||||||
children: children != null ? React.createElement(StaticHtml, { value: children }) : undefined,
|
children: children != null ? React.createElement(StaticHtml, { value: children }) : undefined,
|
||||||
});
|
}
|
||||||
|
const vnode = React.createElement(Component, newProps);
|
||||||
let html;
|
let html;
|
||||||
if (metadata && metadata.hydrate) {
|
if (metadata && metadata.hydrate) {
|
||||||
html = ReactDOM.renderToString(vnode);
|
html = ReactDOM.renderToString(vnode);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import ReactDOM from 'react-dom/server';
|
import ReactDOM from 'react-dom/server';
|
||||||
import StaticHtml from './static-html.js';
|
import StaticHtml from './static-html.js';
|
||||||
|
|
||||||
|
const slotName = str => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
|
||||||
const reactTypeof = Symbol.for('react.element');
|
const reactTypeof = Symbol.for('react.element');
|
||||||
|
|
||||||
function errorIsComingFromPreactComponent(err) {
|
function errorIsComingFromPreactComponent(err) {
|
||||||
|
@ -56,12 +57,20 @@ async function getNodeWritable() {
|
||||||
return Writable;
|
return Writable;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderToStaticMarkup(Component, props, children, metadata) {
|
async function renderToStaticMarkup(Component, props, { default: children, ...slotted }, metadata) {
|
||||||
delete props['class'];
|
delete props['class'];
|
||||||
const vnode = React.createElement(Component, {
|
const slots = {};
|
||||||
|
for (const [key, value] of Object.entries(slotted)) {
|
||||||
|
const name = slotName(key);
|
||||||
|
slots[name] = React.createElement(StaticHtml, { value, name });
|
||||||
|
}
|
||||||
|
// Note: create newProps to avoid mutating `props` before they are serialized
|
||||||
|
const newProps = {
|
||||||
...props,
|
...props,
|
||||||
|
...slots,
|
||||||
children: children != null ? React.createElement(StaticHtml, { value: children }) : undefined,
|
children: children != null ? React.createElement(StaticHtml, { value: children }) : undefined,
|
||||||
});
|
}
|
||||||
|
const vnode = React.createElement(Component, newProps);
|
||||||
let html;
|
let html;
|
||||||
if (metadata && metadata.hydrate) {
|
if (metadata && metadata.hydrate) {
|
||||||
html = ReactDOM.renderToString(vnode);
|
html = ReactDOM.renderToString(vnode);
|
||||||
|
|
|
@ -7,9 +7,10 @@ import { createElement as h } from 'react';
|
||||||
* As a bonus, we can signal to React that this subtree is
|
* As a bonus, we can signal to React that this subtree is
|
||||||
* entirely static and will never change via `shouldComponentUpdate`.
|
* entirely static and will never change via `shouldComponentUpdate`.
|
||||||
*/
|
*/
|
||||||
const StaticHtml = ({ value }) => {
|
const StaticHtml = ({ value, name }) => {
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
return h('astro-fragment', {
|
return h('astro-slot', {
|
||||||
|
name,
|
||||||
suppressHydrationWarning: true,
|
suppressHydrationWarning: true,
|
||||||
dangerouslySetInnerHTML: { __html: value },
|
dangerouslySetInnerHTML: { __html: value },
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { sharedConfig } from 'solid-js';
|
||||||
import { hydrate, render, createComponent } from 'solid-js/web';
|
import { hydrate, render, createComponent } from 'solid-js/web';
|
||||||
|
|
||||||
export default (element) =>
|
export default (element) =>
|
||||||
(Component, props, childHTML, { client }) => {
|
(Component, props, slotted, { client }) => {
|
||||||
// Prepare global object expected by Solid's hydration logic
|
// Prepare global object expected by Solid's hydration logic
|
||||||
if (!window._$HY) {
|
if (!window._$HY) {
|
||||||
window._$HY = { events: [], completed: new WeakSet(), r: {} };
|
window._$HY = { events: [], completed: new WeakSet(), r: {} };
|
||||||
|
@ -11,26 +11,30 @@ export default (element) =>
|
||||||
|
|
||||||
const fn = client === 'only' ? render : hydrate;
|
const fn = client === 'only' ? render : hydrate;
|
||||||
|
|
||||||
// Perform actual hydration
|
let _slots = {};
|
||||||
let children;
|
if (Object.keys(slotted).length > 0) {
|
||||||
|
// hydrating
|
||||||
|
if (sharedConfig.context) {
|
||||||
|
element.querySelectorAll('astro-slot').forEach((slot) => {
|
||||||
|
_slots[slot.getAttribute('name') || 'default'] = slot.cloneNode(true);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (const [key, value] of Object.entries(slotted)) {
|
||||||
|
_slots[key] = document.createElement('astro-slot');
|
||||||
|
if (key !== 'default') _slots[key].setAttribute('name', key);
|
||||||
|
_slots[key].innerHTML = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { default: children, ...slots } = _slots;
|
||||||
|
|
||||||
fn(
|
fn(
|
||||||
() =>
|
() =>
|
||||||
createComponent(Component, {
|
createComponent(Component, {
|
||||||
...props,
|
...props,
|
||||||
get children() {
|
...slots,
|
||||||
if (childHTML != null) {
|
children
|
||||||
// hydrating
|
|
||||||
if (sharedConfig.context) {
|
|
||||||
children = element.querySelector('astro-fragment');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (children == null) {
|
|
||||||
children = document.createElement('astro-fragment');
|
|
||||||
children.innerHTML = childHTML;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return children;
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
element
|
element
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,23 +1,28 @@
|
||||||
import { renderToString, ssr, createComponent } from 'solid-js/web';
|
import { renderToString, ssr, createComponent } from 'solid-js/web';
|
||||||
|
|
||||||
|
const slotName = str => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
|
||||||
|
|
||||||
function check(Component, props, children) {
|
function check(Component, props, children) {
|
||||||
if (typeof Component !== 'function') return false;
|
if (typeof Component !== 'function') return false;
|
||||||
const { html } = renderToStaticMarkup(Component, props, children);
|
const { html } = renderToStaticMarkup(Component, props, children);
|
||||||
return typeof html === 'string';
|
return typeof html === 'string';
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderToStaticMarkup(Component, props, children) {
|
function renderToStaticMarkup(Component, props, { default: children, ...slotted }) {
|
||||||
const html = renderToString(() =>
|
const slots = {};
|
||||||
createComponent(Component, {
|
for (const [key, value] of Object.entries(slotted)) {
|
||||||
...props,
|
const name = slotName(key);
|
||||||
// In Solid SSR mode, `ssr` creates the expected structure for `children`.
|
slots[name] = ssr(`<astro-slot name="${name}">${value}</astro-slot>`);
|
||||||
// In Solid client mode, `ssr` is just a stub.
|
}
|
||||||
children: children != null ? ssr(`<astro-fragment>${children}</astro-fragment>`) : children,
|
// Note: create newProps to avoid mutating `props` before they are serialized
|
||||||
})
|
const newProps = {
|
||||||
);
|
...props,
|
||||||
return {
|
...slots,
|
||||||
html: html,
|
// In Solid SSR mode, `ssr` creates the expected structure for `children`.
|
||||||
};
|
children: children != null ? ssr(`<astro-slot>${children}</astro-slot>`) : children,
|
||||||
|
}
|
||||||
|
const html = renderToString(() => createComponent(Component, newProps));
|
||||||
|
return { html }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { ssr } from 'solid-js/web';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Astro passes `children` as a string of HTML, so we need
|
|
||||||
* a wrapper `astro-fragment` to render that content as VNodes.
|
|
||||||
*/
|
|
||||||
const StaticHtml = ({ innerHTML }) => {
|
|
||||||
if (!innerHTML) return null;
|
|
||||||
return ssr(`<astro-fragment>${innerHTML}</astro-fragment>`);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default StaticHtml;
|
|
|
@ -1,21 +0,0 @@
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* Why do we need a wrapper component?
|
|
||||||
*
|
|
||||||
* Astro passes `children` as a string of HTML, so we need
|
|
||||||
* a way to render that content.
|
|
||||||
*
|
|
||||||
* Rather than passing a magical prop which needs special
|
|
||||||
* handling, using this wrapper allows Svelte users to just
|
|
||||||
* use `<slot />` like they would for any other component.
|
|
||||||
*/
|
|
||||||
const { __astro_component: Component, __astro_children, ...props } = $$props;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:component this={Component} {...props}>
|
|
||||||
{#if __astro_children != null}
|
|
||||||
<astro-fragment>
|
|
||||||
{@html __astro_children}
|
|
||||||
</astro-fragment>
|
|
||||||
{/if}
|
|
||||||
</svelte:component>
|
|
|
@ -1,19 +0,0 @@
|
||||||
/* App.svelte generated by Svelte v3.38.2 */
|
|
||||||
import { create_ssr_component, missing_component, validate_component } from 'svelte/internal';
|
|
||||||
|
|
||||||
const App = create_ssr_component(($$result, $$props, $$bindings, slots) => {
|
|
||||||
const { __astro_component: Component, __astro_children, ...props } = $$props;
|
|
||||||
const children = {};
|
|
||||||
if (__astro_children != null) {
|
|
||||||
children.default = () => `<astro-fragment>${__astro_children}</astro-fragment>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${validate_component(Component || missing_component, 'svelte:component').$$render(
|
|
||||||
$$result,
|
|
||||||
Object.assign(props),
|
|
||||||
{},
|
|
||||||
children
|
|
||||||
)}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default App;
|
|
|
@ -1,15 +1,43 @@
|
||||||
import SvelteWrapper from './Wrapper.svelte';
|
const noop = () => {};
|
||||||
|
|
||||||
export default (target) => {
|
export default (target) => {
|
||||||
return (component, props, children, { client }) => {
|
return (Component, props, slotted, { client }) => {
|
||||||
if (!target.hasAttribute('ssr')) return;
|
if (!target.hasAttribute('ssr')) return;
|
||||||
delete props['class'];
|
delete props['class'];
|
||||||
|
const slots = {};
|
||||||
|
for (const [key, value] of Object.entries(slotted)) {
|
||||||
|
slots[key] = createSlotDefinition(key, value);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
new SvelteWrapper({
|
new Component({
|
||||||
target,
|
target,
|
||||||
props: { __astro_component: component, __astro_children: children, ...props },
|
props: {
|
||||||
|
...props,
|
||||||
|
$$slots: slots,
|
||||||
|
$$scope: { ctx: [] }
|
||||||
|
},
|
||||||
hydrate: client !== 'only',
|
hydrate: client !== 'only',
|
||||||
|
$$inline: true,
|
||||||
});
|
});
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function createSlotDefinition(key, children) {
|
||||||
|
return [
|
||||||
|
() => ({
|
||||||
|
// mount
|
||||||
|
m(target) {
|
||||||
|
target.insertAdjacentHTML('beforeend', `<astro-slot${key === 'default' ? '' : ` name="${key}"`}>${children}</astro-slot>`)
|
||||||
|
},
|
||||||
|
// create
|
||||||
|
c: noop,
|
||||||
|
// hydrate
|
||||||
|
l: noop,
|
||||||
|
// destroy
|
||||||
|
d: noop,
|
||||||
|
}),
|
||||||
|
noop,
|
||||||
|
noop,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
import SvelteWrapper from './Wrapper.svelte.ssr.js';
|
|
||||||
|
|
||||||
function check(Component) {
|
function check(Component) {
|
||||||
return Component['render'] && Component['$$render'];
|
return Component['render'] && Component['$$render'];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderToStaticMarkup(Component, props, children) {
|
async function renderToStaticMarkup(Component, props, slotted) {
|
||||||
const { html } = SvelteWrapper.render({
|
const slots = {};
|
||||||
__astro_component: Component,
|
for (const [key, value] of Object.entries(slotted)) {
|
||||||
__astro_children: children,
|
slots[key] = () => `<astro-slot${key === 'default' ? '' : ` name="${key}"`}>${value}</astro-slot>`;
|
||||||
...props,
|
}
|
||||||
});
|
const { html } = Component.render(props, { $$slots: slots });
|
||||||
return { html };
|
return { html };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,15 @@ import { h, createSSRApp, createApp } from 'vue';
|
||||||
import StaticHtml from './static-html.js';
|
import StaticHtml from './static-html.js';
|
||||||
|
|
||||||
export default (element) =>
|
export default (element) =>
|
||||||
(Component, props, children, { client }) => {
|
(Component, props, slotted, { client }) => {
|
||||||
delete props['class'];
|
delete props['class'];
|
||||||
if (!element.hasAttribute('ssr')) return;
|
if (!element.hasAttribute('ssr')) return;
|
||||||
|
|
||||||
// Expose name on host component for Vue devtools
|
// Expose name on host component for Vue devtools
|
||||||
const name = Component.name ? `${Component.name} Host` : undefined;
|
const name = Component.name ? `${Component.name} Host` : undefined;
|
||||||
const slots = {};
|
const slots = {};
|
||||||
if (children != null) {
|
for (const [key, value] of Object.entries(slotted)) {
|
||||||
slots.default = () => h(StaticHtml, { value: children });
|
slots[key] = () => h(StaticHtml, { value, name: key === 'default' ? undefined : key });
|
||||||
}
|
}
|
||||||
if (client === 'only') {
|
if (client === 'only') {
|
||||||
const app = createApp({ name, render: () => h(Component, props, slots) });
|
const app = createApp({ name, render: () => h(Component, props, slots) });
|
||||||
|
|
|
@ -6,10 +6,10 @@ function check(Component) {
|
||||||
return !!Component['ssrRender'];
|
return !!Component['ssrRender'];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderToStaticMarkup(Component, props, children) {
|
async function renderToStaticMarkup(Component, props, slotted) {
|
||||||
const slots = {};
|
const slots = {};
|
||||||
if (children != null) {
|
for (const [key, value] of Object.entries(slotted)) {
|
||||||
slots.default = () => h(StaticHtml, { value: children });
|
slots[key] = () => h(StaticHtml, { value, name: key === 'default' ? undefined : key });
|
||||||
}
|
}
|
||||||
const app = createSSRApp({ render: () => h(Component, props, slots) });
|
const app = createSSRApp({ render: () => h(Component, props, slots) });
|
||||||
const html = await renderToString(app);
|
const html = await renderToString(app);
|
||||||
|
|
|
@ -9,10 +9,11 @@ import { h, defineComponent } from 'vue';
|
||||||
const StaticHtml = defineComponent({
|
const StaticHtml = defineComponent({
|
||||||
props: {
|
props: {
|
||||||
value: String,
|
value: String,
|
||||||
|
name: String,
|
||||||
},
|
},
|
||||||
setup({ value }) {
|
setup({ name, value }) {
|
||||||
if (!value) return () => null;
|
if (!value) return () => null;
|
||||||
return () => h('astro-fragment', { innerHTML: value });
|
return () => h('astro-slot', { name, innerHTML: value });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
2
packages/webapi/mod.d.ts
vendored
2
packages/webapi/mod.d.ts
vendored
|
@ -1,5 +1,5 @@
|
||||||
export { pathToPosix } from './lib/utils';
|
export { pathToPosix } from './lib/utils';
|
||||||
export { AbortController, AbortSignal, alert, atob, Blob, btoa, ByteLengthQueuingStrategy, cancelAnimationFrame, cancelIdleCallback, CanvasRenderingContext2D, CharacterData, clearTimeout, Comment, CountQueuingStrategy, CSSStyleSheet, CustomElementRegistry, CustomEvent, Document, DocumentFragment, DOMException, Element, Event, EventTarget, fetch, File, FormData, Headers, HTMLBodyElement, HTMLCanvasElement, HTMLDivElement, HTMLDocument, HTMLElement, HTMLHeadElement, HTMLHtmlElement, HTMLImageElement, HTMLSpanElement, HTMLStyleElement, HTMLTemplateElement, HTMLUnknownElement, Image, ImageData, IntersectionObserver, MediaQueryList, MutationObserver, Node, NodeFilter, NodeIterator, OffscreenCanvas, ReadableByteStreamController, ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, Request, requestAnimationFrame, requestIdleCallback, ResizeObserver, Response, setTimeout, ShadowRoot, structuredClone, StyleSheet, Text, TransformStream, TreeWalker, URLPattern, Window, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter } from './mod.js';
|
export { AbortController, AbortSignal, alert, atob, Blob, btoa, ByteLengthQueuingStrategy, cancelAnimationFrame, cancelIdleCallback, CanvasRenderingContext2D, CharacterData, clearTimeout, Comment, CountQueuingStrategy, CSSStyleSheet, CustomElementRegistry, CustomEvent, Document, DocumentFragment, DOMException, Element, Event, EventTarget, fetch, File, FormData, Headers, HTMLBodyElement, HTMLCanvasElement, HTMLDivElement, HTMLDocument, HTMLElement, HTMLHeadElement, HTMLHtmlElement, HTMLImageElement, HTMLSpanElement, HTMLStyleElement, HTMLTemplateElement, HTMLUnknownElement, Image, ImageData, IntersectionObserver, MediaQueryList, MutationObserver, Node, NodeFilter, NodeIterator, OffscreenCanvas, ReadableByteStreamController, ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, Request, requestAnimationFrame, requestIdleCallback, ResizeObserver, Response, setTimeout, ShadowRoot, structuredClone, StyleSheet, Text, TransformStream, TreeWalker, URLPattern, Window, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, } from './mod.js';
|
||||||
export declare const polyfill: {
|
export declare const polyfill: {
|
||||||
(target: any, options?: PolyfillOptions): any;
|
(target: any, options?: PolyfillOptions): any;
|
||||||
internals(target: any, name: string): any;
|
internals(target: any, name: string): any;
|
||||||
|
|
|
@ -888,6 +888,35 @@ importers:
|
||||||
'@astrojs/vue': link:../../../../integrations/vue
|
'@astrojs/vue': link:../../../../integrations/vue
|
||||||
astro: link:../../..
|
astro: link:../../..
|
||||||
|
|
||||||
|
packages/astro/e2e/fixtures/nested-recursive:
|
||||||
|
specifiers:
|
||||||
|
'@astrojs/preact': workspace:*
|
||||||
|
'@astrojs/react': workspace:*
|
||||||
|
'@astrojs/solid-js': workspace:*
|
||||||
|
'@astrojs/svelte': workspace:*
|
||||||
|
'@astrojs/vue': workspace:*
|
||||||
|
astro: workspace:*
|
||||||
|
preact: ^10.7.3
|
||||||
|
react: ^18.1.0
|
||||||
|
react-dom: ^18.1.0
|
||||||
|
solid-js: ^1.4.3
|
||||||
|
svelte: ^3.48.0
|
||||||
|
vue: ^3.2.36
|
||||||
|
dependencies:
|
||||||
|
preact: 10.7.3
|
||||||
|
react: 18.1.0
|
||||||
|
react-dom: 18.1.0_react@18.1.0
|
||||||
|
solid-js: 1.4.3
|
||||||
|
svelte: 3.48.0
|
||||||
|
vue: 3.2.37
|
||||||
|
devDependencies:
|
||||||
|
'@astrojs/preact': link:../../../../integrations/preact
|
||||||
|
'@astrojs/react': link:../../../../integrations/react
|
||||||
|
'@astrojs/solid-js': link:../../../../integrations/solid
|
||||||
|
'@astrojs/svelte': link:../../../../integrations/svelte
|
||||||
|
'@astrojs/vue': link:../../../../integrations/vue
|
||||||
|
astro: link:../../..
|
||||||
|
|
||||||
packages/astro/e2e/fixtures/nested-styles:
|
packages/astro/e2e/fixtures/nested-styles:
|
||||||
specifiers:
|
specifiers:
|
||||||
astro: workspace:*
|
astro: workspace:*
|
||||||
|
|
Loading…
Reference in a new issue