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

Named layouts thingy #12928

Open
rChaoz opened this issue Oct 31, 2024 · 1 comment
Open

Named layouts thingy #12928

rChaoz opened this issue Oct 31, 2024 · 1 comment

Comments

@rChaoz
Copy link

rChaoz commented Oct 31, 2024

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?

@brunnerh
Copy link
Member

brunnerh commented Oct 31, 2024

This would be something you can do already:

// $lib/layout-slots.svelte.js
import { getContext, setContext } from 'svelte';

const key = Symbol('layout-slots');

export function initSlots() {
	const slots = $state({});
	return setContext(key, slots);
}

export function setSlots(slots) {
	const context = getContext(key);
	Object.assign(context, slots);
}
<!-- +layout.svelte -->
<script>
	import { initSlots } from '$lib/layout-slots.svelte.js';

	let { children } = $props();

	const slots = initSlots();
</script>

<!--
	run page logic first to get slot snippets,
	the page should have no output.
-->
{@render children()}

<h1>{@render slots.heading()}</h1>

{@render slots.children()}
<!-- +page.svelte -->
<script>
	import { setSlots } from '$lib/layout-slots.svelte.js';

	setSlots({ heading, children });
</script>

{#snippet heading()}
	Hello there
{/snippet}

{#snippet children()}
	Main slot content
{/snippet}

SvelteLab

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants