diff --git a/.changeset/ten-onions-talk.md b/.changeset/ten-onions-talk.md new file mode 100644 index 000000000000..7351c4ac6086 --- /dev/null +++ b/.changeset/ten-onions-talk.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: provide `PageProps` and `LayoutProps` types diff --git a/documentation/docs/20-core-concepts/10-routing.md b/documentation/docs/20-core-concepts/10-routing.md index 836b93221aab..3ebe9c9035ec 100644 --- a/documentation/docs/20-core-concepts/10-routing.md +++ b/documentation/docs/20-core-concepts/10-routing.md @@ -42,7 +42,7 @@ Pages can receive data from `load` functions via the `data` prop. ```svelte @@ -51,7 +51,9 @@ Pages can receive data from `load` functions via the `data` prop. ``` > [!LEGACY] -> In Svelte 4, you'd use `export let data` instead +> `PageProps` was added in 2.16.0. In earlier versions, you had to type the `data` property manually with `PageData` instead, see [$types](#\$types). +> +> In Svelte 4, you'd use `export let data` instead. > [!NOTE] SvelteKit uses `` elements to navigate between routes, rather than a framework-specific `` component. @@ -212,7 +214,7 @@ We can create a layout that only applies to pages below `/settings` (while inher ```svelte @@ -227,6 +229,9 @@ We can create a layout that only applies to pages below `/settings` (while inher {@render children()} ``` +> [!LEGACY] +> `LayoutProps` was added in 2.16.0. In earlier versions, you had to [type the properties manually instead](#\$types). + You can see how `data` is populated by looking at the `+layout.js` example in the next section just below. By default, each layout inherits the layout above it. Sometimes that isn't what you want - in this case, [advanced layouts](advanced-routing#Advanced-layouts) can help you. @@ -255,7 +260,7 @@ Data returned from a layout's `load` function is also available to all its child ```svelte ``` +> [!NOTE] +> The `PageProps` and `LayoutProps` types, added in 2.16.0, are a shortcut for typing the `data` prop as `PageData` or `LayoutData`, as well as other props, such as `form` for pages, or `children` for layouts. In earlier versions, you had to type these properties manually. For example, for a page: +> +> ```js +> /// file: +page.svelte +> /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */ +> let { data, form } = $props(); +> ``` +> +> Or, for a layout: +> +> ```js +> /// file: +layout.svelte +> /** @type {{ data: import('./$types').LayoutData, children: Snippet }} */ +> let { data, children } = $props(); +> ``` + In turn, annotating the `load` function with `PageLoad`, `PageServerLoad`, `LayoutLoad` or `LayoutServerLoad` (for `+page.js`, `+page.server.js`, `+layout.js` and `+layout.server.js` respectively) ensures that `params` and the return value are correctly typed. If you're using VS Code or any IDE that supports the language server protocol and TypeScript plugins then you can omit these types _entirely_! Svelte's IDE tooling will insert the correct types for you, so you'll get type checking without writing them yourself. It also works with our command line tool `svelte-check`. diff --git a/documentation/docs/20-core-concepts/20-load.md b/documentation/docs/20-core-concepts/20-load.md index 269d9078dd14..933d38563875 100644 --- a/documentation/docs/20-core-concepts/20-load.md +++ b/documentation/docs/20-core-concepts/20-load.md @@ -24,7 +24,7 @@ export function load({ params }) { ```svelte @@ -33,7 +33,14 @@ export function load({ params }) { ``` > [!LEGACY] -> In Svelte 4, you'd use `export let data` instead +> Before version 2.16.0, the props of a page and layout had to be typed individually: +> ```js +> /// file: +page.svelte +> /** @type {{ data: import('./$types').PageData }} */ +> let { data } = $props(); +> ``` +> +> In Svelte 4, you'd use `export let data` instead. Thanks to the generated `$types` module, we get full type safety. @@ -88,7 +95,7 @@ export async function load() { ```svelte @@ -111,6 +118,14 @@ export async function load() { ``` +> [!LEGACY] +> `LayoutProps` was added in 2.16.0. In earlier versions, properties had to be typed individually: +> ```js +> /// file: +layout.svelte +> /** @type {{ data: import('./$types').LayoutData, children: Snippet }} */ +> let { data, children } = $props(); +> ``` + Data returned from layout `load` functions is available to child `+layout.svelte` components and the `+page.svelte` component as well as the layout that it 'belongs' to. ```svelte @@ -118,7 +133,7 @@ Data returned from layout `load` functions is available to child `+layout.svelte @@ -511,7 +526,7 @@ This is useful for creating skeleton loading states, for example: ```svelte @@ -652,7 +667,7 @@ export async function load({ fetch, depends }) { @@ -152,7 +152,14 @@ export const actions = { ``` > [!LEGACY] -> In Svelte 4, you'd use `export let data` and `export let form` instead to declare properties +> `PageProps` was added in 2.16.0. In earlier versions, you had to type the `data` and `form` properties individually: +> ```js +> /// file: +page.svelte +> /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */ +> let { data, form } = $props(); +> ``` +> +> In Svelte 4, you'd use `export let data` and `export let form` instead to declare properties. ### Validation errors @@ -339,7 +346,7 @@ The easiest way to progressively enhance a form is to add the `use:enhance` acti @@ -390,7 +397,7 @@ If you return a callback, you may need to reproduce part of the default `use:enh @@ -427,7 +434,7 @@ We can also implement progressive enhancement ourselves, without `use:enhance`, import { invalidateAll, goto } from '$app/navigation'; import { applyAction, deserialize } from '$app/forms'; - /** @type {{ form: import('./$types').ActionData }} */ + /** @type {import('./$types').PageProps} */ let { form } = $props(); /** @param {SubmitEvent & { currentTarget: EventTarget & HTMLFormElement}} event */ diff --git a/documentation/docs/20-core-concepts/50-state-management.md b/documentation/docs/20-core-concepts/50-state-management.md index 9c0ab1b1764a..0d56c8cc66e1 100644 --- a/documentation/docs/20-core-concepts/50-state-management.md +++ b/documentation/docs/20-core-concepts/50-state-management.md @@ -89,7 +89,7 @@ You might wonder how we're able to use `page.data` and other [app state]($app-st diff --git a/documentation/docs/98-reference/54-types.md b/documentation/docs/98-reference/54-types.md index 2b24ed69034c..eb76c2fa7ea3 100644 --- a/documentation/docs/98-reference/54-types.md +++ b/documentation/docs/98-reference/54-types.md @@ -84,6 +84,37 @@ export async function load({ params, fetch }) { } ``` +The return types of the load functions are then available through the `$types` module as `PageData` and `LayoutData` respectively, while the union of the return values of all `Actions` is available as `ActionData`. Starting with version 2.16.0, two additional helper types are provided. `PageProps` defines `data: PageData`, as well as `form: ActionData`, when there are actions defined. `LayoutProps` defines `data: LayoutData`, as well as `children: Snippet`: + +```svelte + + +``` + +> [!LEGACY] +> Before 2.16.0: +> ```svelte +> +> +> ``` +> +> Using Svelte 4: +> ```svelte +> +> +> ``` + > [!NOTE] For this to work, your own `tsconfig.json` or `jsconfig.json` should extend from the generated `.svelte-kit/tsconfig.json` (where `.svelte-kit` is your [`outDir`](configuration#outDir)): > > `{ "extends": "./.svelte-kit/tsconfig.json" }` diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index e57b00183e6f..308d566606f8 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -270,6 +270,12 @@ function update_types(config, routes, route, to_delete = new Set()) { 'export type Actions | void = Record | void> = Kit.Actions' ); } + + if (route.leaf.server) { + exports.push('export type PageProps = { data: PageData; form: ActionData }'); + } else { + exports.push('export type PageProps = { data: PageData }'); + } } if (route.layout) { @@ -333,6 +339,10 @@ function update_types(config, routes, route, to_delete = new Set()) { if (proxies.server?.modified) to_delete.delete(proxies.server.file_name); if (proxies.universal?.modified) to_delete.delete(proxies.universal.file_name); + + exports.push( + 'export type LayoutProps = { data: LayoutData; children: import("svelte").Snippet }' + ); } if (route.endpoint) {