-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable named slots in renderers #3652
Conversation
🦋 Changeset detectedLatest commit: b6fdaba The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@@ -1,15 +1,43 @@ | |||
import SvelteWrapper from './Wrapper.svelte'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: Custom (hacky) SvelteWrapper is no longer needed!
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, | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Svelte has difficulty accepting slots from an external source because it expects them to all conform to Svelte's compiled output. After some digging into svelte-jsx
, this is a stable internal API for Svelte v3.
This code allows us to pass our HTML strings in as Svelte-compatible slots.
for (const [key, value] of Object.entries(slotted)) { | ||
slots[key] = () => `<astro-slot${key === 'default' ? '' : ` name="${key}"`}>${value}</astro-slot>`; | ||
} | ||
const { html } = Component.render(props, { $$slots: slots }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the server (where ssr
compilation is enabled), Svelte makes it really easy to pass in $$slots
. I don't know why they don't mirror this simplicity on the client.
let fragment = this.querySelector('astro-fragment'); | ||
if (fragment == null && this.hasAttribute('tmpl')) { | ||
// If there is no child fragment, check to see if there is a template. | ||
const slotted = this.querySelectorAll('astro-slot'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can now have many astro-slot
elements, so we do a querySelectorAll
.
for (const slot of slotted) { | ||
slots[slot.getAttribute('name') || 'default'] = slot.innerHTML; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For every astro-slot
we find, push their innerHTML
to our slots
object.
const promises: Promise<void>[] = [] | ||
for (const [key, value] of Object.entries(slots)) { | ||
promises.push(renderSlot(result, value as string).then((output) => { | ||
if (output?.trim() !== '') { | ||
children[key] = output; | ||
} | ||
})) | ||
} | ||
await Promise.all(promises); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than just rendering out children
, we render all slots
.
Would have been nice to do this lazily/on-demand but most frameworks aren't async
compatible, unfortunately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! One tiny note but it's personal preference, very cool to see named slot support added for all frameworks!
* 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>
Changes
astro-fragment
toastro-slot
.client:only
withdata-astro-template
.slots
. The default slot content is still passed aschildren
.API Usage
Let's say you want to build a Sidebar component. Previously, you may have put all of this code inside of a framework component for the best DX. With this PR, it will be possible to pass named Astro slots to framework components for maximum composability.
JSX Frameworks (React, Preact, Solid)
Named slots will be converted to top-level props, similar to
children
. These props are framework components, NOT strings.The
default
slot is still passed aschildren
.Vue
Vue's API supports slots. Astro will pass slot contents using Vue's normal API.
Svelte
Svelte's API supports slots. Astro will pass slot contents using Svelte's normal API.
Testing
Docs
TODO