Skip to content

Commit da2e1b1

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 da2e1b1

File tree

5 files changed

+123
-15
lines changed

5 files changed

+123
-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: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ 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,
30+
* or resolve a route ID by populating dynamic segments with route parameters.
31+
* Optionally accepts URL parameters and appends these to the resolved route.
3032
*
3133
* During server rendering, the base path is relative and depends on the page currently being rendered.
3234
*
@@ -37,10 +39,26 @@ export function asset(file) {
3739
* // using a pathname
3840
* const resolved = resolve(`/blog/hello-world`);
3941
*
40-
* // using a route ID plus parameters
42+
* // using a route ID plus route parameters
4143
* const resolved = resolve('/blog/[slug]', {
4244
* slug: 'hello-world'
4345
* });
46+
*
47+
* // using a route ID plus URL parameters as Record
48+
* const resolved = resolve('/blog/search',
49+
* { hash: 'results', searchParams: { author: 'John Doe', year: '2025' } }
50+
* });
51+
*
52+
* // using a route ID plus URL parameters as URLSearchParams
53+
* const resolved = resolve('/blog/search',
54+
* { hash: 'results', searchParams: new URLSearchParams({ author: 'John Doe', year: '2025' }) }
55+
* });
56+
*
57+
* // using a route ID plus route parameters and URL parameters
58+
* const resolved = resolve('/blog/[slug]',
59+
* { slug: 'hello-world' },
60+
* { hash: 'introduction' }
61+
* });
4462
* ```
4563
* @since 2.26
4664
*
@@ -49,9 +67,58 @@ export function asset(file) {
4967
* @returns {ResolvedPathname}
5068
*/
5169
export function resolve(...args) {
70+
// args[0] is always the route ID or pathname
71+
const routeID = /** @type {string} */ (args[0]);
72+
73+
/** @type {Record<string, string> | undefined} */
74+
let routeParams;
75+
76+
/** @type {{ searchParams?: Record<string, string> | URLSearchParams; hash?: string } | undefined} */
77+
let urlParams;
78+
79+
// Determine if args[1] is route params or URL params
80+
if (args.length === 2) {
81+
const searchParamsOrURLParams = args[1];
82+
// If args[1] is actually undefined, we don't need to do anything
83+
if (searchParamsOrURLParams) {
84+
if ('searchParams' in searchParamsOrURLParams || 'hash' in searchParamsOrURLParams) {
85+
// It's URL params
86+
urlParams = searchParamsOrURLParams;
87+
} else {
88+
// Otherwise, it's route params
89+
routeParams = /** @type {Record<string, string>} */ (searchParamsOrURLParams);
90+
}
91+
}
92+
} else if (args.length === 3) {
93+
// args[1] is route params, args[2] is URL params
94+
routeParams = args[1];
95+
urlParams = args[2];
96+
}
97+
5298
// 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-
}
99+
// but we don't want to adjust the internal resolve_route function to accept undefined, hence the type cast.
100+
let resolvedPath =
101+
base + resolve_route(routeID, /** @type {Record<string, string>} */ (routeParams));
102+
103+
// Append searchParams and hash if provided. These do not affect route resolving.
104+
if (urlParams?.searchParams) {
105+
const { searchParams } = urlParams;
56106

107+
if (searchParams instanceof URLSearchParams) {
108+
resolvedPath += `?${searchParams.toString()}`;
109+
} else {
110+
const query = new URLSearchParams();
111+
for (const [key, value] of Object.entries(searchParams)) {
112+
query.append(key, value);
113+
}
114+
resolvedPath += `?${query.toString()}`;
115+
}
116+
}
117+
118+
if (urlParams?.hash) {
119+
resolvedPath += `#${urlParams.hash}`;
120+
}
121+
122+
return resolvedPath;
123+
}
57124
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)