Skip to content

Commit b60707c

Browse files
rChaozeltigerchino
andauthored
feat: provide PageProps, LayoutProps types (#13308)
Closes #12726 --------- Co-authored-by: Tee Ming <[email protected]>
1 parent fb04de2 commit b60707c

File tree

8 files changed

+112
-22
lines changed

8 files changed

+112
-22
lines changed

.changeset/ten-onions-talk.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': minor
3+
---
4+
5+
feat: provide `PageProps` and `LayoutProps` types

documentation/docs/20-core-concepts/10-routing.md

+28-6
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Pages can receive data from `load` functions via the `data` prop.
4242
```svelte
4343
<!--- file: src/routes/blog/[slug]/+page.svelte --->
4444
<script>
45-
/** @type {{ data: import('./$types').PageData }} */
45+
/** @type {import('./$types').PageProps} */
4646
let { data } = $props();
4747
</script>
4848
@@ -51,7 +51,9 @@ Pages can receive data from `load` functions via the `data` prop.
5151
```
5252

5353
> [!LEGACY]
54-
> In Svelte 4, you'd use `export let data` instead
54+
> `PageProps` was added in 2.16.0. In earlier versions, you had to type the `data` property manually with `PageData` instead, see [$types](#\$types).
55+
>
56+
> In Svelte 4, you'd use `export let data` instead.
5557
5658
> [!NOTE] SvelteKit uses `<a>` elements to navigate between routes, rather than a framework-specific `<Link>` component.
5759
@@ -212,7 +214,7 @@ We can create a layout that only applies to pages below `/settings` (while inher
212214
```svelte
213215
<!--- file: src/routes/settings/+layout.svelte --->
214216
<script>
215-
/** @type {{ data: import('./$types').LayoutData, children: import('svelte').Snippet }} */
217+
/** @type {import('./$types').LayoutProps} */
216218
let { data, children } = $props();
217219
</script>
218220
@@ -227,6 +229,9 @@ We can create a layout that only applies to pages below `/settings` (while inher
227229
{@render children()}
228230
```
229231

232+
> [!LEGACY]
233+
> `LayoutProps` was added in 2.16.0. In earlier versions, you had to [type the properties manually instead](#\$types).
234+
230235
You can see how `data` is populated by looking at the `+layout.js` example in the next section just below.
231236

232237
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
255260
```svelte
256261
<!--- file: src/routes/settings/profile/+page.svelte --->
257262
<script>
258-
/** @type {{ data: import('./$types').PageData }} */
263+
/** @type {import('./$types').PageProps} */
259264
let { data } = $props();
260265
261266
console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]
@@ -388,16 +393,33 @@ export async function fallback({ request }) {
388393
389394
Throughout the examples above, we've been importing types from a `$types.d.ts` file. This is a file SvelteKit creates for you in a hidden directory if you're using TypeScript (or JavaScript with JSDoc type annotations) to give you type safety when working with your root files.
390395
391-
For example, annotating `let { data } = $props()` with `PageData` (or `LayoutData`, for a `+layout.svelte` file) tells TypeScript that the type of `data` is whatever was returned from `load`:
396+
For example, annotating `let { data } = $props()` with `PageProps` (or `LayoutProps`, for a `+layout.svelte` file) tells TypeScript that the type of `data` is whatever was returned from `load`:
392397
393398
```svelte
394399
<!--- file: src/routes/blog/[slug]/+page.svelte --->
395400
<script>
396-
/** @type {{ data: import('./$types').PageData }} */
401+
/** @type {import('./$types').PageProps} */
397402
let { data } = $props();
398403
</script>
399404
```
400405
406+
> [!NOTE]
407+
> 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:
408+
>
409+
> ```js
410+
> /// file: +page.svelte
411+
> /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */
412+
> let { data, form } = $props();
413+
> ```
414+
>
415+
> Or, for a layout:
416+
>
417+
> ```js
418+
> /// file: +layout.svelte
419+
> /** @type {{ data: import('./$types').LayoutData, children: Snippet }} */
420+
> let { data, children } = $props();
421+
> ```
422+
401423
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.
402424
403425
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`.

documentation/docs/20-core-concepts/20-load.md

+22-7
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function load({ params }) {
2424
```svelte
2525
<!--- file: src/routes/blog/[slug]/+page.svelte --->
2626
<script>
27-
/** @type {{ data: import('./$types').PageData }} */
27+
/** @type {import('./$types').PageProps} */
2828
let { data } = $props();
2929
</script>
3030
@@ -33,7 +33,14 @@ export function load({ params }) {
3333
```
3434

3535
> [!LEGACY]
36-
> In Svelte 4, you'd use `export let data` instead
36+
> Before version 2.16.0, the props of a page and layout had to be typed individually:
37+
> ```js
38+
> /// file: +page.svelte
39+
> /** @type {{ data: import('./$types').PageData }} */
40+
> let { data } = $props();
41+
> ```
42+
>
43+
> In Svelte 4, you'd use `export let data` instead.
3744
3845
Thanks to the generated `$types` module, we get full type safety.
3946
@@ -88,7 +95,7 @@ export async function load() {
8895
```svelte
8996
<!--- file: src/routes/blog/[slug]/+layout.svelte --->
9097
<script>
91-
/** @type {{ data: import('./$types').LayoutData, children: Snippet }} */
98+
/** @type {import('./$types').LayoutProps} */
9299
let { data, children } = $props();
93100
</script>
94101
@@ -111,14 +118,22 @@ export async function load() {
111118
</aside>
112119
```
113120

121+
> [!LEGACY]
122+
> `LayoutProps` was added in 2.16.0. In earlier versions, properties had to be typed individually:
123+
> ```js
124+
> /// file: +layout.svelte
125+
> /** @type {{ data: import('./$types').LayoutData, children: Snippet }} */
126+
> let { data, children } = $props();
127+
> ```
128+
114129
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.
115130
116131
```svelte
117132
/// file: src/routes/blog/[slug]/+page.svelte
118133
<script>
119134
+++import { page } from '$app/state';+++
120135
121-
/** @type {{ data: import('./$types').PageData }} */
136+
/** @type {import('./$types').PageProps} */
122137
let { data } = $props();
123138
124139
+++ // we can access `data.posts` because it's returned from
@@ -372,7 +387,7 @@ export async function load({ parent }) {
372387
```svelte
373388
<!--- file: src/routes/abc/+page.svelte --->
374389
<script>
375-
/** @type {{ data: import('./$types').PageData }} */
390+
/** @type {import('./$types').PageProps} */
376391
let { data } = $props();
377392
</script>
378393
@@ -511,7 +526,7 @@ This is useful for creating skeleton loading states, for example:
511526
```svelte
512527
<!--- file: src/routes/blog/[slug]/+page.svelte --->
513528
<script>
514-
/** @type {{ data: import('./$types').PageData }} */
529+
/** @type {import('./$types').PageProps} */
515530
let { data } = $props();
516531
</script>
517532
@@ -652,7 +667,7 @@ export async function load({ fetch, depends }) {
652667
<script>
653668
import { invalidate, invalidateAll } from '$app/navigation';
654669
655-
/** @type {{ data: import('./$types').PageData }} */
670+
/** @type {import('./$types').PageProps} */
656671
let { data } = $props();
657672
658673
function rerunLoadFunction() {

documentation/docs/20-core-concepts/30-form-actions.md

+12-5
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export const actions = {
140140
```svelte
141141
<!--- file: src/routes/login/+page.svelte --->
142142
<script>
143-
/** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */
143+
/** @type {import('./$types').PageProps} */
144144
let { data, form } = $props();
145145
</script>
146146

@@ -152,7 +152,14 @@ export const actions = {
152152
```
153153
154154
> [!LEGACY]
155-
> In Svelte 4, you'd use `export let data` and `export let form` instead to declare properties
155+
> `PageProps` was added in 2.16.0. In earlier versions, you had to type the `data` and `form` properties individually:
156+
> ```js
157+
> /// file: +page.svelte
158+
> /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */
159+
> let { data, form } = $props();
160+
> ```
161+
>
162+
> In Svelte 4, you'd use `export let data` and `export let form` instead to declare properties.
156163
157164
### Validation errors
158165
@@ -339,7 +346,7 @@ The easiest way to progressively enhance a form is to add the `use:enhance` acti
339346
<script>
340347
+++import { enhance } from '$app/forms';+++
341348

342-
/** @type {{ form: import('./$types').ActionData }} */
349+
/** @type {import('./$types').PageProps} */
343350
let { form } = $props();
344351
</script>
345352

@@ -390,7 +397,7 @@ If you return a callback, you may need to reproduce part of the default `use:enh
390397
<script>
391398
import { enhance, +++applyAction+++ } from '$app/forms';
392399

393-
/** @type {{ form: import('./$types').ActionData }} */
400+
/** @type {import('./$types').PageProps} */
394401
let { form } = $props();
395402
</script>
396403

@@ -427,7 +434,7 @@ We can also implement progressive enhancement ourselves, without `use:enhance`,
427434
import { invalidateAll, goto } from '$app/navigation';
428435
import { applyAction, deserialize } from '$app/forms';
429436

430-
/** @type {{ form: import('./$types').ActionData }} */
437+
/** @type {import('./$types').PageProps} */
431438
let { form } = $props();
432439

433440
/** @param {SubmitEvent & { currentTarget: EventTarget & HTMLFormElement}} event */

documentation/docs/20-core-concepts/50-state-management.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ You might wonder how we're able to use `page.data` and other [app state]($app-st
8989
<script>
9090
import { setContext } from 'svelte';
9191
92-
/** @type {{ data: import('./$types').LayoutData }} */
92+
/** @type {import('./$types').LayoutProps} */
9393
let { data } = $props();
9494
9595
// Pass a function referencing our state
@@ -126,7 +126,7 @@ When you navigate around your application, SvelteKit reuses existing layout and
126126
```svelte
127127
<!--- file: src/routes/blog/[slug]/+page.svelte --->
128128
<script>
129-
/** @type {{ data: import('./$types').PageData }} */
129+
/** @type {import('./$types').PageProps} */
130130
let { data } = $props();
131131
132132
// THIS CODE IS BUGGY!
@@ -149,7 +149,7 @@ Instead, we need to make the value [_reactive_](/tutorial/svelte/state):
149149
```svelte
150150
/// file: src/routes/blog/[slug]/+page.svelte
151151
<script>
152-
/** @type {{ data: import('./$types').PageData }} */
152+
/** @type {import('./$types').PageProps} */
153153
let { data } = $props();
154154
155155
+++ let wordCount = $derived(data.content.split(' ').length);

documentation/docs/25-build-and-deploy/90-adapter-vercel.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ export function load() {
162162
```svelte
163163
<!--- file: +layout.svelte --->
164164
<script>
165-
/** @type {{ data: import('./$types').LayoutServerData }} */
165+
/** @type {import('./$types').LayoutProps} */
166166
let { data } = $props();
167167
</script>
168168

documentation/docs/98-reference/54-types.md

+31
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,37 @@ export async function load({ params, fetch }) {
8484
}
8585
```
8686

87+
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`:
88+
89+
```svelte
90+
<!--- file: src/routes/+page.svelte --->
91+
<script>
92+
/** @type {import('./$types').PageProps} */
93+
let { data, form } = $props();
94+
</script>
95+
```
96+
97+
> [!LEGACY]
98+
> Before 2.16.0:
99+
> ```svelte
100+
> <!--- file: src/routes/+page.svelte --->
101+
> <script>
102+
> /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */
103+
> let { data, form } = $props();
104+
> </script>
105+
> ```
106+
>
107+
> Using Svelte 4:
108+
> ```svelte
109+
> <!--- file: src/routes/+page.svelte --->
110+
> <script>
111+
> /** @type {import('./$types').PageData} */
112+
> export let data;
113+
> /** @type {import('./$types').ActionData} */
114+
> export let form;
115+
> </script>
116+
> ```
117+
87118
> [!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)):
88119
>
89120
> `{ "extends": "./.svelte-kit/tsconfig.json" }`

packages/kit/src/core/sync/write_types/index.js

+10
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,12 @@ function update_types(config, routes, route, to_delete = new Set()) {
270270
'export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>'
271271
);
272272
}
273+
274+
if (route.leaf.server) {
275+
exports.push('export type PageProps = { data: PageData; form: ActionData }');
276+
} else {
277+
exports.push('export type PageProps = { data: PageData }');
278+
}
273279
}
274280

275281
if (route.layout) {
@@ -333,6 +339,10 @@ function update_types(config, routes, route, to_delete = new Set()) {
333339

334340
if (proxies.server?.modified) to_delete.delete(proxies.server.file_name);
335341
if (proxies.universal?.modified) to_delete.delete(proxies.universal.file_name);
342+
343+
exports.push(
344+
'export type LayoutProps = { data: LayoutData; children: import("svelte").Snippet }'
345+
);
336346
}
337347

338348
if (route.endpoint) {

0 commit comments

Comments
 (0)