sv-router 0.11.0 → 0.12.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 +14 -14
- package/src/cli/index.js +64 -15
- package/src/create-router.svelte.js +4 -2
- package/src/gen/generate-router-code.js +25 -11
- package/src/gen/write-router-code.js +2 -2
- package/src/helpers/is-active.js +4 -6
- package/src/helpers/match-route.js +10 -1
- package/src/helpers/utils.js +6 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sv-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Modern Svelte Routing",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -36,29 +36,29 @@
|
|
|
36
36
|
"esm-env": "^1.2.2"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@changesets/cli": "^2.29.
|
|
40
|
-
"@eslint/js": "^9.39.
|
|
39
|
+
"@changesets/cli": "^2.29.8",
|
|
40
|
+
"@eslint/js": "^9.39.1",
|
|
41
41
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
42
42
|
"@testing-library/jest-dom": "^6.9.1",
|
|
43
|
-
"@testing-library/svelte": "^5.2.
|
|
43
|
+
"@testing-library/svelte": "^5.2.9",
|
|
44
44
|
"@testing-library/user-event": "^14.6.1",
|
|
45
|
-
"@types/node": "^24.
|
|
46
|
-
"eslint": "^9.39.
|
|
45
|
+
"@types/node": "^24.10.1",
|
|
46
|
+
"eslint": "^9.39.1",
|
|
47
47
|
"eslint-config-prettier": "^10.1.8",
|
|
48
48
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
49
|
-
"eslint-plugin-svelte": "^3.13.
|
|
49
|
+
"eslint-plugin-svelte": "^3.13.1",
|
|
50
50
|
"eslint-plugin-unicorn": "^62.0.0",
|
|
51
51
|
"globals": "^16.5.0",
|
|
52
|
-
"happy-dom": "^20.0.
|
|
53
|
-
"prettier": "^3.
|
|
54
|
-
"prettier-plugin-jsdoc": "^1.
|
|
52
|
+
"happy-dom": "^20.0.11",
|
|
53
|
+
"prettier": "^3.7.4",
|
|
54
|
+
"prettier-plugin-jsdoc": "^1.8.0",
|
|
55
55
|
"prettier-plugin-svelte": "^3.4.0",
|
|
56
|
-
"svelte-check": "^4.3.
|
|
56
|
+
"svelte-check": "^4.3.4",
|
|
57
57
|
"type-testing": "^0.2.0",
|
|
58
58
|
"typescript": "^5.9.3",
|
|
59
|
-
"typescript-eslint": "^8.
|
|
60
|
-
"vite": "^7.
|
|
61
|
-
"vitest": "^4.0.
|
|
59
|
+
"typescript-eslint": "^8.48.1",
|
|
60
|
+
"vite": "^7.2.6",
|
|
61
|
+
"vitest": "^4.0.15"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"svelte": "^5"
|
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();
|
|
@@ -134,7 +134,7 @@ function navigate(path, options = {}) {
|
|
|
134
134
|
|
|
135
135
|
path = constructPath(path, options.params);
|
|
136
136
|
if (base.name === '#') {
|
|
137
|
-
path =
|
|
137
|
+
path = path.replace('/#', '');
|
|
138
138
|
} else if (options.hash && !options.hash.startsWith('#')) {
|
|
139
139
|
options.hash = '#' + options.hash;
|
|
140
140
|
}
|
|
@@ -200,8 +200,10 @@ export async function onNavigate(path, options = {}) {
|
|
|
200
200
|
url.hash = options.hash || '';
|
|
201
201
|
if (base.name === '#') {
|
|
202
202
|
url.hash = path;
|
|
203
|
+
} else if (base.name && !path.startsWith(base.name)) {
|
|
204
|
+
url.pathname = join(base.name, path);
|
|
203
205
|
} else {
|
|
204
|
-
url.pathname =
|
|
206
|
+
url.pathname = path;
|
|
205
207
|
}
|
|
206
208
|
const historyMethod = options.replace ? 'replaceState' : 'pushState';
|
|
207
209
|
globalThis.history[historyMethod](options.state || {}, '', url.toString());
|
|
@@ -5,11 +5,12 @@ import path from 'node:path';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @typedef {{
|
|
8
|
-
* [key: string]: string | GeneratedRoutes;
|
|
8
|
+
* [key: string]: string | string[] | GeneratedRoutes;
|
|
9
9
|
* }} GeneratedRoutes
|
|
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
|
}
|
|
@@ -144,22 +146,28 @@ function mergeRouteGroup(result, childMap) {
|
|
|
144
146
|
const layout = childMap.layout;
|
|
145
147
|
const hooks = childMap.hooks;
|
|
146
148
|
const meta = childMap.meta;
|
|
149
|
+
const hasRootRoute = '/' in childMap;
|
|
147
150
|
|
|
148
151
|
for (const [key, val] of Object.entries(childMap)) {
|
|
149
152
|
if (key === 'layout' || key === 'hooks' || key === 'meta') {
|
|
150
153
|
continue;
|
|
151
154
|
}
|
|
152
155
|
|
|
156
|
+
const childMeta =
|
|
157
|
+
typeof val === 'object' && !Array.isArray(val) && 'meta' in val ? val.meta : undefined;
|
|
158
|
+
const mergedMeta =
|
|
159
|
+
childMeta && meta && !hasRootRoute ? [childMeta, meta].flat() : childMeta || meta;
|
|
160
|
+
|
|
153
161
|
/** @type {GeneratedRoutes} */
|
|
154
162
|
let routeWithGroupFiles = {};
|
|
155
163
|
if (typeof val === 'string') {
|
|
156
164
|
routeWithGroupFiles = { '/': val };
|
|
157
|
-
} else {
|
|
165
|
+
} else if (!Array.isArray(val)) {
|
|
158
166
|
routeWithGroupFiles = { ...val };
|
|
159
167
|
}
|
|
160
|
-
if (layout) routeWithGroupFiles.layout = layout;
|
|
161
|
-
if (hooks) routeWithGroupFiles.hooks = hooks;
|
|
162
|
-
if (
|
|
168
|
+
if (layout) routeWithGroupFiles.layout = /** @type {string} */ (layout);
|
|
169
|
+
if (hooks) routeWithGroupFiles.hooks = /** @type {string} */ (hooks);
|
|
170
|
+
if (mergedMeta) routeWithGroupFiles.meta = /** @type {string | string[]} */ (mergedMeta);
|
|
163
171
|
if (result[key]) {
|
|
164
172
|
throw new Error(`Route conflict at \`${key}\``);
|
|
165
173
|
}
|
|
@@ -186,12 +194,18 @@ export function createRouterCode(routes, routesPath, { allLazy = false, js = fal
|
|
|
186
194
|
/** @type {GeneratedRoutes} */
|
|
187
195
|
const result = {};
|
|
188
196
|
for (const [key, value] of Object.entries(routes)) {
|
|
189
|
-
if (typeof value === 'object') {
|
|
197
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
190
198
|
result[key] = handleImports(value, routesPath);
|
|
199
|
+
} else if (key === 'meta' && Array.isArray(value)) {
|
|
200
|
+
const varNames = value.map((metaPath) => {
|
|
201
|
+
const variableName = pathToCorrectCasing(metaPath);
|
|
202
|
+
importsMap.set(variableName, routesPath + metaPath);
|
|
203
|
+
return variableName;
|
|
204
|
+
});
|
|
205
|
+
result[key] = `{ ...${varNames.toReversed().join(', ...')} }`;
|
|
191
206
|
} else if (
|
|
192
|
-
|
|
193
|
-
key === 'meta' ||
|
|
194
|
-
(!value.endsWith('.lazy.svelte') && !allLazy)
|
|
207
|
+
typeof value === 'string' &&
|
|
208
|
+
(key === 'hooks' || key === 'meta' || (!value.endsWith('.lazy.svelte') && !allLazy))
|
|
195
209
|
) {
|
|
196
210
|
const variableName = pathToCorrectCasing(value);
|
|
197
211
|
importsMap.set(variableName, routesPath + value);
|
|
@@ -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
|
}
|
package/src/helpers/is-active.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { base, location } from '../create-router.svelte.js';
|
|
2
|
-
import { constructPath
|
|
2
|
+
import { constructPath } from './utils.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @param {string} pathname
|
|
@@ -7,8 +7,7 @@ import { constructPath, join } from './utils.js';
|
|
|
7
7
|
* @returns {boolean}
|
|
8
8
|
*/
|
|
9
9
|
export function isActive(pathname, params) {
|
|
10
|
-
|
|
11
|
-
return compare((a, b) => a === b, p, params);
|
|
10
|
+
return compare((a, b) => a === b, pathname, params);
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
/**
|
|
@@ -17,8 +16,7 @@ export function isActive(pathname, params) {
|
|
|
17
16
|
* @returns {boolean}
|
|
18
17
|
*/
|
|
19
18
|
isActive.startsWith = (pathname, params) => {
|
|
20
|
-
|
|
21
|
-
return compare((a, b) => a.startsWith(b), p, params);
|
|
19
|
+
return compare((a, b) => a.startsWith(b), pathname, params);
|
|
22
20
|
};
|
|
23
21
|
|
|
24
22
|
/**
|
|
@@ -34,7 +32,7 @@ function compare(compareFn, pathname, params) {
|
|
|
34
32
|
|
|
35
33
|
if (params) {
|
|
36
34
|
if (base.name === '#') {
|
|
37
|
-
return compareFn(location.pathname,
|
|
35
|
+
return compareFn(location.pathname, constructPath(pathname, params).replace('/#', ''));
|
|
38
36
|
} else {
|
|
39
37
|
return compareFn(location.pathname, constructPath(pathname, params));
|
|
40
38
|
}
|
|
@@ -45,6 +45,11 @@ export function matchRoute(pathname, routes) {
|
|
|
45
45
|
/** @type {RouteMeta} */
|
|
46
46
|
let meta = {};
|
|
47
47
|
|
|
48
|
+
const rootRoute = routes['/'];
|
|
49
|
+
if (rootRoute && typeof rootRoute === 'object' && 'meta' in rootRoute && rootRoute.meta) {
|
|
50
|
+
meta = { ...meta, ...rootRoute.meta };
|
|
51
|
+
}
|
|
52
|
+
|
|
48
53
|
let breakFromLayouts = false;
|
|
49
54
|
|
|
50
55
|
outer: for (const route of allRoutes) {
|
|
@@ -76,7 +81,7 @@ export function matchRoute(pathname, routes) {
|
|
|
76
81
|
);
|
|
77
82
|
match = /** @type {RouteComponent} */ (routes[resolvedPath]);
|
|
78
83
|
break outer;
|
|
79
|
-
} else if (routePart !== pathPart?.toLowerCase()) {
|
|
84
|
+
} else if (routePart.toLowerCase() !== pathPart?.toLowerCase()) {
|
|
80
85
|
break;
|
|
81
86
|
}
|
|
82
87
|
|
|
@@ -88,6 +93,10 @@ export function matchRoute(pathname, routes) {
|
|
|
88
93
|
routes[/** @type {keyof Routes} */ ('/' + routeParts.join('/'))]
|
|
89
94
|
);
|
|
90
95
|
|
|
96
|
+
if (typeof routeMatch === 'function' && routeParts.length !== pathParts.length) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
91
100
|
if (!breakFromLayouts && 'layout' in routes && routes.layout) {
|
|
92
101
|
layouts.push(routes.layout);
|
|
93
102
|
}
|
package/src/helpers/utils.js
CHANGED
|
@@ -13,11 +13,12 @@ export function constructPath(path, params) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
if (base.name === '#') {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
if (path === '/') {
|
|
17
|
+
return '/#/';
|
|
18
|
+
}
|
|
19
|
+
return join('#', path);
|
|
20
|
+
} else if (base.name) {
|
|
21
|
+
return join(base.name, path);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
return path;
|