sv-router 0.16.2 → 0.17.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/package.json +10 -10
- package/src/cli/index.js +4 -0
- package/src/create-router.svelte.js +42 -24
- package/src/gen/config.js +2 -0
- package/src/gen/generate-router-code.js +7 -5
- package/src/gen/write-router-code.js +2 -1
- package/src/helpers/utils.js +2 -2
- package/src/index.d.ts +22 -6
- package/src/vite-plugin/index.d.ts +6 -0
- package/src/vite-plugin/plugin.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sv-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Type-safe routing for Svelte SPAs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"router",
|
|
@@ -41,22 +41,22 @@
|
|
|
41
41
|
"@testing-library/jest-dom": "^6.9.1",
|
|
42
42
|
"@testing-library/svelte": "^5.3.1",
|
|
43
43
|
"@testing-library/user-event": "^14.6.1",
|
|
44
|
-
"@types/node": "^24.12.
|
|
45
|
-
"@vitest/coverage-v8": "^4.1.
|
|
46
|
-
"eslint": "^10.
|
|
44
|
+
"@types/node": "^24.12.4",
|
|
45
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
46
|
+
"eslint": "^10.4.0",
|
|
47
47
|
"eslint-config-prettier": "^10.1.8",
|
|
48
48
|
"eslint-plugin-simple-import-sort": "^13.0.0",
|
|
49
|
-
"eslint-plugin-svelte": "^3.
|
|
50
|
-
"eslint-plugin-unicorn": "^
|
|
49
|
+
"eslint-plugin-svelte": "^3.18.0",
|
|
50
|
+
"eslint-plugin-unicorn": "^65.0.1",
|
|
51
51
|
"globals": "^17.6.0",
|
|
52
52
|
"happy-dom": "^20.9.0",
|
|
53
|
-
"oxfmt": "^0.
|
|
53
|
+
"oxfmt": "^0.52.0",
|
|
54
54
|
"svelte-check": "^4.4.8",
|
|
55
55
|
"type-testing": "^0.2.0",
|
|
56
56
|
"typescript": "^6.0.3",
|
|
57
|
-
"typescript-eslint": "^8.
|
|
58
|
-
"vite": "^8.0.
|
|
59
|
-
"vitest": "^4.1.
|
|
57
|
+
"typescript-eslint": "^8.60.0",
|
|
58
|
+
"vite": "^8.0.14",
|
|
59
|
+
"vitest": "^4.1.7"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"svelte": "^5"
|
package/src/cli/index.js
CHANGED
|
@@ -23,6 +23,7 @@ function parseViteConfig() {
|
|
|
23
23
|
if (!routerConfig) return;
|
|
24
24
|
console.log('ℹ️ Using router plugin options from Vite config');
|
|
25
25
|
if (routerConfig.allLazy) genConfig.allLazy = routerConfig.allLazy;
|
|
26
|
+
if (routerConfig.base) genConfig.base = routerConfig.base;
|
|
26
27
|
if (routerConfig.js) genConfig.routesInJs = routerConfig.js;
|
|
27
28
|
if (routerConfig.path) genConfig.routesPath = routerConfig.path;
|
|
28
29
|
if (routerConfig.ignore) genConfig.ignore = routerConfig.ignore;
|
|
@@ -58,6 +59,9 @@ function parseArgs() {
|
|
|
58
59
|
const allLazyArg = arg('allLazy');
|
|
59
60
|
if (allLazyArg) genConfig.allLazy = true;
|
|
60
61
|
|
|
62
|
+
const baseArg = arg('base');
|
|
63
|
+
if (baseArg) genConfig.base = baseArg;
|
|
64
|
+
|
|
61
65
|
const jsArg = arg('js');
|
|
62
66
|
if (jsArg) genConfig.routesInJs = true;
|
|
63
67
|
|
|
@@ -62,30 +62,42 @@ let pendingController = /** @type {AbortController | null} */ (null);
|
|
|
62
62
|
/** @type {Promise<void | null> | null} */
|
|
63
63
|
let currentNavigationPromise = null;
|
|
64
64
|
|
|
65
|
+
/** @param {string} basename */
|
|
66
|
+
function setBase(basename) {
|
|
67
|
+
const url = new URL(globalThis.location.toString());
|
|
68
|
+
if (basename === '#') {
|
|
69
|
+
base.name = '#';
|
|
70
|
+
if (!globalThis.location.href.includes('#')) {
|
|
71
|
+
url.hash = '/';
|
|
72
|
+
history.replaceState(
|
|
73
|
+
{ _routerIndex: historyIndex, _userState: history.state ?? null },
|
|
74
|
+
'',
|
|
75
|
+
url.toString(),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
base.name = (basename.startsWith('/') ? '' : '/') + basename;
|
|
80
|
+
if (!url.pathname.startsWith(base.name)) {
|
|
81
|
+
url.pathname = join(base.name, url.pathname);
|
|
82
|
+
history.replaceState(
|
|
83
|
+
{ _routerIndex: historyIndex, _userState: history.state ?? null },
|
|
84
|
+
'',
|
|
85
|
+
url.toString(),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
Object.assign(location, updatedLocation());
|
|
90
|
+
}
|
|
91
|
+
|
|
65
92
|
/** @param {string | undefined} basename */
|
|
66
93
|
export function init(basename) {
|
|
67
94
|
if (basename) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
{ _routerIndex: historyIndex, _userState: history.state ?? null },
|
|
75
|
-
'',
|
|
76
|
-
url.toString(),
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
} else {
|
|
80
|
-
base.name = (basename.startsWith('/') ? '' : '/') + basename;
|
|
81
|
-
if (!url.pathname.startsWith(base.name)) {
|
|
82
|
-
url.pathname = join(base.name, url.pathname);
|
|
83
|
-
history.replaceState(
|
|
84
|
-
{ _routerIndex: historyIndex, _userState: history.state ?? null },
|
|
85
|
-
'',
|
|
86
|
-
url.toString(),
|
|
87
|
-
);
|
|
88
|
-
}
|
|
95
|
+
if (base.name === undefined) {
|
|
96
|
+
setBase(basename);
|
|
97
|
+
} else if (DEV && base.name !== basename && `/${basename}` !== base.name) {
|
|
98
|
+
console.warn(
|
|
99
|
+
'sv-router: the `base` prop on `<Router>` is ignored because a base was already set in `createRouter`.',
|
|
100
|
+
);
|
|
89
101
|
}
|
|
90
102
|
}
|
|
91
103
|
if (history.state?._routerIndex === undefined) {
|
|
@@ -103,11 +115,16 @@ export function init(basename) {
|
|
|
103
115
|
/**
|
|
104
116
|
* @template {import('./index.d.ts').Routes} T
|
|
105
117
|
* @param {T} r
|
|
118
|
+
* @param {import('./index.d.ts').CreateRouterOptions} [options]
|
|
106
119
|
* @returns {import('./index.d.ts').RouterApi<T>}
|
|
107
120
|
*/
|
|
108
|
-
export function createRouter(r) {
|
|
121
|
+
export function createRouter(r, options = {}) {
|
|
109
122
|
routes = r;
|
|
110
123
|
|
|
124
|
+
if (BROWSER && options.base) {
|
|
125
|
+
setBase(options.base);
|
|
126
|
+
}
|
|
127
|
+
|
|
111
128
|
if (DEV && BROWSER) {
|
|
112
129
|
import('./helpers/validate-routes.js').then(({ validateRoutes }) => {
|
|
113
130
|
validateRoutes(routes);
|
|
@@ -229,8 +246,9 @@ export async function onNavigate(path, options = {}) {
|
|
|
229
246
|
const matchPath = getMatchPath(path);
|
|
230
247
|
const { match, layouts, hooks, meta: newMeta, params: newParams } = matchRoute(matchPath, routes);
|
|
231
248
|
|
|
232
|
-
const
|
|
233
|
-
const
|
|
249
|
+
const navContext = path ? options : { ...options, ...updatedLocation() };
|
|
250
|
+
const search = parseSearch(navContext.search);
|
|
251
|
+
const hooksContext = { ...navContext, pathname: matchPath, meta: newMeta, search };
|
|
234
252
|
|
|
235
253
|
let errorHooks = [];
|
|
236
254
|
for (const hook of hooks) {
|
package/src/gen/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @type {{
|
|
3
3
|
* allLazy: boolean;
|
|
4
|
+
* base: string | undefined;
|
|
4
5
|
* ignore: RegExp[];
|
|
5
6
|
* readonly genCodeAlias: string;
|
|
6
7
|
* readonly genCodeDirPath: string;
|
|
@@ -12,6 +13,7 @@
|
|
|
12
13
|
*/
|
|
13
14
|
export const genConfig = {
|
|
14
15
|
allLazy: false,
|
|
16
|
+
base: undefined,
|
|
15
17
|
genCodeAlias: 'sv-router/generated',
|
|
16
18
|
genCodeDirPath: '.router',
|
|
17
19
|
ignore: [],
|
|
@@ -19,7 +19,7 @@ const META_FILENAME_REGEX = /(?<=[/.]|^)(meta)(\.svelte)?\.(js|ts)$/; // meta.js
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* @param {string} routesPath
|
|
22
|
-
* @param {{ allLazy?: boolean; js?: boolean; ignore?: RegExp[] }} [options]
|
|
22
|
+
* @param {{ allLazy?: boolean; base?: string; js?: boolean; ignore?: RegExp[] }} [options]
|
|
23
23
|
* @returns {string}
|
|
24
24
|
*/
|
|
25
25
|
export function generateRouterCode(routesPath, options) {
|
|
@@ -149,7 +149,7 @@ function mergeRouteGroup(result, childMap) {
|
|
|
149
149
|
const hasRootRoute = '/' in childMap;
|
|
150
150
|
|
|
151
151
|
for (const [key, val] of Object.entries(childMap)) {
|
|
152
|
-
if (
|
|
152
|
+
if (['layout', 'hooks', 'meta'].includes(key)) {
|
|
153
153
|
continue;
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -185,10 +185,10 @@ function mergeRouteGroup(result, childMap) {
|
|
|
185
185
|
/**
|
|
186
186
|
* @param {GeneratedRoutes} routes
|
|
187
187
|
* @param {string} routesPath
|
|
188
|
-
* @param {{ allLazy?: boolean; js?: boolean }} [options]
|
|
188
|
+
* @param {{ allLazy?: boolean; base?: string; js?: boolean }} [options]
|
|
189
189
|
* @returns {string}
|
|
190
190
|
*/
|
|
191
|
-
export function createRouterCode(routes, routesPath, { allLazy = false, js = false } = {}) {
|
|
191
|
+
export function createRouterCode(routes, routesPath, { allLazy = false, base, js = false } = {}) {
|
|
192
192
|
if (!routesPath.endsWith('/')) {
|
|
193
193
|
routesPath += '/';
|
|
194
194
|
}
|
|
@@ -240,7 +240,9 @@ export function createRouterCode(routes, routesPath, { allLazy = false, js = fal
|
|
|
240
240
|
'',
|
|
241
241
|
`export const routes = ${stringifiedRoutes};`,
|
|
242
242
|
...(js ? [] : ['export type Routes = typeof routes;']),
|
|
243
|
-
|
|
243
|
+
`export const { p, navigate, isActive, preload, route } = createRouter(routes${
|
|
244
|
+
base === undefined ? '' : `, { base: '${base}' }`
|
|
245
|
+
});`,
|
|
244
246
|
'',
|
|
245
247
|
].join('\n');
|
|
246
248
|
}
|
|
@@ -39,6 +39,7 @@ export function writeRouterCode() {
|
|
|
39
39
|
// Write `.router/router.ts` file
|
|
40
40
|
const routerCode = generateRouterCode(genConfig.routesPath, {
|
|
41
41
|
allLazy: genConfig.allLazy,
|
|
42
|
+
base: genConfig.base,
|
|
42
43
|
js: genConfig.routesInJs,
|
|
43
44
|
ignore: genConfig.ignore,
|
|
44
45
|
});
|
|
@@ -73,7 +74,7 @@ function getTypeScriptMajorVersion() {
|
|
|
73
74
|
const require = createRequire(process.cwd() + '/package.json');
|
|
74
75
|
const tsPackagePath = require.resolve('typescript/package.json');
|
|
75
76
|
const tsPackage = JSON.parse(fs.readFileSync(tsPackagePath, 'utf8'));
|
|
76
|
-
return Number(tsPackage.version.split('.')[0]);
|
|
77
|
+
return Number(tsPackage.version.split('.', 1)[0]);
|
|
77
78
|
} catch {
|
|
78
79
|
return;
|
|
79
80
|
}
|
package/src/helpers/utils.js
CHANGED
|
@@ -8,7 +8,7 @@ import { base } from '../create-router.svelte.js';
|
|
|
8
8
|
export function constructPath(path, params) {
|
|
9
9
|
if (params) {
|
|
10
10
|
for (const key in params) {
|
|
11
|
-
path = path.replace(`:${key}`,
|
|
11
|
+
path = path.replace(`:${key}`, encodeURIComponent(params[key]));
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -68,7 +68,7 @@ function resolveRouteComponent(input) {
|
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* @param {unknown} input
|
|
71
|
-
* @returns {input is import(
|
|
71
|
+
* @returns {input is import('../index.d.ts').LazyRouteComponent}
|
|
72
72
|
*/
|
|
73
73
|
export function isLazyImport(input) {
|
|
74
74
|
return (
|
package/src/index.d.ts
CHANGED
|
@@ -26,8 +26,19 @@ export function serializeSearch(search: Search): string | undefined;
|
|
|
26
26
|
* ...
|
|
27
27
|
* });
|
|
28
28
|
* ```
|
|
29
|
+
*
|
|
30
|
+
* A base path can be provided as an option:
|
|
31
|
+
*
|
|
32
|
+
* ```js
|
|
33
|
+
* export const { p, ... } = createRouter({ ... }, { base: 'my-app' });
|
|
34
|
+
* ```
|
|
29
35
|
*/
|
|
30
|
-
export function createRouter<T extends Routes>(r: T): RouterApi<T>;
|
|
36
|
+
export function createRouter<T extends Routes>(r: T, options?: CreateRouterOptions): RouterApi<T>;
|
|
37
|
+
|
|
38
|
+
export type CreateRouterOptions = {
|
|
39
|
+
/** The base path that is prepended to every URL. Use `'#'` to enable hash-based routing. */
|
|
40
|
+
base?: string;
|
|
41
|
+
};
|
|
31
42
|
|
|
32
43
|
/**
|
|
33
44
|
* Blocks navigation as long as the callback returns `false`.
|
|
@@ -66,11 +77,16 @@ export function blockNavigation(
|
|
|
66
77
|
| { beforeUnload?(): boolean; onNavigate(): boolean | Promise<boolean> },
|
|
67
78
|
): () => void;
|
|
68
79
|
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
/** The component that will render the current route. */
|
|
81
|
+
export const Router: Component<{
|
|
82
|
+
/**
|
|
83
|
+
* The base path that is prepended to every URL.
|
|
84
|
+
*
|
|
85
|
+
* @deprecated Use the `base` option of `createRouter` (or of the Vite plugin for file-based
|
|
86
|
+
* routing) instead.
|
|
87
|
+
*/
|
|
88
|
+
base?: string;
|
|
89
|
+
}>;
|
|
74
90
|
|
|
75
91
|
/**
|
|
76
92
|
* The reactive search params of the URL. It is just a wrapper around `SvelteURLSearchParam` that
|
|
@@ -7,6 +7,12 @@ export type RouterOptions = {
|
|
|
7
7
|
* @default false
|
|
8
8
|
*/
|
|
9
9
|
allLazy?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The base path that is prepended to every URL. Use `'#'` to enable hash-based routing.
|
|
12
|
+
*
|
|
13
|
+
* @default undefined
|
|
14
|
+
*/
|
|
15
|
+
base?: string;
|
|
10
16
|
/**
|
|
11
17
|
* If true, generates the routes in a .js file instead of a .ts file.
|
|
12
18
|
*
|
|
@@ -8,6 +8,7 @@ import { writeRouterCode } from '../gen/write-router-code.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export function router(options) {
|
|
10
10
|
genConfig.allLazy = options?.allLazy || false;
|
|
11
|
+
genConfig.base = options?.base;
|
|
11
12
|
genConfig.ignore = options?.ignore || [];
|
|
12
13
|
genConfig.routesInJs = options?.js || false;
|
|
13
14
|
genConfig.routesPath = options?.path || 'src/routes';
|