-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Describe the problem
This has been discussed a lot in the past (for example #627), but now with Svelte 5 released, I think we can take a look at this from a different angle, considering all the new Svelte 5 toys.
Describe the proposed solution
Let's consider a rather simple usecase - header that changes based on page. Most pages would want to use the same (default) header, while some can customize it. This could be achieved with snippets or with additional +page-<name>.svelte files, for example +page-appbar.svelte.
With snippets (demo)
Allow pages to export snippets:
<script>
export const snippets = { appbar }
</script>
{#snippet appbar()} ... {/snippet}that can then be used in layouts:
<script>
const { children, appbar } = $props()
</script>
<aside> {@render appbar?.()} </aside>
<main> {@render children()} </main>Pros: Code sharing between app bar and page, easy to use
Cons: Not SSR-friendly. The page needs to first render before the snippet is created, which then needs to be sent to the layout for re-rendering.
With additional files (demo)
Allow placing additional +page files that can be used in layouts:
+page.svelte+page-appbar.svelte
<script>
const { children, appbar } = $props()
</script>
<aside> {@render appbar()} </aside>
<main> {@render children()} </main>Pros: SSR-friendly, intuitive (+page goes to children, +page-x goes to x)
Cons: Need a communication mechanism to synchronize the page and named slots, as they don't share code.
Both of these method can sort-of be achieved in user land, but neither of them is great.
With magic (not demo)
Syntactically I think this is great, but it would need a bit (more) help from the Svelte compiler to work.
<!-- +page.svelte -->
<script>
const { data, Header, Footer } = $props()
</script>
<Header> This is a header </Header>
Main content
<Footer> This is a footer </Footer>
<!-- +layout.svelte -->
<script>
const { children, header, footer } = $props()
</script>
<header> {@render header()} </header>
<main> {@render children()} </main>
<footer> {@render footer()} </footer>For this to work, a div with display: contents would need to be created that would hold the header/footer content, which is rendered inside the snippet, and a virtual component with this div as its target would be passed to the page:
<script>
import Layout from "./+layout.svelte"
import Page from "./+page.svelte"
const headerDiv = createDivDisplayContents()
const footerDiv = createDivDisplayContents()
const Header = createComponent({ target: headerDiv })
const Footer = createComponent({ target: footerDiv })
</script>
<Layout>
{#snippet header()}
{@insert headerDiv}
{/snipper}
{#snippet footer()}
{@insert footerDiv}
{/snipper}
<Page {Header} {Footer} />
</Layout>Conclusions
This is pretty unhinged, but would it be doable?