diff --git a/.changeset/famous-bars-search.md b/.changeset/famous-bars-search.md
new file mode 100644
index 000000000000..3e1e8b8af2ab
--- /dev/null
+++ b/.changeset/famous-bars-search.md
@@ -0,0 +1,6 @@
+---
+'@sveltejs/kit': major
+---
+
+breaking: rename `Pathname` type to `Path` and `Asset` to `AssetPath`
+breaking: remove leading `/` from `Path` and `AssetPath`
diff --git a/documentation/docs/98-reference/20-$app-types.md b/documentation/docs/98-reference/20-$app-types.md
index 97053e762869..0663a89ea385 100644
--- a/documentation/docs/98-reference/20-$app-types.md
+++ b/documentation/docs/98-reference/20-$app-types.md
@@ -13,14 +13,14 @@ This module contains generated types for the routes in your app.
import type { RouteId, RouteParams, LayoutParams } from '$app/types';
```
-## Asset
+## AssetPath
-A union of all the filenames of assets contained in your `static` directory, plus a `string` wildcard for asset paths generated from `import` declarations.
+A union of all the filenames of assets contained in your `static` directory, relative to the `base` path.
```dts
-type Asset = '/favicon.png' | '/robots.txt' | (string & {});
+type AssetPath = 'favicon.png' | 'robots.txt' | (string & {});
```
@@ -37,21 +37,21 @@ type RouteId = '/' | '/my-route' | '/my-other-route/[param]';
-## Pathname
+## Path
-A union of all valid pathnames in your app.
+A union of all valid paths in your app, relative to the `base` path.
```dts
-type Pathname = '/' | '/my-route' | `/my-other-route/${string}` & {};
+type Path = '' | 'my-route' | `my-other-route/${string}` & {};
```
## ResolvedPathname
-Similar to `Pathname`, but possibly prefixed with a [base path](configuration#paths). Used for `page.url.pathname`.
+Similar to `Path`, but prefixed with a [base path](configuration#paths). Used for `page.url.pathname`.
diff --git a/packages/kit/src/core/sync/write_non_ambient.js b/packages/kit/src/core/sync/write_non_ambient.js
index 9dd4126aab83..3010cc281f7f 100644
--- a/packages/kit/src/core/sync/write_non_ambient.js
+++ b/packages/kit/src/core/sync/write_non_ambient.js
@@ -22,10 +22,6 @@ const remove_group_segments = (/** @type {string} */ id) => {
* @returns {string[]}
*/
function get_pathnames_for_trailing_slash(pathname, route) {
- if (pathname === '/') {
- return [pathname];
- }
-
/** @type {Set
} */
const pathnames = new Set();
@@ -201,7 +197,7 @@ function generate_app_types(manifest_data, config) {
for (const route of manifest_data.routes) {
const pathname = remove_group_segments(route.id);
- let normalized_pathname = pathname;
+ let normalized_pathname = pathname.slice(1);
/** @type {(path: string) => string} */
let serialise = s;
@@ -215,7 +211,7 @@ function generate_app_types(manifest_data, config) {
dynamic_routes.push(route_type);
- normalized_pathname = replace_required_params(replace_optional_params(pathname));
+ normalized_pathname = replace_required_params(replace_optional_params(pathname)).slice(1);
serialise = (p) => `\`${p}\` & {}`;
}
@@ -240,7 +236,7 @@ function generate_app_types(manifest_data, config) {
layouts.push(`${s(route.id)}: ${layout_type}`);
}
- const assets = manifest_data.assets.map((asset) => s('/' + asset.file));
+ const assets = manifest_data.assets.map((asset) => s(asset.file));
return [
'declare module "$app/types" {',
@@ -248,9 +244,9 @@ function generate_app_types(manifest_data, config) {
`\t\tRouteId(): ${manifest_data.routes.map((r) => s(r.id)).join(' | ')};`,
`\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\tPathname(): ${Array.from(pathnames).join(' | ')};`,
- '\t\tResolvedPathname(): `${"" | `/${string}`}${ReturnType}`;',
- `\t\tAsset(): ${assets.concat('string & {}').join(' | ')};`,
+ `\t\tPath(): ${Array.from(pathnames).join(' | ')};`,
+ '\t\tResolvedPathname(): `${"/" | `/${string}/`}${ReturnType}`;',
+ `\t\tAssetPath(): ${assets.join(' | ') || 'never'};`,
'\t}',
'}'
].join('\n');
diff --git a/packages/kit/src/core/sync/write_types/test/app-types/+page.js b/packages/kit/src/core/sync/write_types/test/app-types/+page.js
index 2bbf8cfe3455..ee5b3a316821 100644
--- a/packages/kit/src/core/sync/write_types/test/app-types/+page.js
+++ b/packages/kit/src/core/sync/write_types/test/app-types/+page.js
@@ -67,44 +67,44 @@ const matcherParentLayoutParams = {};
matcherParentLayoutParams.locale = 'fr'; // any string
-/** @type {import('$app/types').Pathname} */
+/** @type {import('$app/types').Path} */
let pathname;
// @ts-expect-error route doesn't exist
-pathname = '/nope';
+pathname = 'nope';
// @ts-expect-error route doesn't exist
-pathname = '/foo';
+pathname = 'foo';
// @ts-expect-error route doesn't exist
-pathname = '/foo/';
-pathname = '/foo/1/2'; // okay
-pathname = '/foo/1/2/'; // okay
+pathname = 'foo/';
+pathname = 'foo/1/2'; // okay
+pathname = 'foo/1/2/'; // okay
// Test layout groups
-pathname = '/path-a';
+pathname = 'path-a';
// @ts-expect-error default trailing slash is never, so we should not have it here
-pathname = '/path-a/';
+pathname = 'path-a/';
// @ts-expect-error layout group names are NOT part of the pathname type
-pathname = '/(group)/path-a';
+pathname = '(group)/path-a';
// Test trailing-slash - always
-pathname = '/path-a/trailing-slash/always/';
-pathname = '/path-a/trailing-slash/always/endpoint/';
-pathname = '/path-a/trailing-slash/always/layout/inside/';
+pathname = 'path-a/trailing-slash/always/';
+pathname = 'path-a/trailing-slash/always/endpoint/';
+pathname = 'path-a/trailing-slash/always/layout/inside/';
// Test trailing-slash - ignore
-pathname = '/path-a/trailing-slash/ignore';
-pathname = '/path-a/trailing-slash/ignore/';
-pathname = '/path-a/trailing-slash/ignore/endpoint';
-pathname = '/path-a/trailing-slash/ignore/endpoint/';
-pathname = '/path-a/trailing-slash/ignore/layout/inside';
-pathname = '/path-a/trailing-slash/ignore/layout/inside/';
+pathname = 'path-a/trailing-slash/ignore';
+pathname = 'path-a/trailing-slash/ignore/';
+pathname = 'path-a/trailing-slash/ignore/endpoint';
+pathname = 'path-a/trailing-slash/ignore/endpoint/';
+pathname = 'path-a/trailing-slash/ignore/layout/inside';
+pathname = 'path-a/trailing-slash/ignore/layout/inside/';
// Test trailing-slash - never (default)
-pathname = '/path-a/trailing-slash/never';
-pathname = '/path-a/trailing-slash/never/endpoint';
-pathname = '/path-a/trailing-slash/never/layout/inside';
+pathname = 'path-a/trailing-slash/never';
+pathname = 'path-a/trailing-slash/never/endpoint';
+pathname = 'path-a/trailing-slash/never/layout/inside';
// Test trailing-slash - always (endpoint) and never (page)
-pathname = '/path-a/trailing-slash/mixed';
+pathname = 'path-a/trailing-slash/mixed';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
-pathname = '/path-a/trailing-slash/mixed/';
+pathname = 'path-a/trailing-slash/mixed/';
diff --git a/packages/kit/src/runtime/app/paths/client.js b/packages/kit/src/runtime/app/paths/client.js
index 53eb2a7d2fda..1da65040b6b3 100644
--- a/packages/kit/src/runtime/app/paths/client.js
+++ b/packages/kit/src/runtime/app/paths/client.js
@@ -1,8 +1,9 @@
-/** @import { Asset, RouteId, RouteIdWithSearchOrHash, Pathname, PathnameWithSearchOrHash, ResolvedPathname, RouteParams } from '$app/types' */
+/** @import { AssetPath, RouteId, RouteIdWithSearchOrHash, Path, PathnameWithSearchOrHash, ResolvedPathname, RouteParams } from '$app/types' */
/** @import { ResolveArgs } from './types.js' */
import { base, assets, hash_routing } from './internal/client.js';
import { resolve_route } from '../../../utils/routing.js';
import { get_navigation_intent } from '../../client/client.js';
+import { DEV } from 'esm-env';
/**
* Resolve the URL of an asset in your `static` directory, by prefixing it with [`config.paths.assets`](https://svelte.dev/docs/kit/configuration#paths) if configured, or otherwise by prefixing it with the base path.
@@ -15,15 +16,24 @@ import { get_navigation_intent } from '../../client/client.js';
* import { asset } from '$app/paths';
*
*
- *
+ *
* ```
* @since 2.26
*
- * @param {Asset} file
+ * @param {AssetPath} file
* @returns {string}
*/
export function asset(file) {
- return (assets || base) + file;
+ // TODO 4.0 remove this
+ if (file[0] === '/') {
+ if (DEV) {
+ console.warn(`\`asset('${file}')\` should now be \`asset('${file.slice(1)}')\``);
+ }
+
+ file = file.slice(1);
+ }
+
+ return (assets || base) + '/' + file;
}
const pathname_prefix = hash_routing ? '#' : '';
@@ -38,7 +48,7 @@ const pathname_prefix = hash_routing ? '#' : '';
* import { resolve } from '$app/paths';
*
* // using a pathname
- * const resolved = resolve(`/blog/hello-world`);
+ * const resolved = resolve(`blog/hello-world`);
*
* // using a route ID plus parameters
* const resolved = resolve('/blog/[slug]', {
@@ -52,14 +62,18 @@ const pathname_prefix = hash_routing ? '#' : '';
* @returns {ResolvedPathname}
*/
export function resolve(...args) {
- if (!args[0].startsWith('/')) {
- throw new Error(
- `Cannot use \`resolve(...)\` with a non-absolute pathname or route ID (got "${args[0]}"). ` +
- '`resolve` is only for internal pathnames and route IDs; external URLs should be used directly.'
- );
+ if (args[0][0] === '/') {
+ const [id, params] = args;
+
+ // route ID
+ if (id.includes('[') && !params) {
+ throw new Error(`Missing params for dynamic route ID ${id}`);
+ }
+
+ return base + pathname_prefix + resolve_route(args[0], args[1] ?? {});
}
- return base + pathname_prefix + resolve_route(args[0], args[1] ?? {});
+ return base + pathname_prefix + '/' + args[0];
}
/**
@@ -69,7 +83,7 @@ export function resolve(...args) {
* ```js
* import { match } from '$app/paths';
*
- * const route = await match('/blog/hello-world');
+ * const route = await match('blog/hello-world');
*
* if (route?.id === '/blog/[slug]') {
* const slug = route.params.slug;
@@ -79,7 +93,7 @@ export function resolve(...args) {
* ```
* @since 2.52.0
*
- * @param {Pathname | URL | (string & {})} url
+ * @param {Path | URL | (string & {})} url
* @returns {Promise<{ [K in RouteId]: { id: K; params: RouteParams; } }[RouteId] | null>}
*/
export async function match(url) {
diff --git a/packages/kit/src/runtime/app/paths/server.js b/packages/kit/src/runtime/app/paths/server.js
index cc559123eb4d..863f0629768d 100644
--- a/packages/kit/src/runtime/app/paths/server.js
+++ b/packages/kit/src/runtime/app/paths/server.js
@@ -5,23 +5,36 @@ import { add_data_suffix } from '../../pathname.js';
import { try_get_request_store } from '@sveltejs/kit/internal/server';
import { manifest } from '../../server/internal.js';
import { get_hooks } from '__SERVER__/internal.js';
+import { DEV } from 'esm-env';
/** @type {import('./client.js').asset} */
export function asset(file) {
- // @ts-expect-error we use the `resolve` mechanism, but with the 'wrong' input
- return assets && assets !== base ? assets + file : resolve(file);
+ // TODO 4.0 remove this
+ if (file[0] === '/') {
+ if (DEV) {
+ console.warn(`\`asset('${file}')\` should now be \`asset('${file.slice(1)}')\``);
+ }
+
+ file = file.slice(1);
+ }
+
+ return assets !== base ? `${assets}/${file}` : resolve(file);
}
/** @type {import('./client.js').resolve} */
export function resolve(id, params) {
- if (!id.startsWith('/')) {
- throw new Error(
- `Cannot use \`resolve(...)\` with a non-absolute pathname or route ID (got "${id}"). ` +
- '`resolve` is only for internal pathnames and route IDs; external URLs should be used directly.'
- );
- }
+ let resolved;
+
+ if (id[0] === '/') {
+ // route ID
+ if (id.includes('[') && !params) {
+ throw new Error(`Missing params for dynamic route ID ${id}`);
+ }
- const resolved = resolve_route(id, params ?? {});
+ resolved = resolve_route(id, params ?? {});
+ } else {
+ resolved = '/' + id;
+ }
if (relative) {
const store = try_get_request_store();
diff --git a/packages/kit/src/runtime/app/paths/types.d.ts b/packages/kit/src/runtime/app/paths/types.d.ts
index f8bad8f8cf21..9301985fb447 100644
--- a/packages/kit/src/runtime/app/paths/types.d.ts
+++ b/packages/kit/src/runtime/app/paths/types.d.ts
@@ -1,23 +1,15 @@
-import {
- PathnameWithSearchOrHash,
- RouteId,
- RouteIdWithSearchOrHash,
- RouteParams
-} from '$app/types';
+import { RouteId, RouteParams } from '$app/types';
-type StripSearchOrHash = T extends `${infer Pathname}?${string}`
- ? Pathname
- : T extends `${infer Pathname}#${string}`
- ? Pathname
+type StripSearchOrHash = T extends `${infer U}?${string}`
+ ? U
+ : T extends `${infer U}#${string}`
+ ? U
: T;
-export type ResolveArgs =
- T extends RouteId
- ? RouteParams extends Record
+export type ResolveArgs = T extends `/${string}`
+ ? StripSearchOrHash extends infer U extends RouteId
+ ? RouteParams extends Record
? [route: T]
- : [route: T, params: RouteParams]
- : StripSearchOrHash extends infer U extends RouteId
- ? RouteParams extends Record
- ? [route: T]
- : [route: T, params: RouteParams]
- : [route: T];
+ : [route: T, params: RouteParams]
+ : [never]
+ : [pathname: T];
diff --git a/packages/kit/src/types/ambient.d.ts b/packages/kit/src/types/ambient.d.ts
index d01f3b4a426d..ddb218ac68d4 100644
--- a/packages/kit/src/types/ambient.d.ts
+++ b/packages/kit/src/types/ambient.d.ts
@@ -99,9 +99,9 @@ declare module '$app/types' {
RouteId(): string;
RouteParams(): Record>;
LayoutParams(): Record>;
- Pathname(): string;
+ Path(): string;
ResolvedPathname(): string;
- Asset(): string;
+ AssetPath(): string;
}
/**
@@ -129,25 +129,22 @@ declare module '$app/types' {
: Record;
/**
- * A union of all valid pathnames in your app.
+ * A union of all valid paths in your app, relative to the `base` path.
*/
- export type Pathname = ReturnType;
+ export type Path = ReturnType;
/**
- * `Pathname`, but possibly suffixed with a search string and/or hash.
+ * `Path`, but possibly suffixed with a search string and/or hash.
*/
- export type PathnameWithSearchOrHash =
- | Pathname
- | `${Pathname}?${string}`
- | `${Pathname}#${string}`;
+ export type PathnameWithSearchOrHash = Path | `${Path}?${string}` | `${Path}#${string}`;
/**
- * `Pathname`, but possibly prefixed with a base path. Used for `page.url.pathname`.
+ * `Path`, but prefixed with a base path. Used for `page.url.pathname`.
*/
export type ResolvedPathname = ReturnType;
/**
- * A union of all the filenames of assets contained in your `static` directory.
+ * A union of all the filenames of assets contained in your `static` directory, relative to the `base` path.
*/
- export type Asset = ReturnType;
+ export type AssetPath = ReturnType;
}
diff --git a/packages/kit/test/apps/options-2/src/routes/+error.svelte b/packages/kit/test/apps/options-2/src/routes/+error.svelte
index c8320ab59160..e62e63708643 100644
--- a/packages/kit/test/apps/options-2/src/routes/+error.svelte
+++ b/packages/kit/test/apps/options-2/src/routes/+error.svelte
@@ -5,5 +5,5 @@
{page.status}
-base: {resolve('/')}
-assets: {asset('/')}
+base: {resolve('')}
+assets: {asset('answer.txt').replace('answer.txt', '')}
diff --git a/packages/kit/test/apps/options-2/src/routes/+page.svelte b/packages/kit/test/apps/options-2/src/routes/+page.svelte
index 33b8352a72f8..823bce98e12f 100644
--- a/packages/kit/test/apps/options-2/src/routes/+page.svelte
+++ b/packages/kit/test/apps/options-2/src/routes/+page.svelte
@@ -4,8 +4,8 @@
Hello
-base: {resolve('/')}
-assets: {asset('/')}
+base: {resolve('')}
+assets: {asset('answer.txt').replace('answer.txt', '')}
Go to /hello
diff --git a/packages/kit/test/apps/options-2/src/routes/deeply/nested/page/+page.svelte b/packages/kit/test/apps/options-2/src/routes/deeply/nested/page/+page.svelte
index f645edd52e3d..6c2701fa0f46 100644
--- a/packages/kit/test/apps/options-2/src/routes/deeply/nested/page/+page.svelte
+++ b/packages/kit/test/apps/options-2/src/routes/deeply/nested/page/+page.svelte
@@ -4,8 +4,8 @@
Hello
-base: {resolve('/')}
-assets: {asset('/')}
+base: {resolve('')}
+assets: {asset('answer.txt').replace('answer.txt', '')}