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
- ## Installation
30
+ ## Getting Started
31
31
 
32
- Add it to an existing Svelte project:
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.4.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.28.1",
40
- "@eslint/js": "^9.23.0",
41
- "@types/node": "^22.13.14",
42
- "eslint": "^9.23.0",
43
- "eslint-config-prettier": "^10.1.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.3.3",
46
- "eslint-plugin-unicorn": "^58.0.0",
47
- "globals": "^16.0.0",
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.5",
51
+ "svelte-check": "^4.1.7",
52
52
  "type-testing": "^0.2.0",
53
- "typescript": "^5.8.2",
54
- "typescript-eslint": "^8.28.0",
55
- "vite": "^6.2.3",
56
- "vitest": "^3.0.9"
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-on-hover.js';
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.d.ts').Routes} routes */
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
- const { match, layouts } = matchRoute(href, routes);
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="/about" use:isActiveLink={{ className: 'active-link' }}>
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 programatically to a route.
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> extends never ? never : Record<ExtractParams<T>, string>;
172
+ ExtractParams<RemoveParenthesis<T>> extends never
173
+ ? never
174
+ : Record<ExtractParams<RemoveParenthesis<T>>, string>;
159
175
 
160
- export type AllParams<T extends Routes> = Partial<Record<ExtractParams<RecursiveKeys<T>>, string>>;
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<T extends Routes, Prefix extends string = ''> = {
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<T[K], `${Prefix}${K}`>
202
- : `${Prefix}${K}`
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}(:${infer Param})`
246
+ : T extends `${string}:${infer Param}`
215
247
  ? Param
216
- : T extends `${string}:${infer Param}`
217
- ? Param
218
- : T extends `${string}(*${infer Param})`
219
- ? Param
220
- : T extends `${string}*${infer Param}`
221
- ? Param extends ''
222
- ? never
223
- : Param
224
- : never;
248
+ : T extends `${string}*${infer Param}`
249
+ ? Param extends ''
250
+ ? never
251
+ : Param
252
+ : never;