Skip to content
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

Merged
merged 20 commits into from
Jun 23, 2022
Merged

Enable named slots in renderers #3652

merged 20 commits into from
Jun 23, 2022

Conversation

natemoo-re
Copy link
Member

@natemoo-re natemoo-re commented Jun 20, 2022

Changes

  • Enables renderers to take advantage of named slots.
  • Renames astro-fragment to astro-slot.
  • Also handles client:only with data-astro-template.
  • For JSX-based frameworks (React, Preact, Solid), renderers inject a new property called slots. The default slot content is still passed as children.
  • For frameworks that support slots (Vue, Svelte), renderers pass the slots with framework-native semantics.

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.

layout-astro

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 as children.

sidebar-jsx

Vue

Vue's API supports slots. Astro will pass slot contents using Vue's normal API.

sidebar-vue

Svelte

Svelte's API supports slots. Astro will pass slot contents using Svelte's normal API.

Note: This PR also allows us to get rid of our strange SvelteWrapper component that has been prone to breaking.

sidebar-svelte

Testing

  • Added named slots to existing framework slot tests
  • Added e2e test for recursive hydration

Docs

TODO

@changeset-bot
Copy link

changeset-bot bot commented Jun 20, 2022

🦋 Changeset detected

Latest commit: b6fdaba

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@astrojs/lit Minor
@astrojs/preact Minor
@astrojs/react Minor
@astrojs/solid-js Minor
astro Patch
@astrojs/svelte Minor
@astrojs/vue Minor
@e2e/lit-component Patch
@e2e/preact-component Patch
@e2e/react-component Patch
@e2e/ts-resolution Patch
@e2e/solid-component Patch
@e2e/svelte-component Patch

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

@github-actions github-actions bot added pkg: astro Related to the core `astro` package (scope) pkg: preact Related to Preact (scope) pkg: react Related to React (scope) pkg: solid Related to Solid (scope) pkg: svelte Related to Svelte (scope) pkg: vue Related to Vue (scope) pkg: integration Related to any renderer integration (scope) labels Jun 20, 2022
@@ -1,15 +1,43 @@
import SvelteWrapper from './Wrapper.svelte';
Copy link
Member Author

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!

Comment on lines +26 to +43
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,
]
}
Copy link
Member Author

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 });
Copy link
Member Author

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');
Copy link
Member Author

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.

Comment on lines 78 to 80
for (const slot of slotted) {
slots[slot.getAttribute('name') || 'default'] = slot.innerHTML;
}
Copy link
Member Author

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.

Comment on lines 213 to 219
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);
Copy link
Member Author

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.

@github-actions github-actions bot added the pkg: lit Related to Lit (scope) label Jun 21, 2022
@natemoo-re natemoo-re marked this pull request as ready for review June 22, 2022 15:27
Copy link
Contributor

@tony-sull tony-sull left a 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!

packages/astro/src/runtime/server/index.ts Outdated Show resolved Hide resolved
@natemoo-re natemoo-re merged commit 7373d61 into main Jun 23, 2022
@natemoo-re natemoo-re deleted the feat/slots branch June 23, 2022 15:10
@github-actions github-actions bot mentioned this pull request Jun 23, 2022
SiriousHunter pushed a commit to SiriousHunter/astro that referenced this pull request Feb 3, 2023
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg: astro Related to the core `astro` package (scope) pkg: integration Related to any renderer integration (scope) pkg: lit Related to Lit (scope) pkg: preact Related to Preact (scope) pkg: react Related to React (scope) pkg: solid Related to Solid (scope) pkg: svelte Related to Svelte (scope) pkg: vue Related to Vue (scope)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants