sv-router 0.11.1 → 0.13.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 +2 -2
- package/src/cli/index.js +64 -15
- package/src/create-router.svelte.js +24 -0
- package/src/gen/generate-router-code.js +4 -2
- package/src/gen/write-router-code.js +2 -2
- package/src/helpers/match-route.js +7 -3
- package/src/index.d.ts +9 -0
- package/src/index.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sv-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Modern Svelte Routing",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
49
49
|
"eslint-plugin-svelte": "^3.13.1",
|
|
50
50
|
"eslint-plugin-unicorn": "^62.0.0",
|
|
51
|
-
"globals": "^
|
|
51
|
+
"globals": "^17.0.0",
|
|
52
52
|
"happy-dom": "^20.0.11",
|
|
53
53
|
"prettier": "^3.7.4",
|
|
54
54
|
"prettier-plugin-jsdoc": "^1.8.0",
|
package/src/cli/index.js
CHANGED
|
@@ -1,13 +1,76 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console */
|
|
2
3
|
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
3
6
|
import { genConfig } from '../gen/config.js';
|
|
4
7
|
import { writeRouterCode } from '../gen/write-router-code.js';
|
|
5
8
|
|
|
6
9
|
const args = process.argv.slice(2).flatMap((arg) => arg.split('='));
|
|
7
10
|
|
|
11
|
+
if (args.length > 0) {
|
|
12
|
+
parseArgs();
|
|
13
|
+
} else {
|
|
14
|
+
parseViteConfig();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
writeRouterCode();
|
|
18
|
+
|
|
19
|
+
function parseViteConfig() {
|
|
20
|
+
const viteConfig = readViteConfig('ts') || readViteConfig('js');
|
|
21
|
+
if (!viteConfig) return;
|
|
22
|
+
const routerConfig = extractRouterConfig(viteConfig);
|
|
23
|
+
if (!routerConfig) return;
|
|
24
|
+
console.log('ℹ️ Using router plugin options from Vite config');
|
|
25
|
+
if (routerConfig.allLazy) genConfig.allLazy = routerConfig.allLazy;
|
|
26
|
+
if (routerConfig.js) genConfig.routesInJs = routerConfig.js;
|
|
27
|
+
if (routerConfig.path) genConfig.routesPath = routerConfig.path;
|
|
28
|
+
if (routerConfig.ignore) genConfig.ignore = routerConfig.ignore;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {'js' | 'ts'} extension
|
|
33
|
+
* @returns {string | undefined}
|
|
34
|
+
*/
|
|
35
|
+
function readViteConfig(extension) {
|
|
36
|
+
const vitePath = path.join(process.cwd(), 'vite.config.' + extension);
|
|
37
|
+
if (existsSync(vitePath)) {
|
|
38
|
+
return readFileSync(vitePath, 'utf8');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} viteConfig
|
|
44
|
+
* @returns {import('../vite-plugin/index.d.ts').RouterOptions | undefined}
|
|
45
|
+
*/
|
|
46
|
+
function extractRouterConfig(viteConfig) {
|
|
47
|
+
const regex = /router\(\s*([^)]+)\)/;
|
|
48
|
+
const match = viteConfig.match(regex);
|
|
49
|
+
if (!match) return;
|
|
50
|
+
try {
|
|
51
|
+
return new Function(`return ${match[1]}`)();
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('⚠️ Error parsing router config:', error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseArgs() {
|
|
58
|
+
const allLazyArg = arg('allLazy');
|
|
59
|
+
if (allLazyArg) genConfig.allLazy = true;
|
|
60
|
+
|
|
61
|
+
const jsArg = arg('js');
|
|
62
|
+
if (jsArg) genConfig.routesInJs = true;
|
|
63
|
+
|
|
64
|
+
const pathArg = arg('path');
|
|
65
|
+
if (pathArg) genConfig.routesPath = pathArg;
|
|
66
|
+
|
|
67
|
+
const ignoreArg = arg('ignore');
|
|
68
|
+
if (ignoreArg) genConfig.ignore = ignoreArg.split(',').map((ignore) => new RegExp(ignore, 'gu'));
|
|
69
|
+
}
|
|
70
|
+
|
|
8
71
|
/**
|
|
9
72
|
* @param {keyof import('../vite-plugin/index.d.ts').RouterOptions} option
|
|
10
|
-
* @returns
|
|
73
|
+
* @returns {string | undefined}
|
|
11
74
|
*/
|
|
12
75
|
function arg(option) {
|
|
13
76
|
const pathArgIndex = args.indexOf('--' + option);
|
|
@@ -17,17 +80,3 @@ function arg(option) {
|
|
|
17
80
|
}
|
|
18
81
|
return args[pathArgIndex];
|
|
19
82
|
}
|
|
20
|
-
|
|
21
|
-
const allLazyArg = arg('allLazy');
|
|
22
|
-
if (allLazyArg) genConfig.allLazy = true;
|
|
23
|
-
|
|
24
|
-
const jsArg = arg('js');
|
|
25
|
-
if (jsArg) genConfig.routesInJs = true;
|
|
26
|
-
|
|
27
|
-
const pathArg = arg('path');
|
|
28
|
-
if (pathArg) genConfig.routesPath = pathArg;
|
|
29
|
-
|
|
30
|
-
const ignoreArg = arg('ignore');
|
|
31
|
-
if (ignoreArg) genConfig.ignore = ignoreArg.split(',').map((ignore) => new RegExp(ignore, 'gu'));
|
|
32
|
-
|
|
33
|
-
writeRouterCode();
|
|
@@ -43,6 +43,9 @@ let params = $state({ value: {} });
|
|
|
43
43
|
|
|
44
44
|
let meta = $state({ value: {} });
|
|
45
45
|
|
|
46
|
+
/** @type {(() => boolean) | null} */
|
|
47
|
+
let navigationBlocker = null;
|
|
48
|
+
|
|
46
49
|
let navigationIndex = 0;
|
|
47
50
|
let pendingNavigationIndex = 0;
|
|
48
51
|
|
|
@@ -151,6 +154,22 @@ export async function onNavigate(path, options = {}) {
|
|
|
151
154
|
throw new Error('Router not initialized: `createRouter` was not called.');
|
|
152
155
|
}
|
|
153
156
|
|
|
157
|
+
if (navigationBlocker) {
|
|
158
|
+
if (!navigationBlocker()) {
|
|
159
|
+
const url = new URL(globalThis.location.toString());
|
|
160
|
+
url.search = location.search;
|
|
161
|
+
url.hash = location.hash;
|
|
162
|
+
if (base.name === '#') {
|
|
163
|
+
url.hash = location.pathname;
|
|
164
|
+
} else {
|
|
165
|
+
url.pathname = location.pathname;
|
|
166
|
+
}
|
|
167
|
+
globalThis.history.replaceState($state.snapshot(location.state) || {}, '', url.toString());
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
navigationBlocker = null;
|
|
171
|
+
}
|
|
172
|
+
|
|
154
173
|
navigationIndex++;
|
|
155
174
|
const currentNavigationIndex = navigationIndex;
|
|
156
175
|
|
|
@@ -292,3 +311,8 @@ export function onGlobalClick(event) {
|
|
|
292
311
|
viewTransition: viewTransition === '' || viewTransition === 'true',
|
|
293
312
|
});
|
|
294
313
|
}
|
|
314
|
+
|
|
315
|
+
/** @param {() => boolean} callback */
|
|
316
|
+
export function blockNavigation(callback) {
|
|
317
|
+
navigationBlocker = callback;
|
|
318
|
+
}
|
|
@@ -10,6 +10,7 @@ import path from 'node:path';
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const FILENAME_REGEX = /(?<=[/.]|^)\(?([\w-]+)\)?(\.lazy)?\.svelte$/; // any.svelte, any.lazy.svelte, (any).svelte
|
|
13
|
+
const INDEX_FILENAME_REGEX = /(?<=[/.]|^)\(?index\)?(\.lazy)?\.svelte$/; // index.svelte, index.lazy.svelte, (index).svelte
|
|
13
14
|
const PARAM_FILENAME_REGEX = /(?<=[/.]|^)\(?\[([\w-]+)\]\)?(\.lazy)?\.svelte$/; // [any].svelte, [any].lazy.svelte, ([any]).svelte
|
|
14
15
|
const CATCH_ALL_FILENAME_REGEX = /(?<=[/.]|^)\(?\[\.\.\.([\w-]+)\]\)?(\.lazy)?\.svelte$/; // [...any].svelte, [...any].lazy.svelte, ([...any]).svelte
|
|
15
16
|
const OUT_OF_LAYOUT_FILENAME_REGEX = /(?<=[/.]|^)\(\[\.?\.?\.?([\w-]+)\]\)(\.lazy)?\.svelte$/; // ([any]).svelte, ([...any]).lazy.svelte
|
|
@@ -81,8 +82,9 @@ export function createRouteMap(fileTree, prefix = '') {
|
|
|
81
82
|
continue;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
if (
|
|
85
|
-
const
|
|
85
|
+
if (INDEX_FILENAME_REGEX.test(entry)) {
|
|
86
|
+
const replacement = /\.?\(index\)(\.lazy)?\.svelte/.test(entry) ? '()' : '';
|
|
87
|
+
const indexEntry = entry.replace(/\.?\(?index\)?(\.lazy)?\.svelte/, replacement);
|
|
86
88
|
result['/' + (indexEntry ? filePathToRoute(indexEntry) : '')] = prefix + entry;
|
|
87
89
|
continue;
|
|
88
90
|
}
|
|
@@ -40,13 +40,13 @@ export function writeRouterCode() {
|
|
|
40
40
|
const written = writeFileIfDifferent(genConfig.routerPath, routerCode);
|
|
41
41
|
|
|
42
42
|
if (written) {
|
|
43
|
-
console.log('
|
|
43
|
+
console.log('❇️ Routes generated');
|
|
44
44
|
} else {
|
|
45
45
|
console.log('✅️ Routes already up to date');
|
|
46
46
|
}
|
|
47
47
|
} catch (error) {
|
|
48
48
|
console.error(
|
|
49
|
-
'Error during routes generation:',
|
|
49
|
+
'⚠️ Error during routes generation:',
|
|
50
50
|
error instanceof Error ? error.message : String(error),
|
|
51
51
|
);
|
|
52
52
|
}
|
|
@@ -64,11 +64,11 @@ export function matchRoute(pathname, routes) {
|
|
|
64
64
|
|
|
65
65
|
const pathPart = pathParts[index];
|
|
66
66
|
if (routePart.startsWith(':')) {
|
|
67
|
-
params[routePart.slice(1)] = pathPart;
|
|
67
|
+
params[routePart.slice(1)] = decodeURIComponent(pathPart);
|
|
68
68
|
} else if (routePart.startsWith('*')) {
|
|
69
69
|
const param = routePart.slice(1);
|
|
70
70
|
if (param) {
|
|
71
|
-
params[param] = pathParts.slice(index).join('/');
|
|
71
|
+
params[param] = pathParts.slice(index).map(decodeURIComponent).join('/');
|
|
72
72
|
}
|
|
73
73
|
if (breakFromLayouts) {
|
|
74
74
|
routePart = `(${routePart})`;
|
|
@@ -81,7 +81,7 @@ export function matchRoute(pathname, routes) {
|
|
|
81
81
|
);
|
|
82
82
|
match = /** @type {RouteComponent} */ (routes[resolvedPath]);
|
|
83
83
|
break outer;
|
|
84
|
-
} else if (routePart !== pathPart?.toLowerCase()) {
|
|
84
|
+
} else if (routePart.toLowerCase() !== pathPart?.toLowerCase()) {
|
|
85
85
|
break;
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -93,6 +93,10 @@ export function matchRoute(pathname, routes) {
|
|
|
93
93
|
routes[/** @type {keyof Routes} */ ('/' + routeParts.join('/'))]
|
|
94
94
|
);
|
|
95
95
|
|
|
96
|
+
if (typeof routeMatch === 'function' && routeParts.length !== pathParts.length) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
96
100
|
if (!breakFromLayouts && 'layout' in routes && routes.layout) {
|
|
97
101
|
layouts.push(routes.layout);
|
|
98
102
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -28,6 +28,15 @@ export function serializeSearch(search: Search): string | undefined;
|
|
|
28
28
|
*/
|
|
29
29
|
export function createRouter<T extends Routes>(r: T): RouterApi<T>;
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Block navigation until the callback returns `false`.
|
|
33
|
+
*
|
|
34
|
+
* ```js
|
|
35
|
+
* blockNavigation(() => confirm('Are you sure you want to leave?'));
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function blockNavigation(callback: () => boolean): void;
|
|
39
|
+
|
|
31
40
|
/**
|
|
32
41
|
* The component that will render the current route. You can pass a `base` prop to set the base path
|
|
33
42
|
* that is prepended to every url.
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { isActiveLink } from './actions.svelte.js';
|
|
2
|
-
export { createRouter } from './create-router.svelte.js';
|
|
2
|
+
export { blockNavigation, createRouter } from './create-router.svelte.js';
|
|
3
3
|
export { serializeSearch } from './helpers/utils.js';
|
|
4
4
|
export { Navigation } from './navigation.js';
|
|
5
5
|
export { default as Router } from './Router.svelte';
|