Improve the typing of `$page`
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 20.8k
- Forks
- 2.3k
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 156
Description
Describe the problem
I found it surprising that the default typing for $page is App.PageData & Record<string, any> and that refining it is so out of the way. I only knew how to refine the type of App.PageData because of a lot of experience already with declaration merging etc. However I was disappointed to see that Record<string, any> can't be disabled or refined. At the least I would want to be able to do App.PageData & Record<string, unknown> or even just App.PageData to be more type safe.
Furthermore I was surprised that App.PageData wasn't deduced from the return type of my +layout.ts. svelte-kit sync already creates a .svelte-kit/types/src/routes/$types.d.ts file that has a perfectly good PageData type that seems to accurately describe the resolved type of the root load function. However it doesn't use declaration merging to make sure that App.PageData and by extension $page uses it.
I understand children load functions can essentially merge into the page data so $page can be assumed to have extra properties but I don't want simple typos messing me up and Record<string, unknown> makes it easier to avoid it but App.PageData is even safer. For an example look at this:
// This is a typo of `realProperty` which in this example would be part of the `load` function.
// With `$page` typed as `App.PageData` this would be an error immediately saying that that property doesn't exist. I like this and you can always refine the type of `$page` through TypeScript's flow control analysis.
const someData = $page.data.realPorperty;
// With `$page` typed as `App.PageData & Record<string, unknown>` this is an error because `someData` is of type `unknown` but it expects `string.
// Currently this won't error at all though because the type of `$page` is currently `App.PageData & Record<string, any>` and so `someData`'s type is `any`.
processData(someData);
function processData(data: string) { ... }
I understand that export let data is an option for +page.svelte files but plenty of my components use import { page } from '$app/stores'; and I had thought that was idiomatic.
Describe the proposed solution
I pretty much am writing the code that would need to be added already. I'd be willing to PR this but wanted to make an issue first.
I think .svelte-kit/types/src/routes/$types.d.ts should add this to the end:
// Alias to make the declaration merge work.
// `PageData` is already defined earlier in this file.
type _PageData = PageData;
declare module "@sveltejs/kit" {
namespace App {
export interface PageData extends _PageData {}
}
}
This is the top level route file and so it'll only augment for the top level load.
I almost feel like I'm missing something about why this wouldn't work so please let me know if this would be a problem. Maybe there's some way to turn off parent loads so this isn't always sound? However I've already added this to my code and had no issues so far.
Since it wasn't always like this, the only concern I can see is that isn't 100% backwards compatible. Say your load function returns { realProperty: { realData: number } }, if you have faulty code today like $page.realProperty.nonexistantProperty it'll work today and be typed as any. If the type is improved, this would be come an error. However this should only ever cause an issue with currently bugged code. If there's a deep merge situation going on with load functions or something the type could be improved in that case too. Ideally could be tested on the ecosystem to see how much of problem this is. Or it could be introduced only as part of a major version.
Then for my second feature proposal I'd suggest this change:
In @sveltekit/kit/src.types/ambient.d.ts
declare namespace App {
[...]
// This type describes the options the configure the `PageData` type.
export type PageDataOptions = {
// Whether the page data should be considered exhaustive or not.
// By default this is `false` and extra properties are allowed on the page.
exhaustive: boolean;
// Whether extra properties should be `unknown`.
// By default this is `false` and extra properties are typed as `any`.
useUnknown: boolean;
}
export interface PageDataConfiguration extends PageDataOptions {}
}
Then in the core @sveltekit/kit/src/exports/public.d.ts file, I would make this tweak:
export interface Page<...> {
[...]
data: App.PageData & (
// The condition is written this way because we should never distribute and it has to be exactly `true`. The type `boolean` shouldn't count as exhaustive.
true extends App.PageDataConfiguration['exhaustive'] ? {} : (
true extends App.PageDataConfiguration['useUnknown'] ? Record<string, unknown> : Record<string, any>
)
);
}
Users would configure like so:
declare module "@sveltejs/kit" {
namespace App {
export interface PageDataConfiguration extends App.PageDataOptions {
exhaustive: true;
// This is redundant with exhaustive but it's probably not worth an error or anything.
useUnknown: true;
}
}
}
This portion should be 100% backwards compatible because when unset the type is still App.PageData & Record<string, any>.
Alternatives considered
No response
Importance
nice to have
Additional Information
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the generated .svelte-kit/types/src/routes/$types.d.ts and the declarations in @sveltekit/kit/src.types/ambient.d.ts and src/exports/public.d.ts. Trace how PageData and $page.data are currently typed, then verify the proposed configuration and declaration-merging behavior with the relevant type-checking coverage. Done means the intended $page typing works without breaking the default behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- developer-experience, frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100