Skip to content

Commit 751337e

Browse files
committed
feat: add optional params for hash and searchParams to resolve()
Adds optional arguments for URL params `hash` and `searchParams` to `resolve()`. Appends these to the returned URL after resolving the path. Resolves #14750 and helps address sveltejs/eslint-plugin-svelte#1353 feat: make ResolveParams accept `searchParams: Record<string, string> | URLSearchParams | undefined` fix: better arg parsing
1 parent 9d8489a commit 751337e

File tree

5 files changed

+122
-15
lines changed

5 files changed

+122
-15
lines changed

.changeset/wicked-brooms-like.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@sveltejs/kit': minor
3+
---
4+
5+
Adds optional arguments for URL params `hash` and `searchParams` to `resolve()` from `$app/paths`.
6+
Appends these to the returned URL after resolving the path.

packages/kit/src/runtime/app/paths/client.js

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ export function asset(file) {
2626
}
2727

2828
/**
29-
* Resolve a pathname by prefixing it with the base path, if any, or resolve a route ID by populating dynamic segments with parameters.
29+
* Resolve a pathname by prefixing it with the base path, if any, or resolve a route ID by populating dynamic segments with route parameters.
30+
* Optionally accepts URL parameters and appends these to the resolved route.
3031
*
3132
* During server rendering, the base path is relative and depends on the page currently being rendered.
3233
*
@@ -37,10 +38,26 @@ export function asset(file) {
3738
* // using a pathname
3839
* const resolved = resolve(`/blog/hello-world`);
3940
*
40-
* // using a route ID plus parameters
41+
* // using a route ID plus route parameters
4142
* const resolved = resolve('/blog/[slug]', {
4243
* slug: 'hello-world'
4344
* });
45+
*
46+
* // using a route ID plus URL parameters as Record
47+
* const resolved = resolve('/blog/search',
48+
* { hash: 'results', searchParams: { author: 'John Doe', year: '2025' } }
49+
* });
50+
*
51+
* // using a route ID plus URL parameters as URLSearchParams
52+
* const resolved = resolve('/blog/search',
53+
* { hash: 'results', searchParams: new URLSearchParams({ author: 'John Doe', year: '2025' }) }
54+
* });
55+
*
56+
* // using a route ID plus route parameters and URL parameters
57+
* const resolved = resolve('/blog/[slug]',
58+
* { slug: 'hello-world' },
59+
* { hash: 'introduction' }
60+
* });
4461
* ```
4562
* @since 2.26
4663
*
@@ -49,9 +66,58 @@ export function asset(file) {
4966
* @returns {ResolvedPathname}
5067
*/
5168
export function resolve(...args) {
69+
// args[0] is always the route ID or pathname
70+
const routeID = /** @type {string} */ (args[0]);
71+
72+
/** @type {Record<string, string> | undefined} */
73+
let routeParams;
74+
75+
/** @type {{ searchParams?: Record<string, string> | URLSearchParams; hash?: string } | undefined} */
76+
let urlParams;
77+
78+
// Determine if args[1] is route params or URL params
79+
if (args.length === 2) {
80+
const searchParamsOrURLParams = args[1];
81+
// If args[1] is actually undefined, we don't need to do anything
82+
if (searchParamsOrURLParams) {
83+
if ('searchParams' in searchParamsOrURLParams || 'hash' in searchParamsOrURLParams) {
84+
// It's URL params
85+
urlParams = searchParamsOrURLParams;
86+
} else {
87+
// Otherwise, it's route params
88+
routeParams = /** @type {Record<string, string>} */ (searchParamsOrURLParams);
89+
}
90+
}
91+
} else if (args.length === 3) {
92+
// args[1] is route params, args[2] is URL params
93+
routeParams = args[1];
94+
urlParams = args[2];
95+
}
96+
5297
// The type error is correct here, and if someone doesn't pass params when they should there's a runtime error,
53-
// but we don't want to adjust the internal resolve_route function to accept `undefined`, hence the type cast.
54-
return base + resolve_route(args[0], /** @type {Record<string, string>} */ (args[1]));
55-
}
98+
// but we don't want to adjust the internal resolve_route function to accept undefined, hence the type cast.
99+
let resolvedPath =
100+
base + resolve_route(routeID, /** @type {Record<string, string>} */ (routeParams));
101+
102+
// Append searchParams and hash if provided. These do not affect route resolving.
103+
if (urlParams?.searchParams) {
104+
const { searchParams } = urlParams;
56105

106+
if (searchParams instanceof URLSearchParams) {
107+
resolvedPath += `?${searchParams.toString()}`;
108+
} else {
109+
const query = new URLSearchParams();
110+
for (const [key, value] of Object.entries(searchParams)) {
111+
query.append(key, value);
112+
}
113+
resolvedPath += `?${query.toString()}`;
114+
}
115+
}
116+
117+
if (urlParams?.hash) {
118+
resolvedPath += `#${urlParams.hash}`;
119+
}
120+
121+
return resolvedPath;
122+
}
57123
export { base, assets, resolve as resolveRoute };
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { Pathname, RouteId, RouteParams } from '$app/types';
1+
import { Pathname, ResolveURLParams, RouteId, RouteParams } from '$app/types';
22

33
export type ResolveArgs<T extends RouteId | Pathname> = T extends RouteId
44
? RouteParams<T> extends Record<string, never>
5-
? [route: T]
6-
: [route: T, params: RouteParams<T>]
7-
: [route: T];
5+
? [route: T, options?: ResolveURLParams]
6+
: [route: T, params: RouteParams<T>, options?: ResolveURLParams]
7+
: [route: T, options?: ResolveURLParams];

packages/kit/src/types/ambient.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,15 @@ declare module '$app/types' {
111111
? ReturnType<AppTypes['RouteParams']>[T]
112112
: Record<string, never>;
113113

114+
/**
115+
* URL Parameters that can be optionally passed to the `resolve` function.
116+
* Used to specify a hash and/or search parameters to be included in the resolved URL.
117+
*/
118+
export type ResolveURLParams = {
119+
hash?: string;
120+
searchParams?: Record<string, string> | URLSearchParams;
121+
};
122+
114123
/**
115124
* 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.
116125
*/

packages/kit/types/index.d.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3043,7 +3043,7 @@ declare module '$app/navigation' {
30433043
}
30443044

30453045
declare module '$app/paths' {
3046-
import type { RouteId, Pathname, ResolvedPathname, RouteParams, Asset } from '$app/types';
3046+
import type { RouteId, Pathname, ResolvedPathname, ResolveURLParams, RouteParams, Asset } from '$app/types';
30473047
/**
30483048
* A string that matches [`config.kit.paths.base`](https://svelte.dev/docs/kit/configuration#paths).
30493049
*
@@ -3070,9 +3070,9 @@ declare module '$app/paths' {
30703070
): ResolvedPathname;
30713071
type ResolveArgs<T extends RouteId | Pathname> = T extends RouteId
30723072
? RouteParams<T> extends Record<string, never>
3073-
? [route: T]
3074-
: [route: T, params: RouteParams<T>]
3075-
: [route: T];
3073+
? [route: T, options?: ResolveURLParams]
3074+
: [route: T, params: RouteParams<T>, options?: ResolveURLParams]
3075+
: [route: T, options?: ResolveURLParams];
30763076
/**
30773077
* 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.
30783078
*
@@ -3091,7 +3091,8 @@ declare module '$app/paths' {
30913091
* */
30923092
export function asset(file: Asset): string;
30933093
/**
3094-
* Resolve a pathname by prefixing it with the base path, if any, or resolve a route ID by populating dynamic segments with parameters.
3094+
* Resolve a pathname by prefixing it with the base path, if any, or resolve a route ID by populating dynamic segments with route parameters.
3095+
* Optionally accepts URL parameters and appends these to the resolved route.
30953096
*
30963097
* During server rendering, the base path is relative and depends on the page currently being rendered.
30973098
*
@@ -3102,10 +3103,26 @@ declare module '$app/paths' {
31023103
* // using a pathname
31033104
* const resolved = resolve(`/blog/hello-world`);
31043105
*
3105-
* // using a route ID plus parameters
3106+
* // using a route ID plus route parameters
31063107
* const resolved = resolve('/blog/[slug]', {
31073108
* slug: 'hello-world'
31083109
* });
3110+
*
3111+
* // using a route ID plus URL parameters as Record
3112+
* const resolved = resolve('/blog/search',
3113+
* { hash: 'results', searchParams: { author: 'John Doe', year: '2025' } }
3114+
* });
3115+
*
3116+
* // using a route ID plus URL parameters as URLSearchParams
3117+
* const resolved = resolve('/blog/search',
3118+
* { hash: 'results', searchParams: new URLSearchParams({ author: 'John Doe', year: '2025' }) }
3119+
* });
3120+
*
3121+
* // using a route ID plus route parameters and URL parameters
3122+
* const resolved = resolve('/blog/[slug]',
3123+
* { slug: 'hello-world' },
3124+
* { hash: 'introduction' }
3125+
* });
31093126
* ```
31103127
* @since 2.26
31113128
*
@@ -3481,6 +3498,15 @@ declare module '$app/types' {
34813498
? ReturnType<AppTypes['RouteParams']>[T]
34823499
: Record<string, never>;
34833500

3501+
/**
3502+
* URL Parameters that can be optionally passed to the `resolve` function.
3503+
* Used to specify a hash and/or search parameters to be included in the resolved URL.
3504+
*/
3505+
export type ResolveURLParams = {
3506+
hash?: string;
3507+
searchParams?: Record<string, string> | URLSearchParams;
3508+
};
3509+
34843510
/**
34853511
* 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.
34863512
*/

0 commit comments

Comments
 (0)