Closed sto3psl closed 3 years ago
I had the same request.
Would it be good that we introduce a reset-layout file __layout.svelte
(note the double _
), which means it will not inherit layouts up in the file tree, and all the child _layout.svelte
will inherit up to it but stops here.
IMO, it is simple, clear, and consistent to the current layout mechanism.
@zwz I like the idea, but would reconsider the name. From a quick glance the difference between _
and __
isn't that big and it could look like a typo. It could cause some hard to debug issues where people see their layout not apply (or apply incorrectly) and don't find the issue because the difference between _layout.svelte
and __layout.svelte
isn't easily visible.
Maybe _root_layout.svelte
could be used? This makes it super visible in a file tree, when a new route uses a new layout root. What do you think?
The idea to hide elements in a root layout based on segment doesn't really work since not the full route is available. Looking at my example bikes/_layout.svelte has segment === "category" but mountain-bike-promos is needed to know if layout components need to be hidden.
That's because that is the segment
and not the path (which could be removed in a future version, there is an issue discussing this). If you use the page
store, then you have access to the entire path (see the Sapper documentation on stores()
).
Regarding this request, this is something that has come up several times. There has been quite a bit of discussion around what the best way is to achieve this. Additional layouts
in the relevant route folder would be in-keeping with how Sapper handles things, but it could be confusing having multiple layouts. If you wanted to 'disinherit' a parent layout, you would need to add blank 'reset' layouts, for example. This doesn't feel like an ideal solution to me.
I'm inclined to think that this kind of behaviour is better handled with a config file, but I know that Rich is (or at least has been) against adding a config file to Sapper. I am personally of the opinion that with the amount of CLI options we now have and with feature requests like this, a configuration file should be reconsidered.
One other option is to use a special layouts folder to 'shadow' specific layouts (or vice-versa—define them in a layouts file and shadow them in routes
). This could work: custom layouts could be defined by matching the folder location and filename of the file you wish to customise which fits Sapper's routing model, but we would have another special folder. There are probably other problems with this too, what if you only want to 'disinherit' one of many layouts at an arbitrary depth gets weird, I can think of some partial solutions, but the resulting API would be confusing at best and limiting at worst.
If a config file is the best way to go then we need to have a conversation about whether or not we should have a configuration file in Sapper (and some related details) before this issue is tackled.
@pngwn could you elaborate how a config file would look like to solve this layout inheritance issue? Would there be a root config with a list of routes that inherit/don't inherit layouts?
For now I will make layout components and import them explicitly until there is a Sapper solution. I think that is what one would do in Next.js.
@pngwn could you elaborate how a config file would look like to solve this layout inheritance issue? Would there be a root config with a list of routes that inherit/don't inherit layouts?
No, I couldn't because I've given it approximately zero thought. But it seems like it might provide a relatively flexible solution.
Another idea:
<svelte:layout name="simple" />
or
<svelte:layout inherit={false} />
All the ideas proposed so far work for me.
@InstanceOfMichael I don't think this is an issue that should/can be solved in Svelte.
It only concerns how Sapper builds pages by building a Svelte component per page and nesting all _layout.svelte
components on the way.
You can see that here https://github.com/sveltejs/sapper/blob/master/src/core/create_manifest_data.ts#L29
and here
https://github.com/sveltejs/sapper/blob/master/src/core/create_app.ts#L271.
If I use Svelte outside of Sapper, there is no concept of layout inheritance.
Throwing in my 2 sense...
Would it make sense to return a configuration object as part of the output of preload or another method like preload? That way you have a place in JS where you can make the decision on how a layout needs to behave. Hypothetically.
I like the idea of it being part of the route path as well. But this would give programatic control.
For example a blog post that is part of a series and should inherit the current layout, vs is not part of a series and should not.
@khrome83 I'm not sure if that is a possible solution. As I understand it preload
get's called at runtime and the pages with all nested layouts are already compiled. That means the solution needs to happen at build time of the page components.
I think that the hole layout thing makes no sense, is better if each page imports the layout
I personally use this
//_layout.svelte => <slot></slot>
and have _root.svelte files that the pages can import instead of _layout.svelte
There is no layout in svelte why would be one in sapper?
Hello everyone, but maybe you’ll make a type like this person’s, he added _reset.svelte ? https://github.com/jakobrosenberg/svelte-filerouter
Any progress?
I found the routify (https://routify.dev/ ) approach very straightforward, if you have a _reset.svelte file on a folder, you use that and stop traversing the filesystem.
I don't get by there a 2 projects with so many similarities instead of one, it's a hard decision to make a choice between booth, I'm evaluating what to use for a very constrained device (a tv with a low end SOC), and using svelte instead of angular has bring a lot of improvement to the speed of the interface, but having to decide between so many options on routing is tiresome
I really like the _reset.svelte
approach.
I am amazed Sapper does not yet have the ability to use multiple layouts.
Created a PR with _reset.svelte implementation: sveltejs/sapper#1141
I actually quite like the _reset.svelte
idea. It's a pretty simple convention. Can we call it _layout.reset.svelte
or something like that?
@joycollector it appears there may need to be some more thought put into a solution for this.
As @pngwn pointed out:
but it could be confusing having multiple layouts. If you wanted to 'disinherit' a parent layout, you would need to add blank 'reset' layouts, for example. This doesn't feel like an ideal solution to me.
Likewise, what if one is nested three deep and wants to re-inherit the root layout. As much as I ::shudder:: at the thought of a config file to solve this, sadly 🐧 might be right.
Let's keep digging on solutions until we can hit something that answers all the right questions.
I'd favour the _reset
approach over the config file one as it's visual and simple to manage.
A few things:
_layout.reset.svelte
stops all the middle-hierarchy but still inherits the root, is a bit odd (non-obvious). Additionally, the only places I've ever wanted to do this, are places where I've not wanted the root layout._layout.reset.svelte
was actually the new layout, and just indicated that the layout didn't inherit from it's ancestors. That might be what we're discussing, but I'm not sure.What about if layout files had an (optional) layout name:
root/
├── _layout.svelte
├── index.svelte
├── subdir/
│ ├── _layout.svelte // inherits from root
│ ├── index.svelte
and then to manage inheritance:
root/
├── _layout.a.svelte
├── _layout.b.svelte
├── index.svelte
├── subdir/
│ ├── _layout.a.svelte // inherits from a layout
│ ├── index.svelte
│ ├── /somedir
│ │ ├── _layout.b.svelte
│ │ ├── index.svelte // uses layout in same dir, and inherits from b layout (skips a)
│ ├── /otherdir
│ │ ├── index.svelte // uses a layout a
This might be harder to express, however, but something along those lines would allow your layout choice not to depend on hierarchy.
@antony mmmm...interesting idea there, but I can poke holes in it. Which layout is the default? What if you want to use a particular layout, but don't need to change it—does that mean just putting an empty file with the same name in the nested folder? Seems an odd convention.
At first I liked the idea until I saw these holes. I am wondering though if this is closer...
@arxpoetica my intention was (and I didn't express it as I didn't want to muddy the waters), that it would work the same as it does now. The _layout.svelte
would remain the default, and if it doesn't exist, there would be no layout (similar to how it is now). That gives you the flexibility of an automatic layout reset.
The behaviour of sub-directories would be similar. It's not unusual to have (in file-system based routing), a layout file which simply defines <slot></slot>
to inherit a layout from a layer above. It's the same penalty you pay when using js files to duplicate content across URLs. It's a factor of file-system based routing - which I don't especially like, personally, but see the advantage of, for most applications.
I quite like the Routify way of doing it, just a _reset.svelte file in the folder you want to stop the inheritance in: https://github.com/sveltech/routify-starter/tree/master/src/pages/example/reset
Might it be a good idea to make the API cross-over as much as possible between the two projects? Maybe it's not something that's possible to do though.
Hi there, I am only learning sapper and svelte right now and I was browsing the web for a "sapper change layout", to discover the discussion is quite actual. My contribution is quite probably stupid as I am an amateur developer, but as a professional UX designer I would have felt quite "natural" that any _layout.svelte file inside a child route folder would automatically reset the parent layout. Is it stupid ? I was thinking such thing would be perfect as I want to change the background-color of the body. Anyway... Keep up the awesome work !!
@elRomano it's not stupid IMO, for me it would also make sense if layouts wouldn't add up. We would be able to create different layout components and use them as desired in _layout.svelte
. That way layout inheritance would be opt-in.
I think it should be easier but I did following for a personal project:
{#if segment === 'profile'}
<slot />
{:else}
<div>
.... main layout
{/if}
if the segment is profile it renders slot but the default is still my main layout, therefore I can use custom _layout.svelte for that segment.
Routify handles this problem really well with their _reset.svelte
files. I'm currently trying to set up a login page which will completely differ from the standard global layout of the site and it's looking to be quite difficult in sapper.
@arxpoetica
Likewise, what if one is nested three deep and wants to re-inherit the root layout.
Is this an actual concern? I don't really think so, but could be wrong. Also, if this is really needed, people can still have a root layout of
<MyLayout>
<slot/>
</MyLayout>
then just use _layout.reset
on some nested elements and three deep do
<MyLayout>
<slot/>
</MyLayout>
again.
It's funny that there is no solution for such a simple issue. I'm decided to rebuild my app with Nuxt.js (Vue.js) or maybe React. This wasn't what you told us Mr @Rich-Harris . "Oh Svelte is a compiler and we can add so much stuff built-in for the developers so they don't worry about building them they own. Yet we don't have a proper layout system."
@babakfp attitude + take a look at routify if you need extreme routing capabilities "right now or else"
@babakfp what a pity your tone.
People put so much time and effort to build a website and they are pretty right to be upset. Are you saying that they time wort nothing!!!. Learn Svelte and Sapper, build most parts of your website, and later you see that there is no layout system for such a simple issue. Later learn Vue and Nuxt and so much other stuff and you will see after months you didn't make any progress on your project :). @frederikhors @Florian-Schoenherr
Routify isn't a solution for me, I used this: https://github.com/sveltejs/sapper/issues/823#issuecomment-672262133 Thanks @gokayokyay
People put so much time and effort to build a website and they are pretty right to be upset.
It's an open source project. It's run and created by volunteers, who receive no compensation, and have no obligation to you or anybody else for any reason.
Please restrict further comments to either constructive criticism, useful feedback, or meaningful contributions or you will find your ability to comment on github issues removed.
@antony You are right, I'm sorry for my bad reaction. Did you see in the movies that the leader says something and everyone is like "yeeeeah yeaaaaa"? I was like that peoples when I sawed @Rich-Harris videos about Svelte and I was so impressed. Now I'm a little disappointed and sad because there is so much potential in Svelte and...
By the way, I did this for the layout issue.
// routes/_layout.svelte
<script>
export let segment;
import LayoutDafault from "../layouts/Default.svelte";
import HeaderAndFooter from "../layouts/HeaderAndFooter.svelte";
if (segment === undefined) segment = "index";
if (process.browser) document.body.classList.add(`${segment}-page`);
const pages = [
{
segment: "login",
layout: "Default",
},
{
segment: "lost-password",
layout: "Default",
},
{
segment: "index",
layout: "HeaderAndFooter",
},
];
</script>
{#each pages as page}
{#if page.segment === segment}
{#if page.layout === "Default"}
<LayoutDafault>
<slot />
</LayoutDafault>
{:else if page.layout === "HeaderAndFooter"}
<HeaderAndFooter>
<slot />
</HeaderAndFooter>
{/if}
{/if}
{/each}
In this way, we are just using 1 _layout
file and inside the layout, we are using normal components and base on the segment
dynamically decide to which component be used.
layouts
inside the src
folder.layouts
folder.Default
for the else
part of the logic.I was trying to do something like this, but it just didn't work.
{#each pages as page}
{#if page.segment === segment}
<{page.layout}>
<slot />
</{page.layout}>
{/if}
{/each}
I was trying to do something like this, but it just didn't work.
{#each pages as page} {#if page.segment === segment} <{page.layout}> <slot /> </{page.layout}> {/if} {/each}
Thank you for the clarification, for the above, could this work?
{#each pages as page}
{#if page.segment === segment}
<svelte:component this={page.layout}>
<slot />
</svelte:component>
{/if}
{/each}
As you usually don't pass anything dynamic to a Layout, it should be a nice solution.
@Florian-Schoenherr Awesome. It works fine. This is my final solution:
// routes/_layout.svelte
<script>
export let segment;
// src/layouts
import Default from "../layouts/Default.svelte";
import HeaderAndFooter from "../layouts/HeaderAndFooter.svelte";
import Dashboard from "../layouts/Dashboard.svelte";
const layoutManager = {
dashboard: Dashboard,
};
</script>
{#each Object.entries(layoutManager) as [route, layout]}
// For the "/" route
{#if segment === undefined}
<HeaderAndFooter>
<slot />
</HeaderAndFooter>
// For routes in `layoutManager`
{:else if route == segment}
<svelte:component this={layout}>
<slot />
</svelte:component>
// All other routes
{:else}
<Default>
<slot />
</Default>
{/if}
{/each}
layouts
inside the src
folder.Default
inside the layouts
folder.layouts
folder and import them inside the _layout.svelte
file (located in the routes
folder).Default
component is used for all routes except "/"
and the routed inside the layoutManager
variable.layoutManager
variable, the key is the route name and the value is the component name.@Florian-Schoenherr Awesome. It works fine. This is my final solution:
// routes/_layout.svelte <script> export let segment; // src/layouts import Default from "../layouts/Default.svelte"; import HeaderAndFooter from "../layouts/HeaderAndFooter.svelte"; import Dashboard from "../layouts/Dashboard.svelte"; const layoutManager = { dashboard: Dashboard, }; </script> {#each Object.entries(layoutManager) as [route, layout]} // For the "/" route {#if segment === undefined} <HeaderAndFooter> <slot /> </HeaderAndFooter> // For routes in `layoutManager` {:else if route == segment} <svelte:component this={layout}> <slot /> </svelte:component> // All other routes {:else} <Default> <slot /> </Default> {/if} {/each}
How to duplicate into your project
- Create a folder named
layouts
inside thesrc
folder.- Create a component named
Default
inside thelayouts
folder.- Create your custom components inside the
layouts
folder and import them inside the_layout.svelte
file (located in theroutes
folder).- The
Default
component is used for all routes except"/"
and the routed inside thelayoutManager
variable.- In the
layoutManager
variable, the key is the route name and the value is the component name.
So I'm a horrible developer! In the layoutManager
variable if I had 2 lengths of value, the layout going to be rendered two times. Can I somehow specify to break each loop? If the condition evaluates, loop stop looping anymore.
I guess this is ok :|
<script>
export let segment;
import Default from "../layouts/Default.svelte";
import HeaderAndFooter from "../layouts/HeaderAndFooter.svelte";
import Dashboard from "../layouts/Dashboard.svelte";
</script>
{#if segment === undefined || segment === "shop"}
<HeaderAndFooter>
<slot />
</HeaderAndFooter>
{:else if segment === "dashboard"}
<Dashboard>
<slot />
</Dashboard>
{:else}
<Default>
<slot />
</Default>
{/if}
@babakfp this seems to work for me:
<script lang="ts">
export let segment: string;
import Default from "../layouts/Default.svelte";
import HeaderAndFooter from "../layouts/HeaderAndFooter.svelte";
import Dashboard from "../layouts/Dashboard.svelte";
const defaultLayout = Default;
const customLayouts = {
undefined: HeaderAndFooter,
"shop": HeaderAndFooter,
"dashboard": Dashboard,
};
$: layout = customLayouts[segment] ?? defaultLayout;
</script>
<svelte:component this={layout} {segment}>
<slot />
</svelte:component>
I also pass on the segment
prop, so the individual layout components can basically stay 100% the same - as far as I've tested.
For those, like myself, coming across this issue with newer release of SvelteKit, this seems to be working well for me:
<script lang="ts">
import { page } from '$app/stores';
import Default from '$lib/layouts/Default.svelte';
import Alternative from '$lib/layouts/Alternative.svelte';
export let layout = Default;
const layouts = {
'/': Default,
'/other': Alternative
};
page.subscribe(({ path }) => {
layout = layouts[path] ?? Default;
});
</script>
<svelte:component this={layout}>
<slot />
</svelte:component>
Note, I made use of the $lib
prefix and stored my layouts there (./src/lib/layouts
)
I'm not fully up to date on this thread, because it's loooooong, but my current thinking is that we just turn $layout.svelte
into $layout.reset.svelte
any time we want a reset
What about another const export in $layout.svelte
's <script context="module">
instead? That would avoid having to deal with the situation where both $layout.svelte
and $layout.reset.svelte
are in the directory at the same time.
<script context="module">
export const reset = true;
</script>
This could also extend to pages where you can opt out of the layout on a per-page basis without needing a separate API. E.g. opting out for the /login
route but using the layout for everything else.
The route manifest is generated purely based on the files inside src/routes
. If you had files like this...
src/routes/$layout.svelte
src/routes/foo/$layout.svelte
src/routes/foo/bar/$layout.svelte
src/routes/foo/bar/baz/$layout.svelte
src/routes/foo/bar/baz/index.svelte
then your route manifest would look like this:
import * as layout from "../../../src/routes/$layout.svelte";
const components = [
() => import("../../../src/routes/foo/$layout.svelte"),
() => import("../../../src/routes/foo/bar/$layout.svelte"),
() => import("../../../src/routes/foo/bar/baz/$layout.svelte"),
() => import("../../../src/routes/foo/bar/baz/index.svelte")
];
export const routes = [
// src/routes/foo/bar/baz/index.svelte
[/^\/foo\/bar\/baz$/, [components[0], components[1], components[2], components[3]]]
];
export { layout };
In other words you need to import all those components. That's true regardless of whether one of those layouts has the reset
export, meaning the earlier imports are wasted. By contrast, if you use the filename, you can trivially do this:
src/routes/$layout.svelte
src/routes/foo/$layout.svelte
src/routes/foo/bar/$layout.svelte
-src/routes/foo/bar/baz/$layout.svelte
+src/routes/foo/bar/baz/$layout.reset.svelte
src/routes/foo/bar/baz/index.svelte
import * as layout from "../../../src/routes/$layout.svelte";
const components = [
- () => import("../../../src/routes/foo/$layout.svelte"),
- () => import("../../../src/routes/foo/bar/$layout.svelte"),
- () => import("../../../src/routes/foo/bar/baz/$layout.svelte"),
+ () => import("../../../src/routes/foo/bar/baz/$layout.reset.svelte"),
() => import("../../../src/routes/foo/bar/baz/index.svelte")
];
export const routes = [
// src/routes/foo/bar/baz/index.svelte
- [/^\/foo\/bar\/baz$/, [components[0], components[1], components[2], components[3]]]
+ [/^\/foo\/bar\/baz$/, [components[0], components[1]]]
];
export { layout };
(I'm glossing over the fact that right now the root layout is always included; we'd need to start explicitly including it in route definitions if we started having resets. Which is totally fine as it would actually simplify some stuff.)
Interesting! You can also use $layout.reset.svelte
with the /login use case anyway, so this seems like a good way to handle it.
Would it be possible then to have one layout in-between have it reset, but everything below will be able to opt in to all parent's layouts again? Like a $layout.restore.svelte
?
@dummdidumm what's the usecase? (I already asked this somewhere in this thread) I genuinely want to know, because I can't think of any route layout where you go down the resource-tree, a layout gets removed and deeper it gets restored. You could also do
<MyLayout>
<slot/>
</MyLayout>
on root $layout.svelte
and on the deeper nested $layout.reset.svelte
.
If it's no implementation overhead, of course it would be cool.
@Florian-Schoenherr A use case I have found is a kind of "summary" page in a long journey.
You would want the layout all the way down but somehow exclude it for that page and then be included afterwards.
At the moment I just toggle components in my layout page based on the page path, but that requires anyone updating my "summary" page to be aware that they need to go up the tree and update the layout if they want to make changes.
@seanlail 😕
In $layout.reset.svelte
you would have the advantage of just putting some kind of wrapper there again, without toggling.
What's the usecase for $layout.restore.svelte
, can anyone point me at a live project which resets and also restores layout? (+with too much overhead due to this not being available)
@Florian-Schoenherr very true. That does make it simpler. I guess then the restore part is left? Would the reset only work in that specific route and not cascade?
For those, like myself, coming across this issue with newer release of SvelteKit, this seems to be working well for me:
<script lang="ts"> import { page } from '$app/stores'; import Default from '$lib/layouts/Default.svelte'; import Alternative from '$lib/layouts/Alternative.svelte'; export let layout = Default; const layouts = { '/': Default, '/other': Alternative }; page.subscribe(({ path }) => { layout = layouts[path] ?? Default; }); </script> <svelte:component this={layout}> <slot /> </svelte:component>
Note, I made use of the
$lib
prefix and stored my layouts there (./src/lib/layouts
)
What about /shop/
? Your layout system will break if the user manually writes the URL like example.com/blog/
. This /
in the last of the URL is a common thing so, HEADSHOT :)
Hi
@Rich-Harris
Can't we just create the layout system like how it is in the NuxtJS? What if I want to change the layout if the x
was greater than the y
? This is not possible with the current layout system. So it means that SvelteKit isn't 100% reactive because the app can't react in some circumstances, right? (hmm emoji).
@SillyFreak Your solution was great at the time but now I'm facing an issue. Check out this repo please
Is your feature request related to a problem? Please describe.
When building a website I want pages to share a specific layout with common UI elements like a navigation, sidebar, footer, etc. Other pages might want to use different layouts like a custom landing page. This works fine as long as my landing page is not a sub page of an existing page with layout.
In this example
/bikes
has a custom layout. Individual bike routes like/bikes/fancy-bike-3000
add their own layout and inherit the one from/bikes
.The problem appears when I want to make a super fancy landing page to promote all my mountain bikes. This page is completely individual and uses its own layout.
/bikes/category/mountain-bike-promo
will always inherit the layout from/bikes
, but in this case it's not wanted.Sapper needs a feature to bail out of the layout inheritance and let sub pages define their own layouts.
Describe the solution you'd like I would like an API that allows me as developer to get more flexibility when needed but has the layout inheritance by default.
Ideas:
Use a special prop in layout components, that tell Sapper to not include layouts up in the file tree. Maybe something like this:
Nuxt.js has a
layout/
folder with custom layouts that you can specify in your page (https://nuxtjs.org/api/pages-layout). This probably doesn't work well with the current inheritance model but I like it.Describe alternatives you've considered
In the end it's always possible to import layout components like any other component on every page. This provides full flexibility but can be a bit cumbersome if something like the inheritance model already exists.
A different solution could be to structure routes differently but a site structure should ideally not be constrained by the framework of choice.
How important is this feature to you?
I think this is a use case every application of a particular size will eventually run into. May it be custom landing pages or interactive stories with data visualizations.
Additional context
There was a former discussion in this issue https://github.com/sveltejs/sapper/issues/809.
The idea to hide elements in a root layout based on
segment
doesn't really work since not the full route is available. Looking at my examplebikes/_layout.svelte
hassegment === "category"
butmountain-bike-promos
is needed to know if layout components need to be hidden.I look forward to any API discussion and can maybe come up with an implementation if this is a feature you want to integrate into Sapper.