From 74c0fd7fd58740f27fb89b8993773e2dd73f0fb3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 12:53:32 -0400 Subject: [PATCH 01/31] add `RouteId` to `$app/types` --- packages/kit/src/core/sync/write_tsconfig.js | 5 ++++- .../kit/src/core/sync/write_types/index.js | 20 +++++++++++++++++++ packages/kit/src/runtime/app/paths/types.d.ts | 7 ++++++- packages/kit/types/index.d.ts | 6 +++++- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js index e9df340902e1..6bca8214bdb1 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig.js @@ -98,7 +98,10 @@ export function get_tsconfig(kit) { const config = { compilerOptions: { // generated options - paths: get_tsconfig_paths(kit), + paths: { + ...get_tsconfig_paths(kit), + '$app/types': ['./types/index.d.ts'] + }, rootDirs: [config_relative('.'), './types'], // essential options diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index 308d566606f8..0b4b9021b890 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -5,6 +5,7 @@ import MagicString from 'magic-string'; import { posixify, rimraf, walk } from '../../../utils/filesystem.js'; import { compact } from '../../../utils/array.js'; import { ts } from '../ts.js'; +import { s } from '../../../utils/misc.js'; /** * @typedef {{ @@ -49,6 +50,25 @@ export function write_all_types(config, manifest_data) { } } + /** @type {string[]} */ + const routes = []; + + for (const route of manifest_data.routes) { + const params = route.params.map((p) => `${p.name}${p.optional ? '?:' : ':'} string`).join('; '); + const type = `${s(route.id)}: ${params.length > 0 ? `{ ${params} }` : 'undefined'}`; + + routes.push(type); + } + + fs.writeFileSync( + `${types_dir}/index.d.ts`, + [ + `type Routes = {\n\t${routes.join(';\n\t')}\n};`, + `export type RouteId = ${manifest_data.routes.map((r) => s(r.id)).join(' | ')};`, + 'export type RouteParams = Routes[T];' + ].join('\n\n') + ); + // Read/write meta data on each invocation, not once per node process, // it could be invoked by another process in the meantime. const meta_data_file = `${types_dir}/route_meta_data.json`; diff --git a/packages/kit/src/runtime/app/paths/types.d.ts b/packages/kit/src/runtime/app/paths/types.d.ts index d17c45dd4efd..a8967caf2aa7 100644 --- a/packages/kit/src/runtime/app/paths/types.d.ts +++ b/packages/kit/src/runtime/app/paths/types.d.ts @@ -1,3 +1,5 @@ +import { RouteId, RouteParams } from '$app/types'; + /** * A string that matches [`config.kit.paths.base`](https://svelte.dev/docs/kit/configuration#paths). * @@ -12,6 +14,9 @@ export let base: '' | `/${string}`; */ export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets'; +type ResolveRouteArgs = + RouteParams extends undefined ? [route: T] : [route: T, params: RouteParams]; + /** * Populate a route ID with params to resolve a pathname. * @example @@ -27,4 +32,4 @@ export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit * ); // `/blog/hello-world/something/else` * ``` */ -export function resolveRoute(id: string, params: Record): string; +export function resolveRoute(...args: ResolveRouteArgs): string; diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 74d438a6f5cc..a884be226edf 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -2367,6 +2367,7 @@ declare module '$app/navigation' { } declare module '$app/paths' { + import type { RouteId, RouteParams } from '$app/types'; /** * A string that matches [`config.kit.paths.base`](https://svelte.dev/docs/kit/configuration#paths). * @@ -2381,6 +2382,9 @@ declare module '$app/paths' { */ export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets'; + type ResolveRouteArgs = + RouteParams extends undefined ? [route: T] : [route: T, params: RouteParams]; + /** * Populate a route ID with params to resolve a pathname. * @example @@ -2396,7 +2400,7 @@ declare module '$app/paths' { * ); // `/blog/hello-world/something/else` * ``` */ - export function resolveRoute(id: string, params: Record): string; + export function resolveRoute(...args: ResolveRouteArgs): string; export {}; } From c95a69f733cf0abdf37b2382195bc27765dd6987 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 13:14:15 -0400 Subject: [PATCH 02/31] more --- .../kit/src/core/sync/write_types/index.js | 2 +- packages/kit/src/exports/public.d.ts | 41 +++++++++--------- packages/kit/types/index.d.ts | 43 ++++++++++--------- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index 0b4b9021b890..29c8c2bb01ee 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -65,7 +65,7 @@ export function write_all_types(config, manifest_data) { [ `type Routes = {\n\t${routes.join(';\n\t')}\n};`, `export type RouteId = ${manifest_data.routes.map((r) => s(r.id)).join(' | ')};`, - 'export type RouteParams = Routes[T];' + 'export type RouteParams = Routes[T] | Record;' ].join('\n\n') ); diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index ec930b7d073a..cf3ac619c1d1 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -19,6 +19,7 @@ import { } from '../types/private.js'; import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types'; import type { PluginOptions } from '@sveltejs/vite-plugin-svelte'; +import { RouteId as AppRouteId, RouteParams as AppRouteParams } from '$app/types'; export { PrerenderOption } from '../types/private.js'; @@ -858,11 +859,11 @@ export interface Transporter< * rather than using `Load` directly. */ export type Load< - Params extends Partial> = Partial>, + Params extends AppRouteParams = AppRouteParams, InputData extends Record | null = Record | null, ParentData extends Record = Record, OutputData extends Record | void = Record | void, - RouteId extends string | null = string | null + RouteId extends AppRouteId | null = AppRouteId | null > = (event: LoadEvent) => MaybePromise; /** @@ -870,10 +871,10 @@ export type Load< * rather than using `LoadEvent` directly. */ export interface LoadEvent< - Params extends Partial> = Partial>, + Params extends AppRouteParams = AppRouteParams, Data extends Record | null = Record | null, ParentData extends Record = Record, - RouteId extends string | null = string | null + RouteId extends AppRouteId | null = AppRouteId | null > extends NavigationEvent { /** * `fetch` is equivalent to the [native `fetch` web API](https://developer.mozilla.org/en-US/docs/Web/API/fetch), with a few additional features: @@ -978,8 +979,8 @@ export interface LoadEvent< } export interface NavigationEvent< - Params extends Partial> = Partial>, - RouteId extends string | null = string | null + Params extends AppRouteParams = AppRouteParams, + RouteId extends AppRouteId | null = AppRouteId | null > { /** * The parameters of the current page - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object @@ -1118,8 +1119,8 @@ export interface AfterNavigate extends Omit { * The shape of the [`page`](https://svelte.dev/docs/kit/$app-state#page) reactive object and the [`$page`](https://svelte.dev/docs/kit/$app-stores) store. */ export interface Page< - Params extends Record = Record, - RouteId extends string | null = string | null + Params extends AppRouteParams = AppRouteParams, + RouteId extends AppRouteId | null = AppRouteId | null > { /** * The URL of the current page. @@ -1166,8 +1167,8 @@ export interface Page< export type ParamMatcher = (param: string) => boolean; export interface RequestEvent< - Params extends Partial> = Partial>, - RouteId extends string | null = string | null + Params extends AppRouteParams = AppRouteParams, + RouteId extends AppRouteId | null = AppRouteId | null > { /** * Get or set cookies related to the current request @@ -1258,8 +1259,8 @@ export interface RequestEvent< * It receives `Params` as the first generic argument, which you can skip by using [generated types](https://svelte.dev/docs/kit/types#Generated-types) instead. */ export type RequestHandler< - Params extends Partial> = Partial>, - RouteId extends string | null = string | null + Params extends AppRouteParams = AppRouteParams, + RouteId extends AppRouteId | null = AppRouteId | null > = (event: RequestEvent) => MaybePromise; export interface ResolveOptions { @@ -1337,16 +1338,16 @@ export interface SSRManifest { * rather than using `ServerLoad` directly. */ export type ServerLoad< - Params extends Partial> = Partial>, + Params extends AppRouteParams = AppRouteParams, ParentData extends Record = Record, OutputData extends Record | void = Record | void, - RouteId extends string | null = string | null + RouteId extends AppRouteId | null = AppRouteId | null > = (event: ServerLoadEvent) => MaybePromise; export interface ServerLoadEvent< - Params extends Partial> = Partial>, + Params extends AppRouteParams = AppRouteParams, ParentData extends Record = Record, - RouteId extends string | null = string | null + RouteId extends AppRouteId | null = AppRouteId | null > extends RequestEvent { /** * `await parent()` returns data from parent `+layout.server.js` `load` functions. @@ -1413,9 +1414,9 @@ export interface ServerLoadEvent< * See [form actions](https://svelte.dev/docs/kit/form-actions) for more information. */ export type Action< - Params extends Partial> = Partial>, + Params extends AppRouteParams = AppRouteParams, OutputData extends Record | void = Record | void, - RouteId extends string | null = string | null + RouteId extends AppRouteId | null = AppRouteId | null > = (event: RequestEvent) => MaybePromise; /** @@ -1423,9 +1424,9 @@ export type Action< * See [form actions](https://svelte.dev/docs/kit/form-actions) for more information. */ export type Actions< - Params extends Partial> = Partial>, + Params extends AppRouteParams = AppRouteParams, OutputData extends Record | void = Record | void, - RouteId extends string | null = string | null + RouteId extends AppRouteId | null = AppRouteId | null > = Record>; /** diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index a884be226edf..305ee1368477 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -4,6 +4,7 @@ declare module '@sveltejs/kit' { import type { CompileOptions } from 'svelte/compiler'; import type { PluginOptions } from '@sveltejs/vite-plugin-svelte'; + import type { RouteId as AppRouteId, RouteParams as AppRouteParams } from '$app/types'; /** * [Adapters](https://svelte.dev/docs/kit/adapters) are responsible for taking the production build and turning it into something that can be deployed to a platform of your choosing. */ @@ -840,11 +841,11 @@ declare module '@sveltejs/kit' { * rather than using `Load` directly. */ export type Load< - Params extends Partial> = Partial>, + Params extends AppRouteParams = AppRouteParams, InputData extends Record | null = Record | null, ParentData extends Record = Record, OutputData extends Record | void = Record | void, - RouteId extends string | null = string | null + RouteId extends AppRouteId | null = AppRouteId | null > = (event: LoadEvent) => MaybePromise; /** @@ -852,10 +853,10 @@ declare module '@sveltejs/kit' { * rather than using `LoadEvent` directly. */ export interface LoadEvent< - Params extends Partial> = Partial>, + Params extends AppRouteParams = AppRouteParams, Data extends Record | null = Record | null, ParentData extends Record = Record, - RouteId extends string | null = string | null + RouteId extends AppRouteId | null = AppRouteId | null > extends NavigationEvent { /** * `fetch` is equivalent to the [native `fetch` web API](https://developer.mozilla.org/en-US/docs/Web/API/fetch), with a few additional features: @@ -960,8 +961,8 @@ declare module '@sveltejs/kit' { } export interface NavigationEvent< - Params extends Partial> = Partial>, - RouteId extends string | null = string | null + Params extends AppRouteParams = AppRouteParams, + RouteId extends AppRouteId | null = AppRouteId | null > { /** * The parameters of the current page - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object @@ -1100,8 +1101,8 @@ declare module '@sveltejs/kit' { * The shape of the [`page`](https://svelte.dev/docs/kit/$app-state#page) reactive object and the [`$page`](https://svelte.dev/docs/kit/$app-stores) store. */ export interface Page< - Params extends Record = Record, - RouteId extends string | null = string | null + Params extends AppRouteParams = AppRouteParams, + RouteId extends AppRouteId | null = AppRouteId | null > { /** * The URL of the current page. @@ -1148,8 +1149,8 @@ declare module '@sveltejs/kit' { export type ParamMatcher = (param: string) => boolean; export interface RequestEvent< - Params extends Partial> = Partial>, - RouteId extends string | null = string | null + Params extends AppRouteParams = AppRouteParams, + RouteId extends AppRouteId | null = AppRouteId | null > { /** * Get or set cookies related to the current request @@ -1240,8 +1241,8 @@ declare module '@sveltejs/kit' { * It receives `Params` as the first generic argument, which you can skip by using [generated types](https://svelte.dev/docs/kit/types#Generated-types) instead. */ export type RequestHandler< - Params extends Partial> = Partial>, - RouteId extends string | null = string | null + Params extends AppRouteParams = AppRouteParams, + RouteId extends AppRouteId | null = AppRouteId | null > = (event: RequestEvent) => MaybePromise; export interface ResolveOptions { @@ -1319,16 +1320,16 @@ declare module '@sveltejs/kit' { * rather than using `ServerLoad` directly. */ export type ServerLoad< - Params extends Partial> = Partial>, + Params extends AppRouteParams = AppRouteParams, ParentData extends Record = Record, OutputData extends Record | void = Record | void, - RouteId extends string | null = string | null + RouteId extends AppRouteId | null = AppRouteId | null > = (event: ServerLoadEvent) => MaybePromise; export interface ServerLoadEvent< - Params extends Partial> = Partial>, + Params extends AppRouteParams = AppRouteParams, ParentData extends Record = Record, - RouteId extends string | null = string | null + RouteId extends AppRouteId | null = AppRouteId | null > extends RequestEvent { /** * `await parent()` returns data from parent `+layout.server.js` `load` functions. @@ -1395,9 +1396,9 @@ declare module '@sveltejs/kit' { * See [form actions](https://svelte.dev/docs/kit/form-actions) for more information. */ export type Action< - Params extends Partial> = Partial>, + Params extends AppRouteParams = AppRouteParams, OutputData extends Record | void = Record | void, - RouteId extends string | null = string | null + RouteId extends AppRouteId | null = AppRouteId | null > = (event: RequestEvent) => MaybePromise; /** @@ -1405,9 +1406,9 @@ declare module '@sveltejs/kit' { * See [form actions](https://svelte.dev/docs/kit/form-actions) for more information. */ export type Actions< - Params extends Partial> = Partial>, + Params extends AppRouteParams = AppRouteParams, OutputData extends Record | void = Record | void, - RouteId extends string | null = string | null + RouteId extends AppRouteId | null = AppRouteId | null > = Record>; /** @@ -2426,7 +2427,7 @@ declare module '$app/server' { * In environments without [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage), this must be called synchronously (i.e. not after an `await`). * @since 2.20.0 */ - export function getRequestEvent(): RequestEvent>, string | null>; + export function getRequestEvent(): RequestEvent, any>; export {}; } From 4fe5f0b9396691706c189af88165c5bd8b886170 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 13:29:07 -0400 Subject: [PATCH 03/31] create directory --- packages/kit/src/core/sync/write_types/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index 29c8c2bb01ee..4530a42eb9d1 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -60,6 +60,10 @@ export function write_all_types(config, manifest_data) { routes.push(type); } + try { + fs.mkdirSync(types_dir, { recursive: true }); + } catch {} + fs.writeFileSync( `${types_dir}/index.d.ts`, [ From 5669c4288ad885ca858e8513606c4d9ce124d1cb Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 13:49:10 -0400 Subject: [PATCH 04/31] hmm --- .../kit/src/core/sync/write_tsconfig.spec.js | 1 + .../src/core/sync/write_types/index.spec.js | 40 +++++++++++++------ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/packages/kit/src/core/sync/write_tsconfig.spec.js b/packages/kit/src/core/sync/write_tsconfig.spec.js index 65a6d09f1499..fbd780d74bdd 100644 --- a/packages/kit/src/core/sync/write_tsconfig.spec.js +++ b/packages/kit/src/core/sync/write_tsconfig.spec.js @@ -20,6 +20,7 @@ test('Creates tsconfig path aliases from kit.alias', () => { // $lib isn't part of the outcome because there's a "path exists" // check in the implementation expect(compilerOptions.paths).toEqual({ + '$app/types': ['./types/index.d.ts'], simpleKey: ['../simple/value'], 'simpleKey/*': ['../simple/value/*'], key: ['../value'], diff --git a/packages/kit/src/core/sync/write_types/index.spec.js b/packages/kit/src/core/sync/write_types/index.spec.js index 210922514089..b42a687e031d 100644 --- a/packages/kit/src/core/sync/write_types/index.spec.js +++ b/packages/kit/src/core/sync/write_types/index.spec.js @@ -1,4 +1,5 @@ import { execSync } from 'node:child_process'; +import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { assert, expect, test } from 'vitest'; @@ -33,20 +34,33 @@ test('Creates correct $types', { timeout: 6000 }, () => { // To save us from creating a real SvelteKit project for each of the tests, // we first run the type generation directly for each test case, and then // call `tsc` to check that the generated types are valid. - run_test('actions'); - run_test('simple-page-shared-only'); - run_test('simple-page-server-only'); - run_test('simple-page-server-and-shared'); - run_test('layout'); - run_test('layout-advanced'); - run_test('slugs'); - run_test('slugs-layout-not-all-pages-have-load'); - run_test('param-type-inference'); + const directories = fs + .readdirSync(cwd) + .filter((dir) => fs.statSync(`${cwd}/${dir}`).isDirectory()); + + const tsconfig_file = `${cwd}/tsconfig.json`; + const tsconfig_json = fs.readFileSync(tsconfig_file, 'utf-8'); + const tsconfig = JSON.parse(tsconfig_json); + try { - execSync('pnpm testtypes', { cwd }); - } catch (e) { - console.error(/** @type {any} */ (e).stdout.toString()); - throw new Error('Type tests failed'); + for (const dir of directories) { + run_test(dir); + + try { + tsconfig.compilerOptions.paths = { + '$app/types': [`${cwd}/${dir}/.svelte-kit/types/index.d.ts`] + }; + + fs.writeFileSync(tsconfig_file, JSON.stringify(tsconfig)); + + execSync('pnpm testtypes', { cwd }); + } catch (e) { + console.error(/** @type {any} */ (e).stdout.toString()); + throw new Error('Type tests failed'); + } + } + } finally { + fs.writeFileSync(tsconfig_file, tsconfig_json); } }); From 3e6b274e99e1b6427d8959793859312d68dbb6bd Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 15:40:16 -0400 Subject: [PATCH 05/31] fix --- .../kit/src/core/sync/write_types/index.js | 34 ++++++++++++++++--- .../src/core/sync/write_types/index.spec.js | 10 +++--- packages/kit/src/exports/public.d.ts | 24 +++++++------ packages/kit/types/index.d.ts | 16 ++++----- 4 files changed, 58 insertions(+), 26 deletions(-) diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index 4530a42eb9d1..996a17fedffe 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -53,11 +53,34 @@ export function write_all_types(config, manifest_data) { /** @type {string[]} */ const routes = []; + /** @type {string[]} */ + const layouts = []; + for (const route of manifest_data.routes) { - const params = route.params.map((p) => `${p.name}${p.optional ? '?:' : ':'} string`).join('; '); - const type = `${s(route.id)}: ${params.length > 0 ? `{ ${params} }` : 'undefined'}`; + const route_params = route.params + .map((p) => `${p.name}${p.optional ? '?:' : ':'} string`) + .join('; '); + + const route_type = `${s(route.id)}: ${route_params.length > 0 ? `{ ${route_params} }` : 'undefined'}`; + routes.push(route_type); + + /** @type {Map} */ + const child_params = new Map(route.params.map((p) => [p.name, p.optional])); + + for (const child of manifest_data.routes.filter((r) => r.id.startsWith(route.id))) { + for (const p of child.params) { + if (!child_params.has(p.name)) { + child_params.set(p.name, true); // always optional + } + } + } + + const layout_params = Array.from(child_params) + .map(([name, optional]) => `${name}${optional ? '?:' : ':'} string`) + .join('; '); - routes.push(type); + const layout_type = `${s(route.id)}: ${layout_params.length > 0 ? `{ ${layout_params} }` : 'undefined'}`; + layouts.push(layout_type); } try { @@ -68,8 +91,11 @@ export function write_all_types(config, manifest_data) { `${types_dir}/index.d.ts`, [ `type Routes = {\n\t${routes.join(';\n\t')}\n};`, + `type Layouts = {\n\t${layouts.join(';\n\t')}\n};`, + // we enumerate these rather than doing `keyof Routes` so that the list is visible on hover `export type RouteId = ${manifest_data.routes.map((r) => s(r.id)).join(' | ')};`, - 'export type RouteParams = Routes[T] | Record;' + 'export type RouteParams = Routes[T] | Record;', + 'export type LayoutParams = Layouts[T] | Record;' ].join('\n\n') ); diff --git a/packages/kit/src/core/sync/write_types/index.spec.js b/packages/kit/src/core/sync/write_types/index.spec.js index b42a687e031d..0cb59ed5cc4e 100644 --- a/packages/kit/src/core/sync/write_types/index.spec.js +++ b/packages/kit/src/core/sync/write_types/index.spec.js @@ -30,7 +30,7 @@ function run_test(dir) { write_all_types(initial, manifest); } -test('Creates correct $types', { timeout: 6000 }, () => { +test('Creates correct $types', { timeout: 30000 }, () => { // To save us from creating a real SvelteKit project for each of the tests, // we first run the type generation directly for each test case, and then // call `tsc` to check that the generated types are valid. @@ -47,9 +47,11 @@ test('Creates correct $types', { timeout: 6000 }, () => { run_test(dir); try { - tsconfig.compilerOptions.paths = { - '$app/types': [`${cwd}/${dir}/.svelte-kit/types/index.d.ts`] - }; + tsconfig.compilerOptions.paths['$app/types'] = [ + `${cwd}/${dir}/.svelte-kit/types/index.d.ts` + ]; + + tsconfig.include = [`./${dir}/**/*.js`]; fs.writeFileSync(tsconfig_file, JSON.stringify(tsconfig)); diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index cf3ac619c1d1..cf07c4e8a54c 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -19,7 +19,11 @@ import { } from '../types/private.js'; import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types'; import type { PluginOptions } from '@sveltejs/vite-plugin-svelte'; -import { RouteId as AppRouteId, RouteParams as AppRouteParams } from '$app/types'; +import { + RouteId as AppRouteId, + RouteParams as AppRouteParams, + LayoutParams as AppLayoutParams +} from '$app/types'; export { PrerenderOption } from '../types/private.js'; @@ -859,7 +863,7 @@ export interface Transporter< * rather than using `Load` directly. */ export type Load< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, InputData extends Record | null = Record | null, ParentData extends Record = Record, OutputData extends Record | void = Record | void, @@ -871,7 +875,7 @@ export type Load< * rather than using `LoadEvent` directly. */ export interface LoadEvent< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, Data extends Record | null = Record | null, ParentData extends Record = Record, RouteId extends AppRouteId | null = AppRouteId | null @@ -979,7 +983,7 @@ export interface LoadEvent< } export interface NavigationEvent< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, RouteId extends AppRouteId | null = AppRouteId | null > { /** @@ -1167,7 +1171,7 @@ export interface Page< export type ParamMatcher = (param: string) => boolean; export interface RequestEvent< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, RouteId extends AppRouteId | null = AppRouteId | null > { /** @@ -1259,7 +1263,7 @@ export interface RequestEvent< * It receives `Params` as the first generic argument, which you can skip by using [generated types](https://svelte.dev/docs/kit/types#Generated-types) instead. */ export type RequestHandler< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, RouteId extends AppRouteId | null = AppRouteId | null > = (event: RequestEvent) => MaybePromise; @@ -1338,14 +1342,14 @@ export interface SSRManifest { * rather than using `ServerLoad` directly. */ export type ServerLoad< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, ParentData extends Record = Record, OutputData extends Record | void = Record | void, RouteId extends AppRouteId | null = AppRouteId | null > = (event: ServerLoadEvent) => MaybePromise; export interface ServerLoadEvent< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, ParentData extends Record = Record, RouteId extends AppRouteId | null = AppRouteId | null > extends RequestEvent { @@ -1414,7 +1418,7 @@ export interface ServerLoadEvent< * See [form actions](https://svelte.dev/docs/kit/form-actions) for more information. */ export type Action< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, OutputData extends Record | void = Record | void, RouteId extends AppRouteId | null = AppRouteId | null > = (event: RequestEvent) => MaybePromise; @@ -1424,7 +1428,7 @@ export type Action< * See [form actions](https://svelte.dev/docs/kit/form-actions) for more information. */ export type Actions< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, OutputData extends Record | void = Record | void, RouteId extends AppRouteId | null = AppRouteId | null > = Record>; diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 305ee1368477..f0deff9f35ab 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -4,7 +4,7 @@ declare module '@sveltejs/kit' { import type { CompileOptions } from 'svelte/compiler'; import type { PluginOptions } from '@sveltejs/vite-plugin-svelte'; - import type { RouteId as AppRouteId, RouteParams as AppRouteParams } from '$app/types'; + import type { RouteId as AppRouteId, RouteParams as AppRouteParams, LayoutParams as AppLayoutParams } from '$app/types'; /** * [Adapters](https://svelte.dev/docs/kit/adapters) are responsible for taking the production build and turning it into something that can be deployed to a platform of your choosing. */ @@ -841,7 +841,7 @@ declare module '@sveltejs/kit' { * rather than using `Load` directly. */ export type Load< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, InputData extends Record | null = Record | null, ParentData extends Record = Record, OutputData extends Record | void = Record | void, @@ -853,7 +853,7 @@ declare module '@sveltejs/kit' { * rather than using `LoadEvent` directly. */ export interface LoadEvent< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, Data extends Record | null = Record | null, ParentData extends Record = Record, RouteId extends AppRouteId | null = AppRouteId | null @@ -961,7 +961,7 @@ declare module '@sveltejs/kit' { } export interface NavigationEvent< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, RouteId extends AppRouteId | null = AppRouteId | null > { /** @@ -1149,7 +1149,7 @@ declare module '@sveltejs/kit' { export type ParamMatcher = (param: string) => boolean; export interface RequestEvent< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, RouteId extends AppRouteId | null = AppRouteId | null > { /** @@ -1320,14 +1320,14 @@ declare module '@sveltejs/kit' { * rather than using `ServerLoad` directly. */ export type ServerLoad< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, ParentData extends Record = Record, OutputData extends Record | void = Record | void, RouteId extends AppRouteId | null = AppRouteId | null > = (event: ServerLoadEvent) => MaybePromise; export interface ServerLoadEvent< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, ParentData extends Record = Record, RouteId extends AppRouteId | null = AppRouteId | null > extends RequestEvent { @@ -2427,7 +2427,7 @@ declare module '$app/server' { * In environments without [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage), this must be called synchronously (i.e. not after an `await`). * @since 2.20.0 */ - export function getRequestEvent(): RequestEvent, any>; + export function getRequestEvent(): RequestEvent, any>; export {}; } From df2293d674f20d2755aaad3243a12978e3934b15 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 15:41:16 -0400 Subject: [PATCH 06/31] changeset --- .changeset/wicked-bananas-yawn.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/wicked-bananas-yawn.md diff --git a/.changeset/wicked-bananas-yawn.md b/.changeset/wicked-bananas-yawn.md new file mode 100644 index 000000000000..35664ccf76f5 --- /dev/null +++ b/.changeset/wicked-bananas-yawn.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: `RouteId` type, and type safety for things like `resolveRoute`, `page.route.id` and `page.params` From 039fa0f5feb197c941044639132238ff8a539c71 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 16:03:34 -0400 Subject: [PATCH 07/31] regenerate --- packages/kit/types/index.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index f0deff9f35ab..6602463f5c5f 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -1241,7 +1241,7 @@ declare module '@sveltejs/kit' { * It receives `Params` as the first generic argument, which you can skip by using [generated types](https://svelte.dev/docs/kit/types#Generated-types) instead. */ export type RequestHandler< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, RouteId extends AppRouteId | null = AppRouteId | null > = (event: RequestEvent) => MaybePromise; @@ -1396,7 +1396,7 @@ declare module '@sveltejs/kit' { * See [form actions](https://svelte.dev/docs/kit/form-actions) for more information. */ export type Action< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, OutputData extends Record | void = Record | void, RouteId extends AppRouteId | null = AppRouteId | null > = (event: RequestEvent) => MaybePromise; @@ -1406,7 +1406,7 @@ declare module '@sveltejs/kit' { * See [form actions](https://svelte.dev/docs/kit/form-actions) for more information. */ export type Actions< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams = AppLayoutParams, OutputData extends Record | void = Record | void, RouteId extends AppRouteId | null = AppRouteId | null > = Record>; From e2afa93ada89b2dfbbbbfc303571a84cfdf348a2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 16:16:41 -0400 Subject: [PATCH 08/31] some basic docs --- .../docs/98-reference/20-$app-types.md | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 documentation/docs/98-reference/20-$app-types.md diff --git a/documentation/docs/98-reference/20-$app-types.md b/documentation/docs/98-reference/20-$app-types.md new file mode 100644 index 000000000000..3e81511d6792 --- /dev/null +++ b/documentation/docs/98-reference/20-$app-types.md @@ -0,0 +1,46 @@ +--- +title: $app/types +--- + +This module contains generated types for the routes in your app. + +```js +// @noErrors +import type { RouteId, RouteParams, LayoutParams } from '$app/types'; +``` + +## RouteId + +A union of all the route IDs in your app. + +
+ +```dts +type RouteId = '/' | '/my-route' | '/my-other-route/[param]'; +``` + +
+ +## RouteParams + +A utility for getting the parameters associated with a given route. + +
+ +```dts +type RouteParams = { /* generated */ } | Record; +``` + +
+ +## LayoutParams + +A utility for getting the parameters associated with a given layout, which is similar to `RouteParams` but also includes optional parameters for any child route. + +
+ +```dts +type RouteParams = { /* generated */ } | Record; +``` + +
From 42ea4f8bef66270d300ed5a398aff208627cd984 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 16:18:05 -0400 Subject: [PATCH 09/31] oh wow that seemed to work --- packages/kit/src/exports/public.d.ts | 1 + packages/kit/src/runtime/app/paths/types.d.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index cf07c4e8a54c..74704d7a6345 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -23,6 +23,7 @@ import { RouteId as AppRouteId, RouteParams as AppRouteParams, LayoutParams as AppLayoutParams + // @ts-ignore } from '$app/types'; export { PrerenderOption } from '../types/private.js'; diff --git a/packages/kit/src/runtime/app/paths/types.d.ts b/packages/kit/src/runtime/app/paths/types.d.ts index a8967caf2aa7..76cd4e71b79e 100644 --- a/packages/kit/src/runtime/app/paths/types.d.ts +++ b/packages/kit/src/runtime/app/paths/types.d.ts @@ -1,3 +1,4 @@ +// @ts-ignore import { RouteId, RouteParams } from '$app/types'; /** From 26beba73e40ce299d2bd4b0be5a4166d81fa2df1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 16:25:43 -0400 Subject: [PATCH 10/31] test --- .../src/core/sync/write_types/index.spec.js | 2 +- .../sync/write_types/test/app-types/+page.ts | 19 +++++++++++++++++++ .../test/app-types/foo/[bar]/[baz]/+page.ts | 0 .../core/sync/write_types/test/tsconfig.json | 2 +- 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 packages/kit/src/core/sync/write_types/test/app-types/+page.ts create mode 100644 packages/kit/src/core/sync/write_types/test/app-types/foo/[bar]/[baz]/+page.ts diff --git a/packages/kit/src/core/sync/write_types/index.spec.js b/packages/kit/src/core/sync/write_types/index.spec.js index 0cb59ed5cc4e..9f7d2c538474 100644 --- a/packages/kit/src/core/sync/write_types/index.spec.js +++ b/packages/kit/src/core/sync/write_types/index.spec.js @@ -51,7 +51,7 @@ test('Creates correct $types', { timeout: 30000 }, () => { `${cwd}/${dir}/.svelte-kit/types/index.d.ts` ]; - tsconfig.include = [`./${dir}/**/*.js`]; + tsconfig.include = [`./${dir}/**/*.js`, `./${dir}/**/*.ts`]; fs.writeFileSync(tsconfig_file, JSON.stringify(tsconfig)); diff --git a/packages/kit/src/core/sync/write_types/test/app-types/+page.ts b/packages/kit/src/core/sync/write_types/test/app-types/+page.ts new file mode 100644 index 000000000000..8f904cc26d61 --- /dev/null +++ b/packages/kit/src/core/sync/write_types/test/app-types/+page.ts @@ -0,0 +1,19 @@ +import type { RouteId, RouteParams } from '$app/types'; + +declare let id: RouteId; + +// okay +id = '/'; +id = '/foo/[bar]/[baz]'; + +// @ts-expect-error +id = '/nope'; + +id; + +declare let params: RouteParams<'/foo/[bar]/[baz]'>; + +// @ts-expect-error +params.foo; // not okay +params.bar; // okay +params.baz; // okay diff --git a/packages/kit/src/core/sync/write_types/test/app-types/foo/[bar]/[baz]/+page.ts b/packages/kit/src/core/sync/write_types/test/app-types/foo/[bar]/[baz]/+page.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/sync/write_types/test/tsconfig.json b/packages/kit/src/core/sync/write_types/test/tsconfig.json index 39ea59584873..9c43f3c10e41 100644 --- a/packages/kit/src/core/sync/write_types/test/tsconfig.json +++ b/packages/kit/src/core/sync/write_types/test/tsconfig.json @@ -14,6 +14,6 @@ "types": ["../../../../types/internal"] } }, - "include": ["./**/*.js"], + "include": ["./**/*.js", "./**/*.ts"], "exclude": ["./**/.svelte-kit/**"] } From 5865e34ade15d7cb321922f589aa782cb6246508 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 16:29:00 -0400 Subject: [PATCH 11/31] ouch --- packages/kit/src/core/sync/write_types/index.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/core/sync/write_types/index.spec.js b/packages/kit/src/core/sync/write_types/index.spec.js index 9f7d2c538474..e8f3c91c9701 100644 --- a/packages/kit/src/core/sync/write_types/index.spec.js +++ b/packages/kit/src/core/sync/write_types/index.spec.js @@ -30,7 +30,7 @@ function run_test(dir) { write_all_types(initial, manifest); } -test('Creates correct $types', { timeout: 30000 }, () => { +test('Creates correct $types', { timeout: 90000 }, () => { // To save us from creating a real SvelteKit project for each of the tests, // we first run the type generation directly for each test case, and then // call `tsc` to check that the generated types are valid. From e155d927c2799826e11defbd1a38b36323b1cd4a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 20:01:39 -0400 Subject: [PATCH 12/31] oh actually lets do this instead --- .../src/core/sync/write_types/index.spec.js | 32 +++++-------------- .../sync/write_types/test/app-types/+page.ts | 2 +- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/packages/kit/src/core/sync/write_types/index.spec.js b/packages/kit/src/core/sync/write_types/index.spec.js index e8f3c91c9701..82af21f9d3f4 100644 --- a/packages/kit/src/core/sync/write_types/index.spec.js +++ b/packages/kit/src/core/sync/write_types/index.spec.js @@ -30,7 +30,7 @@ function run_test(dir) { write_all_types(initial, manifest); } -test('Creates correct $types', { timeout: 90000 }, () => { +test('Creates correct $types', { timeout: 6000 }, () => { // To save us from creating a real SvelteKit project for each of the tests, // we first run the type generation directly for each test case, and then // call `tsc` to check that the generated types are valid. @@ -38,31 +38,15 @@ test('Creates correct $types', { timeout: 90000 }, () => { .readdirSync(cwd) .filter((dir) => fs.statSync(`${cwd}/${dir}`).isDirectory()); - const tsconfig_file = `${cwd}/tsconfig.json`; - const tsconfig_json = fs.readFileSync(tsconfig_file, 'utf-8'); - const tsconfig = JSON.parse(tsconfig_json); + for (const dir of directories) { + run_test(dir); + } try { - for (const dir of directories) { - run_test(dir); - - try { - tsconfig.compilerOptions.paths['$app/types'] = [ - `${cwd}/${dir}/.svelte-kit/types/index.d.ts` - ]; - - tsconfig.include = [`./${dir}/**/*.js`, `./${dir}/**/*.ts`]; - - fs.writeFileSync(tsconfig_file, JSON.stringify(tsconfig)); - - execSync('pnpm testtypes', { cwd }); - } catch (e) { - console.error(/** @type {any} */ (e).stdout.toString()); - throw new Error('Type tests failed'); - } - } - } finally { - fs.writeFileSync(tsconfig_file, tsconfig_json); + execSync('pnpm testtypes', { cwd }); + } catch (e) { + console.error(/** @type {any} */ (e).stdout.toString()); + throw new Error('Type tests failed'); } }); diff --git a/packages/kit/src/core/sync/write_types/test/app-types/+page.ts b/packages/kit/src/core/sync/write_types/test/app-types/+page.ts index 8f904cc26d61..38cf61111176 100644 --- a/packages/kit/src/core/sync/write_types/test/app-types/+page.ts +++ b/packages/kit/src/core/sync/write_types/test/app-types/+page.ts @@ -1,4 +1,4 @@ -import type { RouteId, RouteParams } from '$app/types'; +import type { RouteId, RouteParams } from './.svelte-kit/types/index.d.ts'; declare let id: RouteId; From a4839613760bb6065923bc9bd11939c7300f9bcd Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 20:04:18 -0400 Subject: [PATCH 13/31] fix --- packages/adapter-auto/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/adapter-auto/tsconfig.json b/packages/adapter-auto/tsconfig.json index c1f396b255ce..99b99d00b826 100644 --- a/packages/adapter-auto/tsconfig.json +++ b/packages/adapter-auto/tsconfig.json @@ -7,7 +7,8 @@ "target": "es2022", "module": "node16", "moduleResolution": "node16", - "baseUrl": "." + "baseUrl": ".", + "skipLibCheck": true }, "include": ["**/*.js"] } From 7987509dee54b010f5860b325cb900f76b4dcf72 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 20:12:21 -0400 Subject: [PATCH 14/31] couple more, though still doesnt quite fix everything --- packages/adapter-node/tsconfig.json | 3 ++- packages/adapter-static/tsconfig.json | 3 ++- packages/adapter-vercel/tsconfig.json | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/adapter-node/tsconfig.json b/packages/adapter-node/tsconfig.json index 895d76f908cf..e550f4e7748e 100644 --- a/packages/adapter-node/tsconfig.json +++ b/packages/adapter-node/tsconfig.json @@ -11,7 +11,8 @@ "baseUrl": ".", "paths": { "@sveltejs/kit": ["../kit/types/index"] - } + }, + "skipLibCheck": true }, "include": ["index.js", "src/**/*.js", "tests/**/*.js", "internal.d.ts", "utils.js"], "exclude": ["tests/smoke.spec_disabled.js"] diff --git a/packages/adapter-static/tsconfig.json b/packages/adapter-static/tsconfig.json index dafbce422a39..9360c09e0730 100644 --- a/packages/adapter-static/tsconfig.json +++ b/packages/adapter-static/tsconfig.json @@ -11,7 +11,8 @@ "baseUrl": ".", "paths": { "@sveltejs/kit": ["../kit/types/index"] - } + }, + "skipLibCheck": true }, "include": ["index.js", "test/utils.js"] } diff --git a/packages/adapter-vercel/tsconfig.json b/packages/adapter-vercel/tsconfig.json index 3d157ebc29e5..e832e33d4024 100644 --- a/packages/adapter-vercel/tsconfig.json +++ b/packages/adapter-vercel/tsconfig.json @@ -12,6 +12,7 @@ "baseUrl": ".", "paths": { "@sveltejs/kit": ["../kit/types/index"] - } + }, + "skipLibCheck": true } } From 7cf2cfc56b462d2ee079df0243c8d8727d381f8b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 20:25:19 -0400 Subject: [PATCH 15/31] forgive me --- packages/kit/scripts/generate-dts.js | 8 +++++++- packages/kit/types/index.d.ts | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/kit/scripts/generate-dts.js b/packages/kit/scripts/generate-dts.js index e8579ec59054..1af5db6ae96b 100644 --- a/packages/kit/scripts/generate-dts.js +++ b/packages/kit/scripts/generate-dts.js @@ -1,5 +1,5 @@ import { createBundle } from 'dts-buddy'; -import { readFileSync } from 'node:fs'; +import { readFileSync, writeFileSync } from 'node:fs'; await createBundle({ output: 'types/index.d.ts', @@ -28,3 +28,9 @@ if (types.includes('__sveltekit/')) { types ); } + +// this is hacky as all hell but it gets the tests passing. might be a bug in dts-buddy? +// prettier-ignore +writeFileSync('./types/index.d.ts', types.replace("declare module '$app/server' {", `declare module '$app/server' { + // @ts-ignore + import { LayoutParams as AppLayoutParams, RouteId as AppRouteId } from '$app/types'`)); diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 6602463f5c5f..9fe1bef7a8a2 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -2407,6 +2407,8 @@ declare module '$app/paths' { } declare module '$app/server' { + // @ts-ignore + import { LayoutParams as AppLayoutParams, RouteId as AppRouteId } from '$app/types' import type { RequestEvent } from '@sveltejs/kit'; /** * Read the contents of an imported asset from the filesystem From 8c8c03724cdb3240870d68c8bc2241452c9c2e06 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 21:22:53 -0400 Subject: [PATCH 16/31] fix --- packages/kit/src/exports/public.d.ts | 2 +- packages/kit/types/index.d.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 74704d7a6345..e33207319934 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -1124,7 +1124,7 @@ export interface AfterNavigate extends Omit { * The shape of the [`page`](https://svelte.dev/docs/kit/$app-state#page) reactive object and the [`$page`](https://svelte.dev/docs/kit/$app-stores) store. */ export interface Page< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, RouteId extends AppRouteId | null = AppRouteId | null > { /** diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 9fe1bef7a8a2..ab170e62e793 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -4,7 +4,7 @@ declare module '@sveltejs/kit' { import type { CompileOptions } from 'svelte/compiler'; import type { PluginOptions } from '@sveltejs/vite-plugin-svelte'; - import type { RouteId as AppRouteId, RouteParams as AppRouteParams, LayoutParams as AppLayoutParams } from '$app/types'; + import type { RouteId as AppRouteId, LayoutParams as AppLayoutParams } from '$app/types'; /** * [Adapters](https://svelte.dev/docs/kit/adapters) are responsible for taking the production build and turning it into something that can be deployed to a platform of your choosing. */ @@ -1101,7 +1101,7 @@ declare module '@sveltejs/kit' { * The shape of the [`page`](https://svelte.dev/docs/kit/$app-state#page) reactive object and the [`$page`](https://svelte.dev/docs/kit/$app-stores) store. */ export interface Page< - Params extends AppRouteParams = AppRouteParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, RouteId extends AppRouteId | null = AppRouteId | null > { /** From c6c3355c7d0f3f60f7a9ebab75b67ae648b082e2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 7 Jun 2025 22:20:50 -0400 Subject: [PATCH 17/31] fix --- packages/kit/src/exports/public.d.ts | 18 +++++++++--------- packages/kit/types/index.d.ts | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index e33207319934..c28d23821db9 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -864,7 +864,7 @@ export interface Transporter< * rather than using `Load` directly. */ export type Load< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, InputData extends Record | null = Record | null, ParentData extends Record = Record, OutputData extends Record | void = Record | void, @@ -876,7 +876,7 @@ export type Load< * rather than using `LoadEvent` directly. */ export interface LoadEvent< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, Data extends Record | null = Record | null, ParentData extends Record = Record, RouteId extends AppRouteId | null = AppRouteId | null @@ -984,7 +984,7 @@ export interface LoadEvent< } export interface NavigationEvent< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, RouteId extends AppRouteId | null = AppRouteId | null > { /** @@ -1172,7 +1172,7 @@ export interface Page< export type ParamMatcher = (param: string) => boolean; export interface RequestEvent< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, RouteId extends AppRouteId | null = AppRouteId | null > { /** @@ -1264,7 +1264,7 @@ export interface RequestEvent< * It receives `Params` as the first generic argument, which you can skip by using [generated types](https://svelte.dev/docs/kit/types#Generated-types) instead. */ export type RequestHandler< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, RouteId extends AppRouteId | null = AppRouteId | null > = (event: RequestEvent) => MaybePromise; @@ -1343,14 +1343,14 @@ export interface SSRManifest { * rather than using `ServerLoad` directly. */ export type ServerLoad< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, ParentData extends Record = Record, OutputData extends Record | void = Record | void, RouteId extends AppRouteId | null = AppRouteId | null > = (event: ServerLoadEvent) => MaybePromise; export interface ServerLoadEvent< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, ParentData extends Record = Record, RouteId extends AppRouteId | null = AppRouteId | null > extends RequestEvent { @@ -1419,7 +1419,7 @@ export interface ServerLoadEvent< * See [form actions](https://svelte.dev/docs/kit/form-actions) for more information. */ export type Action< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, OutputData extends Record | void = Record | void, RouteId extends AppRouteId | null = AppRouteId | null > = (event: RequestEvent) => MaybePromise; @@ -1429,7 +1429,7 @@ export type Action< * See [form actions](https://svelte.dev/docs/kit/form-actions) for more information. */ export type Actions< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, OutputData extends Record | void = Record | void, RouteId extends AppRouteId | null = AppRouteId | null > = Record>; diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index ab170e62e793..c9c9f9566dfb 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -841,7 +841,7 @@ declare module '@sveltejs/kit' { * rather than using `Load` directly. */ export type Load< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, InputData extends Record | null = Record | null, ParentData extends Record = Record, OutputData extends Record | void = Record | void, @@ -853,7 +853,7 @@ declare module '@sveltejs/kit' { * rather than using `LoadEvent` directly. */ export interface LoadEvent< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, Data extends Record | null = Record | null, ParentData extends Record = Record, RouteId extends AppRouteId | null = AppRouteId | null @@ -961,7 +961,7 @@ declare module '@sveltejs/kit' { } export interface NavigationEvent< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, RouteId extends AppRouteId | null = AppRouteId | null > { /** @@ -1149,7 +1149,7 @@ declare module '@sveltejs/kit' { export type ParamMatcher = (param: string) => boolean; export interface RequestEvent< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, RouteId extends AppRouteId | null = AppRouteId | null > { /** @@ -1241,7 +1241,7 @@ declare module '@sveltejs/kit' { * It receives `Params` as the first generic argument, which you can skip by using [generated types](https://svelte.dev/docs/kit/types#Generated-types) instead. */ export type RequestHandler< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, RouteId extends AppRouteId | null = AppRouteId | null > = (event: RequestEvent) => MaybePromise; @@ -1320,14 +1320,14 @@ declare module '@sveltejs/kit' { * rather than using `ServerLoad` directly. */ export type ServerLoad< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, ParentData extends Record = Record, OutputData extends Record | void = Record | void, RouteId extends AppRouteId | null = AppRouteId | null > = (event: ServerLoadEvent) => MaybePromise; export interface ServerLoadEvent< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, ParentData extends Record = Record, RouteId extends AppRouteId | null = AppRouteId | null > extends RequestEvent { @@ -1396,7 +1396,7 @@ declare module '@sveltejs/kit' { * See [form actions](https://svelte.dev/docs/kit/form-actions) for more information. */ export type Action< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, OutputData extends Record | void = Record | void, RouteId extends AppRouteId | null = AppRouteId | null > = (event: RequestEvent) => MaybePromise; @@ -1406,7 +1406,7 @@ declare module '@sveltejs/kit' { * See [form actions](https://svelte.dev/docs/kit/form-actions) for more information. */ export type Actions< - Params extends AppLayoutParams = AppLayoutParams, + Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, OutputData extends Record | void = Record | void, RouteId extends AppRouteId | null = AppRouteId | null > = Record>; @@ -2429,7 +2429,7 @@ declare module '$app/server' { * In environments without [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage), this must be called synchronously (i.e. not after an `await`). * @since 2.20.0 */ - export function getRequestEvent(): RequestEvent, any>; + export function getRequestEvent(): RequestEvent, any>; export {}; } From cfd16eda0c7be8a56c5b5ed44436ae67e7b6b2f3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 8 Jun 2025 06:41:04 -0400 Subject: [PATCH 18/31] make url.pathname type safe as well --- documentation/docs/98-reference/20-$app-types.md | 14 +++++++++++++- packages/kit/src/core/sync/write_types/index.js | 10 +++++++++- .../core/sync/write_types/test/app-types/+page.ts | 11 ++++++++++- packages/kit/src/exports/public.d.ts | 10 +++------- .../kit/test/apps/basics/src/routes/+layout.svelte | 2 ++ packages/kit/types/index.d.ts | 4 ++-- 6 files changed, 39 insertions(+), 12 deletions(-) diff --git a/documentation/docs/98-reference/20-$app-types.md b/documentation/docs/98-reference/20-$app-types.md index 3e81511d6792..6e29458a7515 100644 --- a/documentation/docs/98-reference/20-$app-types.md +++ b/documentation/docs/98-reference/20-$app-types.md @@ -11,7 +11,7 @@ import type { RouteId, RouteParams, LayoutParams } from '$app/types'; ## RouteId -A union of all the route IDs in your app. +A union of all the route IDs in your app. Used for `page.route.id` and `event.route.id`.
@@ -21,6 +21,18 @@ type RouteId = '/' | '/my-route' | '/my-other-route/[param]';
+## Pathname + +A union of all valid pathnames in your app. Used for `page.url.pathname`. + +
+ +```dts +type Pathname = '/' | '/my-route' | `/my-other-route/${string}`; +``` + +
+ ## RouteParams A utility for getting the parameters associated with a given route. diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index 996a17fedffe..d5873e79a8f6 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -50,6 +50,9 @@ export function write_all_types(config, manifest_data) { } } + /** @type {string[]} */ + const pathnames = []; + /** @type {string[]} */ const routes = []; @@ -57,6 +60,10 @@ export function write_all_types(config, manifest_data) { const layouts = []; for (const route of manifest_data.routes) { + pathnames.push( + `\`${route.id.replace(/\/\[\[[^\]]+\]\]/g, '${string}').replace(/\/\[[^\]]+\]/g, '/${string}')}\`` + ); + const route_params = route.params .map((p) => `${p.name}${p.optional ? '?:' : ':'} string`) .join('; '); @@ -95,7 +102,8 @@ export function write_all_types(config, manifest_data) { // we enumerate these rather than doing `keyof Routes` so that the list is visible on hover `export type RouteId = ${manifest_data.routes.map((r) => s(r.id)).join(' | ')};`, 'export type RouteParams = Routes[T] | Record;', - 'export type LayoutParams = Layouts[T] | Record;' + 'export type LayoutParams = Layouts[T] | Record;', + `export type Pathname = ${pathnames.join(' | ')};` ].join('\n\n') ); diff --git a/packages/kit/src/core/sync/write_types/test/app-types/+page.ts b/packages/kit/src/core/sync/write_types/test/app-types/+page.ts index 38cf61111176..f7d0a80d2287 100644 --- a/packages/kit/src/core/sync/write_types/test/app-types/+page.ts +++ b/packages/kit/src/core/sync/write_types/test/app-types/+page.ts @@ -1,4 +1,4 @@ -import type { RouteId, RouteParams } from './.svelte-kit/types/index.d.ts'; +import type { RouteId, RouteParams, Pathname } from './.svelte-kit/types/index.d.ts'; declare let id: RouteId; @@ -17,3 +17,12 @@ declare let params: RouteParams<'/foo/[bar]/[baz]'>; params.foo; // not okay params.bar; // okay params.baz; // okay + +declare let pathname: Pathname; + +// @ts-expect-error +pathname = '/nope'; +pathname = '/foo'; +pathname = '/foo/1/2'; + +pathname; diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index c28d23821db9..f48b65701f27 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -19,12 +19,8 @@ import { } from '../types/private.js'; import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types'; import type { PluginOptions } from '@sveltejs/vite-plugin-svelte'; -import { - RouteId as AppRouteId, - RouteParams as AppRouteParams, - LayoutParams as AppLayoutParams - // @ts-ignore -} from '$app/types'; +// @ts-ignore +import { RouteId as AppRouteId, LayoutParams as AppLayoutParams, Pathname } from '$app/types'; export { PrerenderOption } from '../types/private.js'; @@ -1130,7 +1126,7 @@ export interface Page< /** * The URL of the current page. */ - url: URL; + url: URL & { pathname: Pathname }; /** * The parameters of the current page - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object. */ diff --git a/packages/kit/test/apps/basics/src/routes/+layout.svelte b/packages/kit/test/apps/basics/src/routes/+layout.svelte index 6272671c81ae..88accff7b670 100644 --- a/packages/kit/test/apps/basics/src/routes/+layout.svelte +++ b/packages/kit/test/apps/basics/src/routes/+layout.svelte @@ -7,6 +7,8 @@ setup(); +{#if page.url.} +
{data.foo.bar}
diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index c9c9f9566dfb..94ea16ee74a5 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -4,7 +4,7 @@ declare module '@sveltejs/kit' { import type { CompileOptions } from 'svelte/compiler'; import type { PluginOptions } from '@sveltejs/vite-plugin-svelte'; - import type { RouteId as AppRouteId, LayoutParams as AppLayoutParams } from '$app/types'; + import type { RouteId as AppRouteId, LayoutParams as AppLayoutParams, Pathname } from '$app/types'; /** * [Adapters](https://svelte.dev/docs/kit/adapters) are responsible for taking the production build and turning it into something that can be deployed to a platform of your choosing. */ @@ -1107,7 +1107,7 @@ declare module '@sveltejs/kit' { /** * The URL of the current page. */ - url: URL; + url: URL & { pathname: Pathname }; /** * The parameters of the current page - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object. */ From c000bb4ba10e565351ed771a42b5e99946a99468 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 8 Jun 2025 07:38:09 -0400 Subject: [PATCH 19/31] oops didnt mean to commit that --- packages/kit/test/apps/basics/src/routes/+layout.svelte | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/kit/test/apps/basics/src/routes/+layout.svelte b/packages/kit/test/apps/basics/src/routes/+layout.svelte index 88accff7b670..969229a5f678 100644 --- a/packages/kit/test/apps/basics/src/routes/+layout.svelte +++ b/packages/kit/test/apps/basics/src/routes/+layout.svelte @@ -1,4 +1,4 @@ - -{#if page.url.} -
{data.foo.bar}
From 945f9ccee7507b8c0b8c1ee8753918cdba067099 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 9 Jun 2025 11:42:27 -0400 Subject: [PATCH 20/31] fix --- packages/kit/src/core/sync/write_types/index.js | 16 ++++++++-------- packages/kit/src/runtime/app/paths/types.d.ts | 2 +- packages/kit/types/index.d.ts | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index d5873e79a8f6..3523d5b027cb 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -54,7 +54,7 @@ export function write_all_types(config, manifest_data) { const pathnames = []; /** @type {string[]} */ - const routes = []; + const dynamic_routes = []; /** @type {string[]} */ const layouts = []; @@ -64,12 +64,12 @@ export function write_all_types(config, manifest_data) { `\`${route.id.replace(/\/\[\[[^\]]+\]\]/g, '${string}').replace(/\/\[[^\]]+\]/g, '/${string}')}\`` ); - const route_params = route.params - .map((p) => `${p.name}${p.optional ? '?:' : ':'} string`) - .join('; '); + if (route.params.length > 0) { + const params = route.params.map((p) => `${p.name}${p.optional ? '?:' : ':'} string`); + const route_type = `${s(route.id)}: { ${params.join('; ')} }`; - const route_type = `${s(route.id)}: ${route_params.length > 0 ? `{ ${route_params} }` : 'undefined'}`; - routes.push(route_type); + dynamic_routes.push(route_type); + } /** @type {Map} */ const child_params = new Map(route.params.map((p) => [p.name, p.optional])); @@ -97,11 +97,11 @@ export function write_all_types(config, manifest_data) { fs.writeFileSync( `${types_dir}/index.d.ts`, [ - `type Routes = {\n\t${routes.join(';\n\t')}\n};`, + `type DynamicRoutes = {\n\t${dynamic_routes.join(';\n\t')}\n};`, `type Layouts = {\n\t${layouts.join(';\n\t')}\n};`, // we enumerate these rather than doing `keyof Routes` so that the list is visible on hover `export type RouteId = ${manifest_data.routes.map((r) => s(r.id)).join(' | ')};`, - 'export type RouteParams = Routes[T] | Record;', + 'export type RouteParams = T extends keyof DynamicRoutes ? DynamicRoutes[T] : Record;', 'export type LayoutParams = Layouts[T] | Record;', `export type Pathname = ${pathnames.join(' | ')};` ].join('\n\n') diff --git a/packages/kit/src/runtime/app/paths/types.d.ts b/packages/kit/src/runtime/app/paths/types.d.ts index 76cd4e71b79e..a9fdfb1f5615 100644 --- a/packages/kit/src/runtime/app/paths/types.d.ts +++ b/packages/kit/src/runtime/app/paths/types.d.ts @@ -16,7 +16,7 @@ export let base: '' | `/${string}`; export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets'; type ResolveRouteArgs = - RouteParams extends undefined ? [route: T] : [route: T, params: RouteParams]; + RouteParams extends Record ? [route: T] : [route: T, params: RouteParams]; /** * Populate a route ID with params to resolve a pathname. diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 94ea16ee74a5..c13c5ed6bef9 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -2384,7 +2384,7 @@ declare module '$app/paths' { export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets'; type ResolveRouteArgs = - RouteParams extends undefined ? [route: T] : [route: T, params: RouteParams]; + RouteParams extends Record ? [route: T] : [route: T, params: RouteParams]; /** * Populate a route ID with params to resolve a pathname. From 9bd9a676fff0b2df184537bdbef9fac82849cdfb Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 9 Jun 2025 13:10:03 -0400 Subject: [PATCH 21/31] page.url.pathname needs to account for base path --- documentation/docs/98-reference/20-$app-types.md | 14 +++++++++++++- packages/kit/src/core/sync/write_types/index.js | 3 ++- packages/kit/src/exports/public.d.ts | 10 +++++++--- packages/kit/types/index.d.ts | 4 ++-- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/documentation/docs/98-reference/20-$app-types.md b/documentation/docs/98-reference/20-$app-types.md index 6e29458a7515..af936d826739 100644 --- a/documentation/docs/98-reference/20-$app-types.md +++ b/documentation/docs/98-reference/20-$app-types.md @@ -23,7 +23,19 @@ type RouteId = '/' | '/my-route' | '/my-other-route/[param]'; ## Pathname -A union of all valid pathnames in your app. Used for `page.url.pathname`. +A union of all valid pathnames in your app. + +
+ +```dts +type Pathname = '/' | '/my-route' | `/my-other-route/${string}`; +``` + +
+ +## ResolvedPathname + +`Pathname`, but possibly prefixed with a [base path](https://svelte.dev/docs/kit/configuration#paths). Used for `page.url.pathname`.
diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index 3523d5b027cb..4ba7135a49a7 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -103,7 +103,8 @@ export function write_all_types(config, manifest_data) { `export type RouteId = ${manifest_data.routes.map((r) => s(r.id)).join(' | ')};`, 'export type RouteParams = T extends keyof DynamicRoutes ? DynamicRoutes[T] : Record;', 'export type LayoutParams = Layouts[T] | Record;', - `export type Pathname = ${pathnames.join(' | ')};` + `export type Pathname = ${pathnames.join(' | ')};`, + 'export type ResolvedPathname = `${"" | `/${string}`}${Pathname}`;' ].join('\n\n') ); diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index f48b65701f27..e4fe96a925c3 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -19,8 +19,12 @@ import { } from '../types/private.js'; import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types'; import type { PluginOptions } from '@sveltejs/vite-plugin-svelte'; -// @ts-ignore -import { RouteId as AppRouteId, LayoutParams as AppLayoutParams, Pathname } from '$app/types'; +import { + RouteId as AppRouteId, + LayoutParams as AppLayoutParams, + ResolvedPathname + // @ts-ignore +} from '$app/types'; export { PrerenderOption } from '../types/private.js'; @@ -1126,7 +1130,7 @@ export interface Page< /** * The URL of the current page. */ - url: URL & { pathname: Pathname }; + url: URL & { pathname: ResolvedPathname }; /** * The parameters of the current page - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object. */ diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index c13c5ed6bef9..bb52d69d66ee 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -4,7 +4,7 @@ declare module '@sveltejs/kit' { import type { CompileOptions } from 'svelte/compiler'; import type { PluginOptions } from '@sveltejs/vite-plugin-svelte'; - import type { RouteId as AppRouteId, LayoutParams as AppLayoutParams, Pathname } from '$app/types'; + import type { RouteId as AppRouteId, LayoutParams as AppLayoutParams, ResolvedPathname } from '$app/types'; /** * [Adapters](https://svelte.dev/docs/kit/adapters) are responsible for taking the production build and turning it into something that can be deployed to a platform of your choosing. */ @@ -1107,7 +1107,7 @@ declare module '@sveltejs/kit' { /** * The URL of the current page. */ - url: URL & { pathname: Pathname }; + url: URL & { pathname: ResolvedPathname }; /** * The parameters of the current page - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object. */ From 82d66957a1f5a6d332313685c8bfdcd53f2a1a5b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 9 Jun 2025 13:57:10 -0400 Subject: [PATCH 22/31] resolveRoute -> resolve --- .../kit/src/core/sync/write_types/index.js | 10 +++-- packages/kit/src/runtime/app/paths/index.js | 6 ++- packages/kit/src/runtime/app/paths/types.d.ts | 38 ++++++++++++------ packages/kit/types/index.d.ts | 40 ++++++++++++------- 4 files changed, 61 insertions(+), 33 deletions(-) diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index 4ba7135a49a7..509d3690d95a 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -60,15 +60,17 @@ export function write_all_types(config, manifest_data) { const layouts = []; for (const route of manifest_data.routes) { - pathnames.push( - `\`${route.id.replace(/\/\[\[[^\]]+\]\]/g, '${string}').replace(/\/\[[^\]]+\]/g, '/${string}')}\`` - ); - if (route.params.length > 0) { const params = route.params.map((p) => `${p.name}${p.optional ? '?:' : ':'} string`); const route_type = `${s(route.id)}: { ${params.join('; ')} }`; dynamic_routes.push(route_type); + + pathnames.push( + `\`${route.id.replace(/\/\[\[[^\]]+\]\]/g, '${string}').replace(/\/\[[^\]]+\]/g, '/${string}')}\` & {}` + ); + } else { + pathnames.push(s(route.id)); } /** @type {Map} */ diff --git a/packages/kit/src/runtime/app/paths/index.js b/packages/kit/src/runtime/app/paths/index.js index 5a150fbfaa0f..da4bf9e47c87 100644 --- a/packages/kit/src/runtime/app/paths/index.js +++ b/packages/kit/src/runtime/app/paths/index.js @@ -2,7 +2,9 @@ export { base, assets } from '__sveltekit/paths'; import { base } from '__sveltekit/paths'; import { resolve_route } from '../../../utils/routing.js'; -/** @type {import('./types.d.ts').resolveRoute} */ -export function resolveRoute(id, params) { +/** @type {import('./types.d.ts').resolve} */ +export function resolve(id, params) { return base + resolve_route(id, params); } + +export { resolve as resolveRoute }; diff --git a/packages/kit/src/runtime/app/paths/types.d.ts b/packages/kit/src/runtime/app/paths/types.d.ts index a9fdfb1f5615..8e860bb24b6f 100644 --- a/packages/kit/src/runtime/app/paths/types.d.ts +++ b/packages/kit/src/runtime/app/paths/types.d.ts @@ -1,5 +1,5 @@ // @ts-ignore -import { RouteId, RouteParams } from '$app/types'; +import { RouteId, RouteParams, Pathname, ResolvedPathname } from '$app/types'; /** * A string that matches [`config.kit.paths.base`](https://svelte.dev/docs/kit/configuration#paths). @@ -15,22 +15,34 @@ export let base: '' | `/${string}`; */ export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets'; -type ResolveRouteArgs = - RouteParams extends Record ? [route: T] : [route: T, params: RouteParams]; +type ResolveArgs = T extends RouteId + ? RouteParams extends Record + ? [route: T] + : [route: T, params: RouteParams] + : [route: T]; /** - * Populate a route ID with params to resolve a pathname. + * Resolve a pathname by prefixing it with the base path, if any, + * or resolve a route ID by populating dynamic segments with parameters. * @example * ```js - * import { resolveRoute } from '$app/paths'; + * import { resolve } from '$app/paths'; * - * resolveRoute( - * `/blog/[slug]/[...somethingElse]`, - * { - * slug: 'hello-world', - * somethingElse: 'something/else' - * } - * ); // `/blog/hello-world/something/else` + * // using a pathname + * const resolved = resolve(`/blog/hello-world`); + * + * // using a route ID plus parameters + * const resolved = resolve('/blog/[slug]', { + * slug: 'hello-world' + * }); * ``` + * @since 2.22 + */ +export function resolve(...args: ResolveArgs): ResolvedPathname; + +/** + * @deprecated Use `resolve(...)` instead */ -export function resolveRoute(...args: ResolveRouteArgs): string; +export function resolveRoute( + ...args: ResolveArgs +): ResolvedPathname; diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index bb52d69d66ee..7719c0c55ac0 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -2368,7 +2368,7 @@ declare module '$app/navigation' { } declare module '$app/paths' { - import type { RouteId, RouteParams } from '$app/types'; + import type { RouteId, RouteParams, Pathname, ResolvedPathname } from '$app/types'; /** * A string that matches [`config.kit.paths.base`](https://svelte.dev/docs/kit/configuration#paths). * @@ -2383,25 +2383,37 @@ declare module '$app/paths' { */ export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets'; - type ResolveRouteArgs = - RouteParams extends Record ? [route: T] : [route: T, params: RouteParams]; + type ResolveArgs = T extends RouteId + ? RouteParams extends Record + ? [route: T] + : [route: T, params: RouteParams] + : [route: T]; /** - * Populate a route ID with params to resolve a pathname. + * Resolve a pathname by prefixing it with the base path, if any, + * or resolve a route ID by populating dynamic segments with parameters. * @example * ```js - * import { resolveRoute } from '$app/paths'; - * - * resolveRoute( - * `/blog/[slug]/[...somethingElse]`, - * { - * slug: 'hello-world', - * somethingElse: 'something/else' - * } - * ); // `/blog/hello-world/something/else` + * import { resolve } from '$app/paths'; + * + * // using a pathname + * const resolved = resolve(`/blog/hello-world`); + * + * // using a route ID plus parameters + * const resolved = resolve('/blog/[slug]', { + * slug: 'hello-world' + * }); * ``` + * @since 2.22 + */ + export function resolve(...args: ResolveArgs): ResolvedPathname; + + /** + * @deprecated Use `resolve(...)` instead */ - export function resolveRoute(...args: ResolveRouteArgs): string; + export function resolveRoute( + ...args: ResolveArgs + ): ResolvedPathname; export {}; } From 6cd32d4d8d83c4b32fde815dd5bb5bc136f8ecd1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 9 Jun 2025 14:22:08 -0400 Subject: [PATCH 23/31] add `asset(...)` function, deprecate `base` and `assets` --- .../docs/98-reference/20-$app-types.md | 12 ++++++++ .../kit/src/core/sync/write_types/index.js | 3 +- packages/kit/src/runtime/app/paths/index.js | 10 +++++-- packages/kit/src/runtime/app/paths/types.d.ts | 28 +++++++++++++++++-- packages/kit/types/index.d.ts | 24 ++++++++++++++-- 5 files changed, 67 insertions(+), 10 deletions(-) diff --git a/documentation/docs/98-reference/20-$app-types.md b/documentation/docs/98-reference/20-$app-types.md index af936d826739..478f008fd371 100644 --- a/documentation/docs/98-reference/20-$app-types.md +++ b/documentation/docs/98-reference/20-$app-types.md @@ -9,6 +9,18 @@ This module contains generated types for the routes in your app. import type { RouteId, RouteParams, LayoutParams } from '$app/types'; ``` +## Asset + +A union of all the filenames of assets contained in your `static` directory. + +
+ +```dts +type Asset = '/favicon.png' | '/robots.txt'; +``` + +
+ ## RouteId A union of all the route IDs in your app. Used for `page.route.id` and `event.route.id`. diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index 509d3690d95a..f9592547fea5 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -106,7 +106,8 @@ export function write_all_types(config, manifest_data) { 'export type RouteParams = T extends keyof DynamicRoutes ? DynamicRoutes[T] : Record;', 'export type LayoutParams = Layouts[T] | Record;', `export type Pathname = ${pathnames.join(' | ')};`, - 'export type ResolvedPathname = `${"" | `/${string}`}${Pathname}`;' + 'export type ResolvedPathname = `${"" | `/${string}`}${Pathname}`;', + `export type Asset = ${manifest_data.assets.map((asset) => s('/' + asset.file)).join(' | ')};` ].join('\n\n') ); diff --git a/packages/kit/src/runtime/app/paths/index.js b/packages/kit/src/runtime/app/paths/index.js index da4bf9e47c87..d9f29bbdc395 100644 --- a/packages/kit/src/runtime/app/paths/index.js +++ b/packages/kit/src/runtime/app/paths/index.js @@ -1,10 +1,14 @@ -export { base, assets } from '__sveltekit/paths'; -import { base } from '__sveltekit/paths'; +import { base, assets } from '__sveltekit/paths'; import { resolve_route } from '../../../utils/routing.js'; +/** @type {import('./types.d.ts').asset} */ +export function asset(file) { + return (assets || base) + file; +} + /** @type {import('./types.d.ts').resolve} */ export function resolve(id, params) { return base + resolve_route(id, params); } -export { resolve as resolveRoute }; +export { base, assets, resolve as resolveRoute }; diff --git a/packages/kit/src/runtime/app/paths/types.d.ts b/packages/kit/src/runtime/app/paths/types.d.ts index 8e860bb24b6f..a7785db7bd66 100644 --- a/packages/kit/src/runtime/app/paths/types.d.ts +++ b/packages/kit/src/runtime/app/paths/types.d.ts @@ -1,10 +1,12 @@ // @ts-ignore -import { RouteId, RouteParams, Pathname, ResolvedPathname } from '$app/types'; +import { Asset, RouteId, RouteParams, Pathname, ResolvedPathname } from '$app/types'; /** * A string that matches [`config.kit.paths.base`](https://svelte.dev/docs/kit/configuration#paths). * * Example usage: `Link` + * + * @deprecated Use `resolve(...)` instead */ export let base: '' | `/${string}`; @@ -12,6 +14,8 @@ export let base: '' | `/${string}`; * An absolute path that matches [`config.kit.paths.assets`](https://svelte.dev/docs/kit/configuration#paths). * * > [!NOTE] If a value for `config.kit.paths.assets` is specified, it will be replaced with `'/_svelte_kit_assets'` during `vite dev` or `vite preview`, since the assets don't yet live at their eventual URL. + * + * @deprecated Use `asset(...)` instead */ export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets'; @@ -22,8 +26,10 @@ type ResolveArgs = T extends RouteId : [route: T]; /** - * Resolve a pathname by prefixing it with the base path, if any, - * or resolve a route ID by populating dynamic segments with parameters. + * Resolve a pathname by prefixing it with the base path, if any, or resolve a route ID by populating dynamic segments with parameters. + * + * During server rendering, the base path is relative and depends on the page currently being rendered. + * * @example * ```js * import { resolve } from '$app/paths'; @@ -40,6 +46,22 @@ type ResolveArgs = T extends RouteId */ export function resolve(...args: ResolveArgs): ResolvedPathname; +/** + * Resolve the URL of an asset in your `static` directory, by prefixing it with [`config.kit.paths.assets`](https://svelte.dev/docs/kit/configuration#paths) if configured, or otherwise by prefixing it with the base path. + * + * During server rendering, the base path is relative and depends on the page currently being rendered. + * + * @example + * ```svelte + * + * + * a potato + * ``` + */ +export function asset(file: Asset): string; + /** * @deprecated Use `resolve(...)` instead */ diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 7719c0c55ac0..38517da9d7f2 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -2368,7 +2368,7 @@ declare module '$app/navigation' { } declare module '$app/paths' { - import type { RouteId, RouteParams, Pathname, ResolvedPathname } from '$app/types'; + import type { Asset, RouteId, RouteParams, Pathname, ResolvedPathname } from '$app/types'; /** * A string that matches [`config.kit.paths.base`](https://svelte.dev/docs/kit/configuration#paths). * @@ -2390,8 +2390,10 @@ declare module '$app/paths' { : [route: T]; /** - * Resolve a pathname by prefixing it with the base path, if any, - * or resolve a route ID by populating dynamic segments with parameters. + * Resolve a pathname by prefixing it with the base path, if any, or resolve a route ID by populating dynamic segments with parameters. + * + * During server rendering, the base path is relative and depends on the page currently being rendered. + * * @example * ```js * import { resolve } from '$app/paths'; @@ -2408,6 +2410,22 @@ declare module '$app/paths' { */ export function resolve(...args: ResolveArgs): ResolvedPathname; + /** + * Resolve the URL of an asset in your `static` directory, by prefixing it with [`config.kit.paths.assets`](https://svelte.dev/docs/kit/configuration#paths) if configured, or otherwise by prefixing it with the base path. + * + * During server rendering, the base path is relative and depends on the page currently being rendered. + * + * @example + * ```svelte + * + * + * a potato + * ``` + */ + export function asset(file: Asset): string; + /** * @deprecated Use `resolve(...)` instead */ From eb8b8766d45602fac4c6dbc647257c9c319170a0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 9 Jun 2025 14:27:22 -0400 Subject: [PATCH 24/31] fix --- packages/kit/src/core/sync/write_types/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index f9592547fea5..f774ba8ea607 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -107,7 +107,7 @@ export function write_all_types(config, manifest_data) { 'export type LayoutParams = Layouts[T] | Record;', `export type Pathname = ${pathnames.join(' | ')};`, 'export type ResolvedPathname = `${"" | `/${string}`}${Pathname}`;', - `export type Asset = ${manifest_data.assets.map((asset) => s('/' + asset.file)).join(' | ')};` + `export type Asset = ${manifest_data.assets.map((asset) => s('/' + asset.file)).join(' | ') || 'never'};` ].join('\n\n') ); From f6c4b35c972e5a90f9369f3b6ba80384eebd7c69 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 9 Jun 2025 14:31:36 -0400 Subject: [PATCH 25/31] changesets --- .changeset/proud-rules-bow.md | 5 +++++ .changeset/slow-weeks-wave.md | 5 +++++ .changeset/wicked-bananas-yawn.md | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .changeset/proud-rules-bow.md create mode 100644 .changeset/slow-weeks-wave.md diff --git a/.changeset/proud-rules-bow.md b/.changeset/proud-rules-bow.md new file mode 100644 index 000000000000..19b710b2784c --- /dev/null +++ b/.changeset/proud-rules-bow.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: better type-safety for `page.route.id`, `page.params`, page.url.pathname` and various other places diff --git a/.changeset/slow-weeks-wave.md b/.changeset/slow-weeks-wave.md new file mode 100644 index 000000000000..91be7b1af779 --- /dev/null +++ b/.changeset/slow-weeks-wave.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: `resolve(...)` and `asset(...)` helpers for resolving paths diff --git a/.changeset/wicked-bananas-yawn.md b/.changeset/wicked-bananas-yawn.md index 35664ccf76f5..a74d96b71ea8 100644 --- a/.changeset/wicked-bananas-yawn.md +++ b/.changeset/wicked-bananas-yawn.md @@ -2,4 +2,4 @@ '@sveltejs/kit': minor --- -feat: `RouteId` type, and type safety for things like `resolveRoute`, `page.route.id` and `page.params` +feat: Add `$app/types` module with `Asset`, `RouteId`, `Pathname`, `ResolvedPathname` `RouteParams` and `LayoutParams` From c1987e01ed94dbdf122d0ebcf5ef85b72be876ed Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 9 Jun 2025 14:32:32 -0400 Subject: [PATCH 26/31] regenerate --- packages/kit/types/index.d.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 38517da9d7f2..63073d637016 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -2373,6 +2373,8 @@ declare module '$app/paths' { * A string that matches [`config.kit.paths.base`](https://svelte.dev/docs/kit/configuration#paths). * * Example usage: `Link` + * + * @deprecated Use `resolve(...)` instead */ export let base: '' | `/${string}`; @@ -2380,6 +2382,8 @@ declare module '$app/paths' { * An absolute path that matches [`config.kit.paths.assets`](https://svelte.dev/docs/kit/configuration#paths). * * > [!NOTE] If a value for `config.kit.paths.assets` is specified, it will be replaced with `'/_svelte_kit_assets'` during `vite dev` or `vite preview`, since the assets don't yet live at their eventual URL. + * + * @deprecated Use `asset(...)` instead */ export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets'; From 96de94bfe2c8db7fcc2ee480bd4397ac45e6204e Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Thu, 12 Jun 2025 10:22:05 -0600 Subject: [PATCH 27/31] Update packages/kit/src/core/sync/write_types/test/app-types/+page.ts Co-authored-by: Rich Harris --- packages/kit/src/core/sync/write_types/test/app-types/+page.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kit/src/core/sync/write_types/test/app-types/+page.ts b/packages/kit/src/core/sync/write_types/test/app-types/+page.ts index f7d0a80d2287..573c18839b92 100644 --- a/packages/kit/src/core/sync/write_types/test/app-types/+page.ts +++ b/packages/kit/src/core/sync/write_types/test/app-types/+page.ts @@ -25,4 +25,5 @@ pathname = '/nope'; pathname = '/foo'; pathname = '/foo/1/2'; +// read `pathname` otherwise it is treated as unused pathname; From 5a30a83345bdabd04923d876f3cff2e5322446dc Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Thu, 12 Jun 2025 10:22:17 -0600 Subject: [PATCH 28/31] Update packages/kit/src/core/sync/write_types/test/app-types/+page.ts Co-authored-by: Rich Harris --- packages/kit/src/core/sync/write_types/test/app-types/+page.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kit/src/core/sync/write_types/test/app-types/+page.ts b/packages/kit/src/core/sync/write_types/test/app-types/+page.ts index 573c18839b92..6da67ea0e75a 100644 --- a/packages/kit/src/core/sync/write_types/test/app-types/+page.ts +++ b/packages/kit/src/core/sync/write_types/test/app-types/+page.ts @@ -9,6 +9,7 @@ id = '/foo/[bar]/[baz]'; // @ts-expect-error id = '/nope'; +// read `id` otherwise it is treated as unused id; declare let params: RouteParams<'/foo/[bar]/[baz]'>; From eac2a0e414f1093337d884c2d97d17250a6186d9 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Mon, 16 Jun 2025 11:55:10 -0600 Subject: [PATCH 29/31] chore: extract regexes into named functions --- .../kit/src/core/sync/write_types/index.js | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index f774ba8ea607..fdba231e2b0a 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -7,6 +7,14 @@ import { compact } from '../../../utils/array.js'; import { ts } from '../ts.js'; import { s } from '../../../utils/misc.js'; +const remove_relative_parent_traversals = (/** @type {string} */ path) => + path.replace(/\.\.\//g, ''); +const replace_optional_params = (/** @type {string} */ id) => + id.replace(/\/\[\[[^\]]+\]\]/g, '${string}'); +const replace_required_params = (/** @type {string} */ id) => + id.replace(/\/\[[^\]]+\]/g, '/${string}'); +const is_whitespace = (/** @type {string} */ char) => /\s/.test(char); + /** * @typedef {{ * file_name: string; @@ -36,7 +44,9 @@ export function write_all_types(config, manifest_data) { const types_dir = `${config.kit.outDir}/types`; // empty out files that no longer need to exist - const routes_dir = posixify(path.relative('.', config.kit.files.routes)).replace(/\.\.\//g, ''); + const routes_dir = remove_relative_parent_traversals( + posixify(path.relative('.', config.kit.files.routes)) + ); const expected_directories = new Set( manifest_data.routes.map((route) => path.join(routes_dir, route.id)) ); @@ -66,9 +76,7 @@ export function write_all_types(config, manifest_data) { dynamic_routes.push(route_type); - pathnames.push( - `\`${route.id.replace(/\/\[\[[^\]]+\]\]/g, '${string}').replace(/\/\[[^\]]+\]/g, '/${string}')}\` & {}` - ); + pathnames.push(`\`${replace_required_params(replace_optional_params(route.id))}\` & {}`); } else { pathnames.push(s(route.id)); } @@ -236,7 +244,9 @@ function create_routes_map(manifest_data) { * @param {Set} [to_delete] */ function update_types(config, routes, route, to_delete = new Set()) { - const routes_dir = posixify(path.relative('.', config.kit.files.routes)).replace(/\.\.\//g, ''); + const routes_dir = remove_relative_parent_traversals( + posixify(path.relative('.', config.kit.files.routes)) + ); const outdir = path.join(config.kit.outDir, 'types', routes_dir, route.id); // now generate new types @@ -793,7 +803,7 @@ export function tweak_types(content, is_server) { if (declaration.type) { let a = declaration.type.pos; const b = declaration.type.end; - while (/\s/.test(content[a])) a += 1; + while (is_whitespace(content[a])) a += 1; const type = content.slice(a, b); code.remove(declaration.name.end, declaration.type.end); @@ -865,7 +875,7 @@ export function tweak_types(content, is_server) { if (declaration.type) { let a = declaration.type.pos; const b = declaration.type.end; - while (/\s/.test(content[a])) a += 1; + while (is_whitespace(content[a])) a += 1; const type = content.slice(a, b); code.remove(declaration.name.end, declaration.type.end); From 5445a9320293c874e1ea5138ba01ddaa470b9792 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 17 Jun 2025 13:09:02 -0400 Subject: [PATCH 30/31] Update documentation/docs/98-reference/20-$app-types.md --- documentation/docs/98-reference/20-$app-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/98-reference/20-$app-types.md b/documentation/docs/98-reference/20-$app-types.md index 478f008fd371..e5a8e0fac7ae 100644 --- a/documentation/docs/98-reference/20-$app-types.md +++ b/documentation/docs/98-reference/20-$app-types.md @@ -52,7 +52,7 @@ type Pathname = '/' | '/my-route' | `/my-other-route/${string}`;
```dts -type Pathname = '/' | '/my-route' | `/my-other-route/${string}`; +type Pathname = `${'' | `/${string}`}/` | `${'' | `/${string}`}/my-route` | `${'' | `/${string}`}/my-other-route/${string} | {}`; ```
From 22b26dc272b4f7c9c448428cec7e89e0dae35ec3 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Wed, 18 Jun 2025 15:30:43 +0800 Subject: [PATCH 31/31] Update documentation/docs/98-reference/20-$app-types.md Co-authored-by: Rich Harris --- documentation/docs/98-reference/20-$app-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/98-reference/20-$app-types.md b/documentation/docs/98-reference/20-$app-types.md index e5a8e0fac7ae..96f1dcaaa94f 100644 --- a/documentation/docs/98-reference/20-$app-types.md +++ b/documentation/docs/98-reference/20-$app-types.md @@ -40,7 +40,7 @@ A union of all valid pathnames in your app.
```dts -type Pathname = '/' | '/my-route' | `/my-other-route/${string}`; +type Pathname = '/' | '/my-route' | `/my-other-route/${string}` & {}; ```