Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/famous-pandas-hammer.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
'@sveltejs/kit': patch
---

fix: remove paths without layout files from `LayoutParams`
fix: exclude routes without a page or endpoint from `routes` in `$app/manifest`, and remove directories with no route files from `LayoutParams`
5 changes: 5 additions & 0 deletions .changeset/routeid-only-real-routes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': major
---

breaking: only include routes with a `+page` or `+server` in `RouteId`
4 changes: 3 additions & 1 deletion documentation/docs/98-reference/22-$app-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,12 @@ type RouteParams<T extends RouteId> = { /* generated */ } | Record<string, never

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.

Unlike `RouteId`, this accepts any directory in `src/routes`, since a layout can live in a directory that has no `+page` or `+server` of its own.

<div class="ts-block">

```dts
type RouteParams<T extends RouteId> = { /* generated */ } | Record<string, never>;
type LayoutParams<T extends '/' | '/my-route' | '/my-other-route'> = { /* generated */ };
```

</div>
14 changes: 11 additions & 3 deletions packages/kit/src/core/sync/create_manifest_data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ export default function create_manifest_data({
};
}

/**
* Whether the router can match this route. `manifest_data.routes` also contains an entry for
* every other directory in `src/routes`, so that layouts and params can be resolved, and those
* ids never reach `event.route.id`.
* @param {import('types').RouteData} route
*/
export function is_app_route(route) {
return !!(route.page || route.endpoint);
}

/**
* Returns a list of files in the `static` directory.
* @param {import('types').ValidatedConfig} config
Expand Down Expand Up @@ -441,9 +451,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
}

// remove route objects with no route file
routes = routes.filter(
(route) => route.page || route.endpoint || route.leaf || route.layout || route.error
);
routes = routes.filter((route) => route.endpoint || route.leaf || route.layout || route.error);

// add parents to error nodes so that we can compute which page options apply to them
for (const route of routes) {
Expand Down
16 changes: 12 additions & 4 deletions packages/kit/src/core/sync/write_app_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { posixify } from '../../utils/os.js';
import { write_if_changed } from './utils.js';
import { s } from '../../utils/misc.js';
import { get_route_segments } from '../../utils/routing.js';
import { is_app_route } from './create_manifest_data/index.js';

const replace_optional_params = (/** @type {string} */ id) =>
id.replace(/\/\[\[[^\]]+\]\]/g, '${string}');
Expand Down Expand Up @@ -142,6 +143,9 @@ function generate_app_types(manifest_data, config, dir) {
/** @type {Set<string>} */
const pathnames = new Set();

/** @type {string[]} */
const app_route_ids = [];

/** @type {string[]} */
const dynamic_routes = [];

Expand Down Expand Up @@ -192,6 +196,10 @@ function generate_app_types(manifest_data, config, dir) {
}

for (const route of manifest_data.routes) {
if (is_app_route(route)) {
app_route_ids.push(s(route.id));
}

const pathname = remove_group_segments(route.id);
let normalized_pathname = pathname.slice(1);

Expand All @@ -203,9 +211,9 @@ function generate_app_types(manifest_data, config, dir) {
const type = get_matcher_type(p.matcher);
return `${/^\w+$/.test(p.name) ? p.name : `'${p.name}'`}${p.optional ? '?:' : ':'} ${type}${p.optional ? ' | undefined' : ''}`;
});
const route_type = `${s(route.id)}: { ${params.join('; ')} }`;

dynamic_routes.push(route_type);
if (is_app_route(route)) {
dynamic_routes.push(`${s(route.id)}: { ${params.join('; ')} }`);
}

normalized_pathname = replace_required_params(replace_optional_params(pathname)).slice(1);
serialise = (p) => `\`${p}\` & {}`;
Expand Down Expand Up @@ -237,7 +245,7 @@ function generate_app_types(manifest_data, config, dir) {
return [
'declare module "$app/types" {',
'\texport interface AppTypes {',
`\t\tRouteId(): ${manifest_data.routes.map((r) => s(r.id)).join(' | ')};`,
`\t\tRouteId(): ${app_route_ids.join(' | ') || 'never'};`,
`\t\tRouteParams(): {\n\t\t\t${dynamic_routes.join(';\n\t\t\t')}\n\t\t};`,
`\t\tLayoutParams(): {\n\t\t\t${layouts.join(';\n\t\t\t')}\n\t\t};`,
`\t\tPath(): ${Array.from(pathnames).join(' | ')};`,
Expand Down
7 changes: 5 additions & 2 deletions packages/kit/src/core/sync/write_types/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { rimraf, walk, resolve_entry } from '../../../utils/filesystem.js';
import { compact } from '../../../utils/array.js';
import { posixify } from '../../../utils/os.js';
import { ts } from '../ts.js';
import { is_app_route } from '../create_manifest_data/index.js';
const remove_relative_parent_traversals = (/** @type {string} */ path) =>
path.replace(/\.\.\//g, '');
const is_whitespace = (/** @type {string} */ char) => /\s/.test(char);
Expand Down Expand Up @@ -278,7 +279,9 @@ function update_types(config, routes, route, root, to_delete = new Set()) {
let all_pages_have_load = true;
/** @type {import('types').RouteParam[]} */
const layout_params = [];
const ids = ['RouteId'];
// a layout can live in a directory that has no `+page` or `+server`, in which case its
// own id is never the matched route
const ids = is_app_route(route) ? ['RouteId'] : [];

route.layout.child_pages?.forEach((page) => {
const leaf = routes.get(page);
Expand Down Expand Up @@ -312,7 +315,7 @@ function update_types(config, routes, route, root, to_delete = new Set()) {
ids.push('null');
}

declarations.push(`type LayoutRouteId = ${ids.join(' | ')}`);
declarations.push(`type LayoutRouteId = ${ids.join(' | ') || 'never'}`);

declarations.push(
'type LayoutParams = RouteParams & ' + generate_params_type(layout_params, outdir, config)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ id = '/foo/[bar]/[baz]';
id = '/(group)/path-a';

// @ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unused-vars
id = '/nope';

// @ts-expect-error `/foo` is a directory with no `+page` or `+server`, so it is not a route
id = '/foo';

// @ts-expect-error a directory with only a `+layout` is not a route
// eslint-disable-next-line @typescript-eslint/no-unused-vars
id = '/(group)/path-a/trailing-slash/always/layout';

/** @type {import('$app/types').RouteParams<'/foo/[bar]/[baz]'>} */
const params = {
bar: 'A',
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/src/exports/vite/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
} from './utils.js';
import { stackless } from '../../utils/error.js';
import { write_client_manifest } from '../../core/sync/write_client_manifest.js';
import { is_app_route } from '../../core/sync/create_manifest_data/index.js';
import prerender from '../../core/postbuild/prerender.js';
import analyse from '../../core/postbuild/analyse.js';
import { s } from '../../utils/misc.js';
Expand Down Expand Up @@ -2127,7 +2128,7 @@ function stringify_assets(assets) {
function stringify_routes(routes) {
return (
routes
?.filter((route) => route.page || route.endpoint || route.leaf)
?.filter(is_app_route)
.map((route) => s({ id: route.id }))
.join(',\n') ?? ''
);
Expand Down
8 changes: 5 additions & 3 deletions packages/kit/src/types/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,12 @@ declare module '$app/types' {

/**
* 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.
*
* Unlike `RouteId`, this accepts any directory in `src/routes`, since a layout can live in a directory that has no `+page` or `+server` of its own.
*/
export type LayoutParams<T extends RouteId> = T extends keyof ReturnType<AppTypes['LayoutParams']>
? ReturnType<AppTypes['LayoutParams']>[T]
: Record<string, never>;
export type LayoutParams<T extends keyof ReturnType<AppTypes['LayoutParams']>> = ReturnType<
AppTypes['LayoutParams']
>[T];

/**
* A union of all valid paths in your app, relative to the `base` path.
Expand Down
8 changes: 5 additions & 3 deletions packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3797,10 +3797,12 @@ declare module '$app/types' {

/**
* 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.
*
* Unlike `RouteId`, this accepts any directory in `src/routes`, since a layout can live in a directory that has no `+page` or `+server` of its own.
*/
export type LayoutParams<T extends RouteId> = T extends keyof ReturnType<AppTypes['LayoutParams']>
? ReturnType<AppTypes['LayoutParams']>[T]
: Record<string, never>;
export type LayoutParams<T extends keyof ReturnType<AppTypes['LayoutParams']>> = ReturnType<
AppTypes['LayoutParams']
>[T];

/**
* A union of all valid paths in your app, relative to the `base` path.
Expand Down