sv-router 0.4.0 â 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -27,9 +27,15 @@ A feature-rich yet intuitive routing library for Svelte single-page apps.
|
|
|
27
27
|
- ðŠķ **Lightweight**: Minimal impact on your bundle size.
|
|
28
28
|
- ð **Made for Svelte 5**: True Svelte 5 implementation with the latest features.
|
|
29
29
|
|
|
30
|
-
##
|
|
30
|
+
## Getting Started
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Kickstart a new project:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm create sv-router
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
...or add it to an existing project:
|
|
33
39
|
|
|
34
40
|
```bash
|
|
35
41
|
npm install sv-router
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sv-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Modern Svelte Routing",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -36,29 +36,31 @@
|
|
|
36
36
|
"esm-env": "^1.2.2"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@changesets/cli": "^2.
|
|
40
|
-
"@eslint/js": "^9.
|
|
41
|
-
"@types/node": "^22.
|
|
42
|
-
"eslint": "^9.
|
|
43
|
-
"eslint-config-prettier": "^10.1.
|
|
39
|
+
"@changesets/cli": "^2.29.3",
|
|
40
|
+
"@eslint/js": "^9.26.0",
|
|
41
|
+
"@types/node": "^22.15.15",
|
|
42
|
+
"eslint": "^9.26.0",
|
|
43
|
+
"eslint-config-prettier": "^10.1.3",
|
|
44
44
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
45
|
-
"eslint-plugin-svelte": "^3.
|
|
46
|
-
"eslint-plugin-unicorn": "^
|
|
47
|
-
"globals": "^16.
|
|
45
|
+
"eslint-plugin-svelte": "^3.5.1",
|
|
46
|
+
"eslint-plugin-unicorn": "^59.0.1",
|
|
47
|
+
"globals": "^16.1.0",
|
|
48
48
|
"prettier": "^3.5.3",
|
|
49
49
|
"prettier-plugin-jsdoc": "^1.3.2",
|
|
50
50
|
"prettier-plugin-svelte": "^3.3.3",
|
|
51
|
-
"svelte-check": "^4.1.
|
|
51
|
+
"svelte-check": "^4.1.7",
|
|
52
52
|
"type-testing": "^0.2.0",
|
|
53
|
-
"typescript": "^5.8.
|
|
54
|
-
"typescript-eslint": "^8.
|
|
55
|
-
"vite": "^6.
|
|
56
|
-
"vitest": "^3.
|
|
53
|
+
"typescript": "^5.8.3",
|
|
54
|
+
"typescript-eslint": "^8.32.0",
|
|
55
|
+
"vite": "^6.3.5",
|
|
56
|
+
"vitest": "^3.1.3"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"svelte": "^5"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
|
+
"create:dev": "pnpm --filter create-sv-router dev",
|
|
63
|
+
"create:build": "pnpm --filter create-sv-router build",
|
|
62
64
|
"docs:dev": "pnpm --filter docs dev",
|
|
63
65
|
"docs:build": "pnpm --filter docs build",
|
|
64
66
|
"docs:preview": "pnpm --filter docs preview",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BROWSER, DEV } from 'esm-env';
|
|
2
2
|
import { isActive } from './helpers/is-active.js';
|
|
3
3
|
import { matchRoute } from './helpers/match-route.js';
|
|
4
|
-
import { preloadOnHover } from './helpers/preload
|
|
4
|
+
import { preload, preloadOnHover } from './helpers/preload.js';
|
|
5
5
|
import { constructPath, join, resolveRouteComponents } from './helpers/utils.js';
|
|
6
6
|
import { syncSearchParams } from './search-params.svelte.js';
|
|
7
7
|
|
|
@@ -44,6 +44,9 @@ export function createRouter(r) {
|
|
|
44
44
|
p: constructPath,
|
|
45
45
|
navigate,
|
|
46
46
|
isActive,
|
|
47
|
+
async preload(pathname) {
|
|
48
|
+
await preload(routes, pathname);
|
|
49
|
+
},
|
|
47
50
|
route: {
|
|
48
51
|
get params() {
|
|
49
52
|
return params.value;
|
|
@@ -163,7 +163,7 @@ export function createRouterCode(routes, routesPath, { allLazy = false } = {}) {
|
|
|
163
163
|
`import { createRouter } from 'sv-router';`,
|
|
164
164
|
...imports,
|
|
165
165
|
'',
|
|
166
|
-
`export const { p, navigate, isActive, route } = createRouter(${stringifiedRoutes});`,
|
|
166
|
+
`export const { p, navigate, isActive, preload, route } = createRouter(${stringifiedRoutes});`,
|
|
167
167
|
].join('\n');
|
|
168
168
|
}
|
|
169
169
|
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import { matchRoute } from './match-route.js';
|
|
2
2
|
import { resolveRouteComponents } from './utils.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @param {import('../index.js').Routes} routes
|
|
6
|
+
* @param {string} path
|
|
7
|
+
*/
|
|
8
|
+
export async function preload(routes, path) {
|
|
9
|
+
const { match, layouts, hooks } = matchRoute(path, routes);
|
|
10
|
+
for (const { onPreload } of hooks) {
|
|
11
|
+
onPreload?.();
|
|
12
|
+
}
|
|
13
|
+
await resolveRouteComponents(match ? [...layouts, match] : layouts);
|
|
14
|
+
}
|
|
15
|
+
|
|
4
16
|
const linkSet = new Set();
|
|
5
17
|
|
|
6
|
-
/** @param {import('../index.
|
|
18
|
+
/** @param {import('../index.js').Routes} routes */
|
|
7
19
|
export function preloadOnHover(routes) {
|
|
8
20
|
const observer = new MutationObserver(() => {
|
|
9
21
|
const links = document.querySelectorAll('a[data-preload]');
|
|
@@ -15,8 +27,7 @@ export function preloadOnHover(routes) {
|
|
|
15
27
|
link.removeEventListener('mouseenter', callback);
|
|
16
28
|
const href = link.getAttribute('href');
|
|
17
29
|
if (!href) return;
|
|
18
|
-
|
|
19
|
-
resolveRouteComponents(match ? [...layouts, match] : layouts);
|
|
30
|
+
preload(routes, href);
|
|
20
31
|
});
|
|
21
32
|
}
|
|
22
33
|
});
|
package/src/index.d.ts
CHANGED
|
@@ -7,10 +7,11 @@ import type { Action } from 'svelte/action';
|
|
|
7
7
|
* to `is-active`.
|
|
8
8
|
*
|
|
9
9
|
* ```svelte
|
|
10
|
-
* <a href=
|
|
10
|
+
* <a href={p('/about')} use:isActiveLink={{ className: 'active-link' }}>
|
|
11
11
|
* ```
|
|
12
12
|
*/
|
|
13
13
|
export const isActiveLink: IsActiveLink;
|
|
14
|
+
|
|
14
15
|
/**
|
|
15
16
|
* Setup a new router instance with the given routes.
|
|
16
17
|
*
|
|
@@ -23,11 +24,13 @@ export const isActiveLink: IsActiveLink;
|
|
|
23
24
|
* ```
|
|
24
25
|
*/
|
|
25
26
|
export function createRouter<T extends Routes>(r: T): RouterApi<T>;
|
|
27
|
+
|
|
26
28
|
/**
|
|
27
29
|
* The component that will render the current route. You can pass a `base` prop to set the base path
|
|
28
30
|
* that is prepended to every url.
|
|
29
31
|
*/
|
|
30
32
|
export const Router: Component<{ base?: string }>;
|
|
33
|
+
|
|
31
34
|
/**
|
|
32
35
|
* The reactive search params of the URL. It is just a wrapper around `SvelteURLSearchParam` that
|
|
33
36
|
* will update the url on change.
|
|
@@ -62,6 +65,8 @@ export type Hooks = {
|
|
|
62
65
|
beforeLoad?(): void | Promise<void>;
|
|
63
66
|
/** A function that will be called after the route is loaded. */
|
|
64
67
|
afterLoad?(): void;
|
|
68
|
+
/** A function that will be called when the route is preloaded. */
|
|
69
|
+
onPreload?(): void;
|
|
65
70
|
};
|
|
66
71
|
|
|
67
72
|
export type Routes = {
|
|
@@ -90,8 +95,9 @@ export type RouterApi<T extends Routes> = {
|
|
|
90
95
|
* @param params The parameters to replace in the route.
|
|
91
96
|
*/
|
|
92
97
|
p<U extends Path<T>>(...args: ConstructPathArgs<U>): string;
|
|
98
|
+
|
|
93
99
|
/**
|
|
94
|
-
* Navigate
|
|
100
|
+
* Navigate programmatically to a route.
|
|
95
101
|
*
|
|
96
102
|
* ```js
|
|
97
103
|
* navigate('/users');
|
|
@@ -124,6 +130,14 @@ export type RouterApi<T extends Routes> = {
|
|
|
124
130
|
<U extends Path<T>>(...args: IsActiveArgs<U>): boolean;
|
|
125
131
|
startsWith<U extends Path<T>>(...args: IsActiveArgs<U>): boolean;
|
|
126
132
|
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Preloads the given route.
|
|
136
|
+
*
|
|
137
|
+
* @param path The route to preload.
|
|
138
|
+
*/
|
|
139
|
+
preload<U extends Path<T>>(path: U): Promise<void>;
|
|
140
|
+
|
|
127
141
|
route: {
|
|
128
142
|
/**
|
|
129
143
|
* An object containing the parameters of the current route.
|
|
@@ -134,7 +148,7 @@ export type RouterApi<T extends Routes> = {
|
|
|
134
148
|
*/
|
|
135
149
|
params: AllParams<T>;
|
|
136
150
|
/** The reactive pathname of the URL. */
|
|
137
|
-
pathname: Path<T
|
|
151
|
+
pathname: (Path<T, true> & {}) | (string & {});
|
|
138
152
|
/** The reactive query string part of the URL. */
|
|
139
153
|
search: string;
|
|
140
154
|
/** The reactive history state that can be passed to the `navigate` function. */
|
|
@@ -144,8 +158,8 @@ export type RouterApi<T extends Routes> = {
|
|
|
144
158
|
};
|
|
145
159
|
};
|
|
146
160
|
|
|
147
|
-
export type Path<T extends Routes> = RemoveParenthesis<
|
|
148
|
-
RemoveLastSlash<RecursiveKeys<StripNonRoutes<T
|
|
161
|
+
export type Path<T extends Routes, AnyParam extends boolean = false> = RemoveParenthesis<
|
|
162
|
+
RemoveLastSlash<RecursiveKeys<StripNonRoutes<T>, '', AnyParam>>
|
|
149
163
|
>;
|
|
150
164
|
|
|
151
165
|
export type ConstructPathArgs<T extends string> =
|
|
@@ -155,9 +169,13 @@ export type IsActiveArgs<T extends string> =
|
|
|
155
169
|
PathParams<T> extends never ? [T] : [T] | [T, PathParams<T>];
|
|
156
170
|
|
|
157
171
|
export type PathParams<T extends string> =
|
|
158
|
-
ExtractParams<T
|
|
172
|
+
ExtractParams<RemoveParenthesis<T>> extends never
|
|
173
|
+
? never
|
|
174
|
+
: Record<ExtractParams<RemoveParenthesis<T>>, string>;
|
|
159
175
|
|
|
160
|
-
export type AllParams<T extends Routes> = Partial<
|
|
176
|
+
export type AllParams<T extends Routes> = Partial<
|
|
177
|
+
Record<ExtractParams<RemoveParenthesis<RecursiveKeys<T>>>, string>
|
|
178
|
+
>;
|
|
161
179
|
|
|
162
180
|
export type NavigateOptions =
|
|
163
181
|
| {
|
|
@@ -195,14 +213,28 @@ type StripNonRoutes<T extends Routes> = {
|
|
|
195
213
|
: K]: T[K] extends Routes ? StripNonRoutes<T[K]> : T[K];
|
|
196
214
|
};
|
|
197
215
|
|
|
198
|
-
type RecursiveKeys<
|
|
216
|
+
type RecursiveKeys<
|
|
217
|
+
T extends Routes,
|
|
218
|
+
Prefix extends string = '',
|
|
219
|
+
AnyParam extends boolean = false,
|
|
220
|
+
> = {
|
|
199
221
|
[K in keyof T]: K extends string
|
|
200
222
|
? T[K] extends Routes
|
|
201
|
-
? RecursiveKeys<
|
|
202
|
-
|
|
223
|
+
? RecursiveKeys<
|
|
224
|
+
T[K],
|
|
225
|
+
`${Prefix}${AnyParam extends true ? ReplaceParamWithString<K> : K}`,
|
|
226
|
+
AnyParam
|
|
227
|
+
>
|
|
228
|
+
: `${Prefix}${AnyParam extends true ? ReplaceParamWithString<K> : K}`
|
|
203
229
|
: never;
|
|
204
230
|
}[keyof T];
|
|
205
231
|
|
|
232
|
+
type ReplaceParamWithString<T extends string> = T extends `/:${string}`
|
|
233
|
+
? `/${string}`
|
|
234
|
+
: T extends `/(:${string})`
|
|
235
|
+
? `/${string}`
|
|
236
|
+
: T;
|
|
237
|
+
|
|
206
238
|
type RemoveLastSlash<T extends string> = T extends '/' ? T : T extends `${infer R}/` ? R : T;
|
|
207
239
|
|
|
208
240
|
type RemoveParenthesis<T extends string> = T extends `${infer A}(${infer B})${infer C}`
|
|
@@ -211,14 +243,10 @@ type RemoveParenthesis<T extends string> = T extends `${infer A}(${infer B})${in
|
|
|
211
243
|
|
|
212
244
|
type ExtractParams<T extends string> = T extends `${string}:${infer Param}/${infer Rest}`
|
|
213
245
|
? Param | ExtractParams<`/${Rest}`>
|
|
214
|
-
: T extends `${string}
|
|
246
|
+
: T extends `${string}:${infer Param}`
|
|
215
247
|
? Param
|
|
216
|
-
: T extends `${string}
|
|
217
|
-
? Param
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
? Param extends ''
|
|
222
|
-
? never
|
|
223
|
-
: Param
|
|
224
|
-
: never;
|
|
248
|
+
: T extends `${string}*${infer Param}`
|
|
249
|
+
? Param extends ''
|
|
250
|
+
? never
|
|
251
|
+
: Param
|
|
252
|
+
: never;
|