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

Introduce a feature for bottom-up page-to-layout communication #5951

Closed
aradalvand opened this issue Aug 16, 2022 · 42 comments · Fixed by #6226
Closed

Introduce a feature for bottom-up page-to-layout communication #5951

aradalvand opened this issue Aug 16, 2022 · 42 comments · Fixed by #6226

Comments

@aradalvand
Copy link
Contributor

aradalvand commented Aug 16, 2022

Describe the problem

Prior to the recent API overhaul (#5748), I used to have a Meta.svelte component that was rendered by my root layout. This component would read from $page.stuff — which I had typed in app.d.ts — and set various page-level head tags such as <title>, <meta name="description"> and so on based on those stuff values. Every page would then have to provide this information in its load function, like so:

export function load() {
  return {
    stuff: {
      meta: {
        title: 'Home page',
        titleSuffix: 'long',
        description: 'Blah blah blah'
      }
    }
  };
}

My app.d.ts file looked something like:

declare namespace App {
  interface Stuff {
    meta: {
      title: string;
      titleSuffix?: 'short' | 'long' | 'none';
      description?: string;
    };
  }
}

And the <Meta> component:

<script lang="ts">
  import { page } from '$app/stores';

  const titleSuffixes = new Map<TitleSuffix, string>([
    ['short', ' | Foo'],
    ['long', ' | Foo Bar Buzz'],
    ['none', '']
  ]);

  $: ({
    title,
    titleSuffix = 'short',
    description,
  } = $page.stuff.meta);
</script>

<svelte:head>
  <title>{title + titleSuffixes.get(titleSuffix)}</title>

  {#if description}
    <meta name="description" content={description} />
  {/if}
</svelte:head>

Now, ever since the API has been re-designed and the stuff thing has been removed, I've been struggling to re-create this properly.
Asking questions on the Discord server and reading this part of the migration guide lead me to think that returning this information as part of the data returned by each load function is apparently the way things like this are meant to be handled now. But I find that this is an almost unacceptable alternative, for two major reasons + a more minor one:

  1. No type-safety while providing the values in the load functions:
export const load: PageLoad = () => {
  return {
    meta: {
      title: true, // Nothing's preventing me from doing this
      titleSuffix: 'somenonsense', // or this
      descriptititoitnotintotn: 'Blah blah blah', // or this 
    }
  };
};
  1. No type-safety while reading the values in the <Meta> component

Meta.svelte:

<script lang="ts">
    import { page } from '$app/stores';
</script>

<svelte:head>
    <!-- Absolutely no type-safety here, `meta` is `any` -->
    <title>{$page.data.meta.title}</title>
</svelte:head>
  1. These values will be visible from the +page.svelte file included as part of its data prop, which is weird since it's actually none of its business.

image
meta isn't really something that this page is concerned with, so this is slightly awkward.

This (particularly the lack of type-safety) clearly yields a drastically poorer developer experience compared to the previous API. I sense that not enough thought has been put into this particular requirement when re-designing the API, namely bottom-up communication between pages and layouts. Page data isn't really fit for this use case at all.

Describe the proposed solution

I don't have a clear-cut proposal here, the general idea is that perhaps you could consider introducing a new construct designed specifically for bottom-up page-to-layout communication, in order to properly accommodate use cases like this, which are by no means uncommon or unusual, or niche. It actually gets more complex in a real-world app where there are typically multiple levels of layouts, and each of them receives a different set of configurations/settings; which is why I believe this is something that deserves a dedicated feature, so to speak.

Importance

crucial

Additional Information

Please don't just hand wave this away, thanks. I know it's annoying that literally dozens of issues get created on this repo every single day and naturally it's almost impossible to give each and every single one of them a lot of attention, but this one is a real problem, I think, and one that is in need of a response from your side.

And tell me if I'm missing something, because I certainly raised an eyebrow while trying to do this but failing to find a satisfying solution, so it's possible that I'm unaware of something here.

@rmunn
Copy link
Contributor

rmunn commented Aug 17, 2022

The discussion at #5748 (comment) suggested using stores for this purpose. The top-level layout puts a store, let's say it's called meta, into data. Then it subscribes to that store to make any changes (to title, etc.) that are needed. The page then does something like this (note: code NOT tested, just written into the comment box, so it could well be wrong in significant details):

const { meta } = data;
$meta.title = 'New title for this page';

That should fire the subscription in the top-level layout, which would then update the title with the new value.

@dummdidumm
Copy link
Member

I don't think we will reintroduce something else, the "this pops up in my data" is negligible to me - you don't have to use it in your component. The type story is worse for now, but we will implement some level of type safety again for this.

@aradalvand
Copy link
Contributor Author

aradalvand commented Aug 17, 2022

@rmunn That's an interesting idea, but I tried it and it doesn't work during SSR which is a total deal breaker, as you would obviously need meta information like <title> to be set during SSR.

That should fire the subscription in the top-level layout, which would then update the title with the new value.

Unfortuantely that's precisely what doesn't happen (during SSR), for some reason.

@aradalvand
Copy link
Contributor Author

aradalvand commented Aug 17, 2022

I don't think we will reintroduce something else, the "this pops up in my data" is negligible to me - you don't have to use it in your component. The type story is worse for now, but we will implement some level of type safety again for this.

@dummdidumm As I said, that one is the most minor issue, but I thought it's worth mentioning as it could be indicative of a larger conceptual problem. The type-safety is non-existent, and I'm not sure exactly why you're opposed to introducing a new distinct construct for this, but even if you don't want to do that, at the very least I would say just make using stores possible here.

As soon as I read @rmunn's comment I thought yeah that's probably the perfect solution for this, but it turns out it doesn't work during SSR and that's a deal breaker, I would appreciate it if you explain why.

To be more specific, when I set a store value inside a page's <script> tag, the changes to the store aren't reflected in the layout in SSR, I hope that makes sense.

@Rich-Harris
Copy link
Member

The type safety you had before was illusory — nothing was forcing your page load functions to return the properties you needed on stuff. You had type safety insofar as it would prevent you from returning the wrong properties, but it didn't guarantee the right properties. Runtime checking was always necessary.

Stores won't work for this in SSR because you're not setting the value until the leaf is rendered, by which point the layout... has already been rendered. $page.data works because the data for the page is resolved before rendering happens.

@aradalvand
Copy link
Contributor Author

aradalvand commented Aug 17, 2022

@Rich-Harris So then what approach would you take for implementing things like this currently? And it's not just meta information, you might have a hideFooter "layout setting" that needs to be set by the pages, and things of that nature in general?

This gets worse if you need, for example, default values for these settings. All of this renders $page.data unsuitable for this use case, in my opinion, which is the point I'm trying to make.

@abdo643-HULK
Copy link

@Rich-Harris So then what approach would you take for implementing things like this currently? And it's not just meta information, you may have a hideFooter "layout setting" that needs to be set by the pages, and things of that nature in general?

This gets worse if you need, for example, default values for these settings, all of this renders $page.data unsuitable for this use case; that's the point I'm trying to make.

A derived store in the root where you read the props you need from $page.data

@aradalvand
Copy link
Contributor Author

@abdo643-HULK That still has all the downsides and limitations of $page.data that I've touched upon. Doesn't really solve anything.

@aradalvand
Copy link
Contributor Author

aradalvand commented Aug 17, 2022

Something like $page.statics proposed in #1459 (+ #269) is more or less what I'm suggesting. Those were deemed resolved when stuff was allowed to be set in pages and read by the layout, but now we're back to where we were since there's no stuff anymore and the supposed alternative which is $page.data is simply not fitting for such use cases.

I hope this is reconsidered. SvelteKit doesn't currently have a satisfactory answer to this need.

@abdo643-HULK
Copy link

abdo643-HULK commented Aug 17, 2022

Because it is a derived store you can create your own type for ts. And you also said that things like meta aren't concerns of the +page.svelte but I don't agree with that because each page has it's own meta data. Just because the component doesn't consume it because of layouts doesn't make it not part of the page data

@aradalvand
Copy link
Contributor Author

aradalvand commented Aug 17, 2022

Because it is a derived store you can create your own type for ts.

@abdo643-HULK You can create your own TS type already, and then just convert $page.data.meta to it like $page.data.meta as YourType, introducing a derived store is not necessary, that's not the point, the point is that any time you find yourself doing an explicit type conversion like this, that's what we call lack of proper typing.

And you also said that things like meta aren't concerns of the +page.svelte but I don't agree with that because each page has it's own meta data. Just because the component doesn't consume it because of layouts doesn't make it not part of the page data

Perhaps that wasn't the best of examples, fair enough, but I also provided others such as hideFooter, etc. The basic idea is simple and shouldn't be controversial: A layout can have various "settings" that are to be set by any page that derives from it, that's not really an exotic or unusual notion, so my point stands.

@aradalvand
Copy link
Contributor Author

aradalvand commented Aug 17, 2022

It is also awkward to have to create a separate +page.js file and a load function only to set some layout settings for a page, even though the page might not actually do anything else in its load function, the +page.svelte file feels like a more appropriate place for this kind of thing. I think this all clearly begs for a separate feature.

I must say I find it deeply frustrating to have to argue so elaborately for something that I feel should be obvious to anyone after minimal examination.

@dummdidumm
Copy link
Member

You think you "elaborately" argue, I think you are being passive-aggressive. stuff was confusing to a lot of people, and $page.data achieves the same with a uniform solution which satisfied both the layout use case as well as the "don't want to do prop drilling" use case. What I decipher from your posts is that you don't want to mix layout concerns with other data in a single store, which doesn't sound like a concern to me. You are free to introduce a convention to your code that all layout-related data should be within a layout prop or sth like that. Your argument of needing to provide a load function was true for stuff, too, so I don't see a drawback here - and Rich explained why it isn't possible to do this in +page.svelte, because it's too late by then for SSR.

What's left is the TypeScript-story, which I agree needs to get better.

@aradalvand
Copy link
Contributor Author

aradalvand commented Aug 17, 2022

Your argument of needing to provide a load function was true for stuff, too, so I don't see a drawback here

Yes but it's obviously exacerbated by virtue of the fact that now you need to have a separate file for the load function and previously you didn't so it wasn't as big of a deal.

What I decipher from your posts is that you don't want to mix layout concerns with other data in a single store, which doesn't sound like a concern to me.
What's left is the TypeScript-story, which I agree needs to get better.

No, it's far deeper than that, even if the typing is improved, this will still not be a perfectly suitable solution for this kind of scenario. For instance, providing default values, as I mentioned above, would be non-obvious and awkward in this approach. And if you have multiple levels of layouts, the merging behavior won't work as expected, since a shallow merge is performed on the object. Consider the following (rather simple) example:

routes/+layout.svelte:

<script>
  import { page } from '$app/stores';
</script>

<Header type={$page.data.layout.headerType} />

<!-- As a side note, this is more or less how you would have to provide default values, and it's not ideal since the default values are scattered around the entire file and you would potentially have to repeat them in multiple places or otherwise resort to more awkward workarounds -->
{#if $page.data.layout.showFooter ?? true}
  <Footer />
{/if}

routes/whatever/+layout.js:

export function load(){ 
  return {
    layout: {
      headerType: 'secondary'
    }
  };
}

routes/whatever/deeper/+page.js:

export function load(){ 
  return {
    layout: {
      showFooter: false,
    }
  };
}

The resulting $page.data here would not include the values that routes/whatever/+layout.js set; because routes/whatever/deeper/+page.js essentially overrode the entire top-level property (layout) even though it only meant to modify a single property within it. Yes I know I can work around this like so:

routes/whatever/deeper/+page.js:

export async function load({ parent }){ 
  const { layout } = await parent();
  return {
    layout: {
      ...layout
      showFooter: false,
    }
  };
}

But now these two load functions are run serially as opposed to concurrently and that would have a performance cost if they're sending fetch requests and whatnot. I don't really need them to run serially, this is just what I have to do in this particular case because there's no other way to achieve what is needed.

The same problem more or less existed for stuff too, it's not like I'm a huge fan of stuff, I'm just saying that this is the type of thing that I think would greatly benefit from a native feature that's specifically designed around it, because trying to do it using already-existing features (page data in this case) clearly yields a lot of awkwardness and requires hacky and non-obvious workarounds. It's not really fitting, that's the best word I can think of to describe it. Correct me if I'm wrong.

@pilcrowonpaper
Copy link

Maybe route specific hooks might partially address the problem (#5763), like /some-route/+hooks.js?

@elliott-with-the-longest-name-on-github
Copy link
Contributor

#6017 will mostly fix this issue, allowing loads to run in parallel but data resolution to occur as a (partial or complete) waterfall.

Automatic types for $page.data would be chaotic at best, completely unusable at worst, given that whatever type we gave it would have to be the complete, merged result of every possible load return from every possible route, wrapped in Partial. I mean, it's technically possible, but at that point would essentially just be autocomplete.

@Rich-Harris
Copy link
Member

One thought that occurs to me: in the same way we used to have App.Stuff, we could have an App.PageData type that every PageLoad function is required to return, then we could use that same type for $page.data:

// src/app.d.ts
declare namespace App {
  interface PageData {
    title: string;
  }
}
// $types.d.ts
export type PageLoad = Kit.PageLoad<Params, InputData, App.PageData & Record<string, any>>;
import type { PageLoad } from './$types';

export const load: PageLoad = () => ({
  title: `if this is missing, it'll cause red squigglies`,
  other: {
    stuff: 'yay'
  }
});
// types/ambient.d.ts inside Kit
export interface Page<Params extends Record<string, string> = Record<string, string>> {
  url: URL;
  params: Params;
  routeId: string | null;
  status: number;
  error: HttpError | Error | null;
-  data: Record<string, any>;
+  data: App.PageData & Record<string, any>;
}

A fancier version would allow a PageLoad function not to return title if a parent layout already did (I think that's possible), so that a group of pages could share a title.

@elliott-with-the-longest-name-on-github
Copy link
Contributor

@Rich-Harris

I'd make one change: App.Data wrapped in Partial and make it apply to Layout loads as well. Making it required for ALL loads seems extremely restrictive and would limit its usefulness. Wrapping it in Partial at least guarantees assignment type-safety but allows for more flexibility.

@Rich-Harris
Copy link
Member

It wouldn't apply to all load functions, just PageLoad. Making it Partial defeats the object, which is for typechecking to fail if a page's load function doesn't return the required data

@elliott-with-the-longest-name-on-github
Copy link
Contributor

It wouldn't apply to all load functions, just PageLoad. Making it Partial defeats the object, which is for typechecking to fail if a page's load function doesn't return the required data

I'm saying that the object shouldn't be "A page's load should return all of the required data", because the number of cases in which all of my pages share a set of data that they should all return 100% of the time is 0. What actually does happen is that all of my pages share a set of data that they might or might not return, and what's important is that if they do return it, that it be under the correct key and have the correct type. Partial<App.Data> solves a wider, more flexible problem, whereas App.PageData solves a very specific, (probably?) uncommon scenario where you can guarantee that every page in your entire app should return the same set of values.

(It would also allow us to apply it to layout loads, which would make more sense, as both layout and page loads can populate $page.data.)

@aradalvand
Copy link
Contributor Author

aradalvand commented Aug 18, 2022

because the number of cases in which all of my pages share a set of data that they should all return 100% of the time is 0.

That's not necessarily true, if you return page metadata like title, description, and so on as part of the data, then every page would return those values.
And if you don't have such a scenario, then why not just make the properties in App.PageData optional yourself? Am I missing something?

@Rich-Harris
Copy link
Member

Right — the goal here is to be able to guarantee that you can do this sort of thing:

<svelte:head>
  <title>{$page.data.title}<title>
</svelte:head>

For that to work, every PageLoad function must return title. Half-measures like Partial take us back to the illusory type safety we had with App.Stuff.

It sounds like you want an App.DataThatMightExist interface. I'm not personally convinced there's a good use case for it (particularly post-#5994), but if there is, I think it's a separate discussion (other than making sure we pick a future-proof name for App.PageData).

@elliott-with-the-longest-name-on-github
Copy link
Contributor

because the number of cases in which all of my pages share a set of data that they should all return 100% of the time is 0.

That's not necessarily true, if you return page metadata like title, description, and so on as part of the data, then every page would return those values.
And if you don't have such a scenario, then why not just make the properties in App.PageData optional yourself? Am I missing something?

That's true -- you could make the properties optional... would that allow us to use this for layout loads? My biggest concern here is that we create a solution that works for both.

@Rich-Harris
Copy link
Member

Or App.RequiredPageData/App.RequiredData

@aradalvand
Copy link
Contributor Author

aradalvand commented Aug 18, 2022

Or App.RequiredPageData/App.RequiredData

App.SharedPageData may be more accurate since not everything within that type is necessarily "required". Some properties could be optional.

@elliott-with-the-longest-name-on-github
Copy link
Contributor

For that to work, every PageLoad function must return title. Half-measures like Partial take us back to the illusory type safety we had with App.Stuff.

This just isn't true.

Partial<{ foo: string }> disallows assigning anything but a string or undefined to foo. It says "if foo exists, it is definitely a string", which eliminates a whole class of runtime type checks and allows me to just say $page.data?.foo ?? 'fallback' and never think about it again. Whereas without it, $page.data.fou is very typo-able and has the same type as $page.data.foo -- any.

@aradalvand
Copy link
Contributor Author

aradalvand commented Aug 18, 2022

What if different routes have different shared page data by the way? This is rather common, I believe. But this solution (as is right now) would fail to account for those scenarios.

Imagine you have src/routes/panel with a +layout.svelte and some pages deep inside, you might want every page within that sub-directory/route to have some special shared data (like activeMenuItem or something).

@elliott-with-the-longest-name-on-github
Copy link
Contributor

What if different routes have different shared page data by the way? This is rather common, I believe.
Imagine you have src/routes/panel with a +layout.svelte and some pages deep inside, you might want every page within that sub-directory/route to have some special shared data (like activeMenuItem or something).

This is exactly why I advocate for Partial<App.Data> and extending the definition to LayoutLoad as well. App.Data is just way too restrictive for real-world uses.

@aradalvand
Copy link
Contributor Author

This is exactly why I advocate for Partial<App.Data> and extending the definition to LayoutLoad as well. App.Data is just way too restrictive for real-world uses.

But that doesn't seem like the optimal solution, ideally we should have "route-scoped" shared data, if that makes sense, instead of an app-wide one defined in app.d.ts.

@elliott-with-the-longest-name-on-github
Copy link
Contributor

This is exactly why I advocate for Partial<App.Data> and extending the definition to LayoutLoad as well. App.Data is just way too restrictive for real-world uses.

But that doesn't seem like the optimal solution, ideally we should have "route-scoped" shared data, if that makes sense, instead of an app-wide one defined in app.d.ts.

In an ideal world I'd agree with you, but we have to assign $page.data one and only one type, as there's no way to scope its TypeScript types to a specific route. (Maybe there is some hacky, super-hilariously-complicated way, but it'd be a fragile bodge-upon-a-bodge that would be just looking for a way to break...)

@aradalvand
Copy link
Contributor Author

aradalvand commented Aug 18, 2022

In an ideal world I'd agree with you, but we have to assign $page.data one and only one` type, as there's no way to scope its TypeScript types to a specific route. (Maybe there is some hacky, super-hilariously-complicated way, but it'd be a fragile bodge-upon-a-bodge that would be just looking for a way to break...)

Well why not though? Couldn't SvelteKit utilize the same trick used for ./$types here as well? Something like ./$stores or something, I guess.

Not doing this will result in a sub-par type-safety experience however much you tweak it.

@elliott-with-the-longest-name-on-github
Copy link
Contributor

In an ideal world I'd agree with you, but we have to assign $page.data one and only one` type, as there's no way to scope its TypeScript types to a specific route. (Maybe there is some hacky, super-hilariously-complicated way, but it'd be a fragile bodge-upon-a-bodge that would be just looking for a way to break...)

Well why not though? Couldn't SvelteKit utilize the same trick used for ./$types here as well? Something like ./$stores or something, I guess.

Because $page.data is imported from one module to everywhere, and therefore has only one type definition.

@babichjacob
Copy link
Member

<svelte:head>
 <title>{$page.data.title}<title>
</svelte:head>

For that to work, every PageLoad function must return title.

But a layout (whether it's the default layout or a nested or reset one) could return it on behalf of its section of pages, right? It doesn't make sense for title in the example, but for some other shared property, it could (example: stylizing various sections of a website with a color, like I do for my school notes).

@aradalvand
Copy link
Contributor Author

aradalvand commented Aug 18, 2022

These problems are the reason why I've been trying to make the point that bottom-up data flow between pages and layouts is best handled by some new construct/feature, as opposed to page data, which is more suitable for top-down stuff, and becomes increasingly awkward when you try to use it for these types of scenarios.

This also still fails to take into account the problem of default values, there's no obvious way to set default values for these shared page data properties.

@Rich-Harris
Copy link
Member

But a layout (whether it's the default layout or a nested or reset one) could return it on behalf of its section of pages, right? It doesn't make sense for title in the example, but for some other shared property, it could (example: stylizing various sections of a website with a color, like I do for my school notes).

Yep, that's what I meant by this:

A fancier version would allow a PageLoad function not to return title if a parent layout already did (I think that's possible), so that a group of pages could share a title.

best handled by some new construct/feature

The discussion won't get far without a concrete proposal. $page.data seems like a pretty decent solution, all things considered, especially if we add App.PageData (or whatever we call it).

Partial<{ foo: string }> disallows assigning anything but a string or undefined to foo. It says "if foo exists, it is definitely a string", which eliminates a whole class of runtime type checks and allows me to just say $page.data?.foo ?? 'fallback' and never think about it again

Making foo optional seems like a decent solution to this:

declare namespace App {
  interface PageData {
    foo?: string;
  }
}

We could make it so that layout load functions return Partial<App.PageData> & Record<string, any> but page load functions return App.PageData & Record<string, any> (but can also get away with omitting properties that are set by parent load functions) and I think it would solve all the discussed use cases.

@elliott-with-the-longest-name-on-github
Copy link
Contributor

We could make it so that layout load functions return Partial<App.PageData> & Record<string, any> but page load functions return App.PageData & Record<string, any> (but can also get away with omitting properties that are set by parent load functions) and I think it would solve all the discussed use cases.

That's an acceptable compromise for me. It covers the layout case and also the "required for all pages" case while still allowing explicitly optional "global" return properties for pages.

@madeleineostoja
Copy link

madeleineostoja commented Aug 19, 2022

Just trying to parse through this issue, unless there's a PR imminently in the works for the lack of typing on $page.data, should just that request be split into a new issue so it's easier to track? It seems this issue is covering a lot of ground.

FWIW love the proposal of adding a PageData interface to the App namespace, that's how I was previously typing stuff, which $page.data has taken the reigns for in my use case.

@ponderingexistence
Copy link

ponderingexistence commented Aug 19, 2022

It seems to me that what we're trying to achieve here, fundamentally, is "set layout props", in a sense.
If you somehow make it so that every layout can define regular props and every page can then set those props, this will solve this whole domain of problems beautifully. And it also makes it possible to define default values (because as others have mentioned with page data this will not be as straightforward):

+layout.svelte:

<script lang="ts">
    export let showFooter: boolean = true;
</script>

+page.ts:

export const load: PageLoad = ({ setLayoutProps }) => {
    setLayoutProps({
        showFooter: true,
    });
};

This is just a raw idea, so it could be refined.
This approach also provides better SoC (separation of concerns) in my opinion since this stuff wouldn't be part of the page data (and it shouldn't since the page itself doesn't care about them, they're just there for the layout to read), solving this particular point by the OP.
It also makes it easier to provide better typing, but correct me if I'm wrong.

@Rich-Harris Wouldn't something like this be feasible? Thank you.

@david-plugge
Copy link
Contributor

david-plugge commented Aug 19, 2022

I´m very unsure if this is even possible but i´l try to explain my idea:

The types pretty much travel down the tree including every layout until we reach the page we are currently working in.
If we could somehow manage to merge the LayoutData from all layouts before the current file into one type like this

// .svelte-kit/types/src/routes/$types.d.ts
export type LayoutData = { session: { user: { id: string } } }

// .svelte-kit/types/src/routes/profile/$types.d.ts
import type { LayoutData as ParentData } from '../$types';
export type LayoutData = { random: string } & ParentData;

// .svelte-kit/types/src/routes/profile/settings/$types.d.ts
import type { LayoutData as ParentData } from '../$types';
export type PageData = { value: number } & ParentData

the result should be a merged type that includes all parent data types. Should also be possible with named layouts.

The problem would about how to actually use this type. One way could be importing the page store from ./$stores instead of $app/stores where vite resolves to the current $app/stores but typescript uses the types within .svelte-kit/types

src/routes/profile/settings/+page.svelte

<script>
	import { page } from './$stores';

	// typed as string
	$page.session.user.id;
	// typed as string
	$page.random;
	// typed as number
	$page.number;
</script>

Edit:
Maybe ./$stores isnt a bad idea for param autocompletion within a route aswell?

@dummdidumm
Copy link
Member

The problem with ./$types (and likewise something like ./$stores) is that it comes from a type root, you can't put something that is present at runtime in there, only types.

@david-plugge
Copy link
Contributor

david-plugge commented Aug 19, 2022

I´ve updated my comment.
Not sure if its possible to tell vite how to resolve relative paths

The problem would about how to actually use this type. One way could be importing the page store from ./$stores instead of $app/stores where vite resolves to the current $app/stores but typescript uses the types within .svelte-kit/types

Just replacing ./$stores in an import statement with $app/stores should also work

Edit:
This works

// vite.config.js

/** @type {import('vite').UserConfig} */
const config = {
    plugins: [sveltekit()],
    resolve: {
        alias: {
            './$stores': '$app/stores',
        },
    },
};

@AaronFlynn1989
Copy link

Regarding the default value issue that a few people raised here, can't you do something like this?

+layout.svelte:

<script lang="ts">
  $: ({
    showFooter = true,
  } = $page.data.layout);
</script>

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

Successfully merging a pull request may close this issue.