ElMassimo / iles

🏝 The joyful site generator
https://iles.pages.dev
MIT License
1.08k stars 31 forks source link

Add support for layouts with multiple (named) slots #106

Closed davidlueder closed 2 years ago

davidlueder commented 2 years ago

Hi, i would like to use layouts with multiple slots but did not find a way to reference any named slots from within a page.

Example (layout/default.vue):

<template>
    <header>
        <h1>Website</h1>
        <slot name="header" />
    </header>

...

    <main>
        <slot />
    </main>
</template>

Vue pages can only contain a single <template> tag, maybe a custom component that works like like <Head> could be the solution.

Example (pages/index.vue):

<template>
    <SlotContent name="header">
        <h2>Important headline</h2>
    </SlotContent>

    <p>Other content</p>
</template>
ElMassimo commented 2 years ago

Hi David!

While pages can only currently provide a default slot, layouts can provide several slots to parent layouts.

That usually covers plenty of ground, given that you can use page-specific information in layouts by using $frontmatter and $meta.

Would you share a real-life use case where it would be convenient to use several slots at the page level?

davidlueder commented 2 years ago

Hi Máximo, in my opinion this would be helpful if someone wants to split content in different sections on the layout level. I think the definition of a layout itself requires the option to fill different slots: "the way in which the parts of something are arranged or laid out".

Maybe this is the reason that even the vuejs documentation uses a "BaseLayout" as an example for multiple named slots: https://vuejs.org/guide/components/slots.html#named-slots

When we define a Layout like this:

<template>
    <header>
        <slot name="header-content" />
    </header>

    <nav>
        <!-- Navigation etc... -->
    </nav>

    <div class="container">
        <aside>
            <slot name="side-content" />
        </aside>

        <main>
            <slot />
        </main>
    </div>
</template>

It can be styled on the layout level and the content of a page could be separated.

Sure, there are other options. For example, someone might (mis)use (like in the vuejs docs) a component for that:

<template>
    <FakeLayoutComponent>
        <template #header-content> ... </template>
        <template #side-content> ... </template>
        <template> ... </template>
    </FakeLayoutComponent>
</template>

Using $frontmatter and $meta is a great option for simple things like title, author and other information about the page. As far as i know you could even use a frontmatter parameter for html code, but that really doesn't feel right.

ElMassimo commented 2 years ago

I implemented a proof-of-concept, which enables .vue pages to pass slots to the parent layout using the standard syntax:

<template>
  <template #header>
    <h2>Important headline</h2>
  </template>

  <p>Other content</p>
</template>

The only downside is that it requires an additional SFC parse of .vue pages on each pass so it's a bit slower (although it's only a few ms). I'd like to benchmark it in a slower computer before deciding to go ahead with this change.

Please give it a try, and let me know how it goes 😃

davidlueder commented 2 years ago

That was really fast... I will try to test this tomorrow!

ElMassimo commented 2 years ago

Closing for now as I haven't found any implementation without serious downsides, see #114 for further details.

If you need to provide HTML to layouts, you can use the following pattern:

Layout

src/layouts/default.vue

<template>
  <header><slot name="header">Welcome</slot></header>
  <main><slot/></main>
</template>
Page

Opt-out of the default layout using layout="false", and add an existing layout to the template, passing named slots as usual.

<template layout="false">
  <DefaultLayout>
    <template #header>I'm on the header!</template>
    I'm on the main section!
  </DefaultLayout>
</template>

Have in mind that iles automatically exposes existing layouts using the <${Name}Layout> convention, no need to import them.

It's not as terse as the proof-of-concept, but it's not too bad either.