sv-router 0.10.5 → 0.11.1
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 +3 -0
- package/src/create-router.svelte.js +4 -2
- package/src/gen/config.js +8 -6
- package/src/gen/generate-router-code.js +30 -16
- package/src/gen/write-router-code.js +1 -0
- package/src/helpers/is-active.js +4 -6
- package/src/helpers/match-route.js +5 -0
- package/src/helpers/utils.js +6 -5
- 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.11.1",
|
|
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
|
@@ -27,4 +27,7 @@ if (jsArg) genConfig.routesInJs = true;
|
|
|
27
27
|
const pathArg = arg('path');
|
|
28
28
|
if (pathArg) genConfig.routesPath = pathArg;
|
|
29
29
|
|
|
30
|
+
const ignoreArg = arg('ignore');
|
|
31
|
+
if (ignoreArg) genConfig.ignore = ignoreArg.split(',').map((ignore) => new RegExp(ignore, 'gu'));
|
|
32
|
+
|
|
30
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());
|
package/src/gen/config.js
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @type {{
|
|
3
3
|
* allLazy: boolean;
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* ignore: RegExp[];
|
|
5
|
+
* readonly genCodeAlias: string;
|
|
6
6
|
* readonly genCodeDirPath: string;
|
|
7
7
|
* readonly routerPath: string;
|
|
8
8
|
* readonly tsconfigPath: string;
|
|
9
|
-
*
|
|
9
|
+
* routesInJs: boolean;
|
|
10
|
+
* routesPath: string;
|
|
10
11
|
* }}
|
|
11
12
|
*/
|
|
12
13
|
export const genConfig = {
|
|
13
14
|
allLazy: false,
|
|
14
|
-
|
|
15
|
-
routesPath: 'src/routes',
|
|
15
|
+
genCodeAlias: 'sv-router/generated',
|
|
16
16
|
genCodeDirPath: '.router',
|
|
17
|
+
ignore: [],
|
|
17
18
|
get routerPath() {
|
|
18
19
|
return '.router/router.' + (this.routesInJs ? 'js' : 'ts');
|
|
19
20
|
},
|
|
21
|
+
routesInJs: false,
|
|
22
|
+
routesPath: 'src/routes',
|
|
20
23
|
tsconfigPath: '.router/tsconfig.json',
|
|
21
|
-
genCodeAlias: 'sv-router/generated',
|
|
22
24
|
};
|
|
@@ -5,7 +5,7 @@ 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
|
|
|
@@ -18,7 +18,7 @@ const META_FILENAME_REGEX = /(?<=[/.]|^)(meta)(\.svelte)?\.(js|ts)$/; // meta.js
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* @param {string} routesPath
|
|
21
|
-
* @param {{ allLazy?: boolean; js?: boolean }} [options]
|
|
21
|
+
* @param {{ allLazy?: boolean; js?: boolean; ignore?: RegExp[] }} [options]
|
|
22
22
|
* @returns {string}
|
|
23
23
|
*/
|
|
24
24
|
export function generateRouterCode(routesPath, options) {
|
|
@@ -26,29 +26,31 @@ export function generateRouterCode(routesPath, options) {
|
|
|
26
26
|
if (!fs.existsSync(absoluteRoutesPath)) {
|
|
27
27
|
throw new Error(`Routes directory not found at \`${routesPath}\``);
|
|
28
28
|
}
|
|
29
|
-
const fileTree = buildFileTree(absoluteRoutesPath);
|
|
29
|
+
const fileTree = buildFileTree(absoluteRoutesPath, options?.ignore ?? []);
|
|
30
30
|
const routeMap = createRouteMap(fileTree);
|
|
31
31
|
return createRouterCode(routeMap, path.posix.join('..', routesPath), options);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* @param {string} routesPath
|
|
36
|
+
* @param {RegExp[]} ignores
|
|
36
37
|
* @returns {FileTree}
|
|
37
38
|
*/
|
|
38
|
-
export function buildFileTree(routesPath) {
|
|
39
|
+
export function buildFileTree(routesPath, ignores) {
|
|
39
40
|
const entries = fs.readdirSync(routesPath);
|
|
40
41
|
/** @type {FileTree} */
|
|
41
42
|
const tree = [];
|
|
42
43
|
for (const entry of entries) {
|
|
43
44
|
const stat = fs.lstatSync(path.join(routesPath, entry));
|
|
44
45
|
if (stat.isDirectory()) {
|
|
45
|
-
tree.push({ name: entry, tree: buildFileTree(path.join(routesPath, entry)) });
|
|
46
|
+
tree.push({ name: entry, tree: buildFileTree(path.join(routesPath, entry), ignores) });
|
|
46
47
|
continue;
|
|
47
48
|
}
|
|
48
49
|
if (
|
|
49
|
-
!entry.endsWith('.svelte') &&
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
(!entry.endsWith('.svelte') &&
|
|
51
|
+
!HOOKS_FILENAME_REGEX.test(entry) &&
|
|
52
|
+
!META_FILENAME_REGEX.test(entry)) ||
|
|
53
|
+
ignores.some((ignore) => ignore.test(entry))
|
|
52
54
|
) {
|
|
53
55
|
continue;
|
|
54
56
|
}
|
|
@@ -142,22 +144,28 @@ function mergeRouteGroup(result, childMap) {
|
|
|
142
144
|
const layout = childMap.layout;
|
|
143
145
|
const hooks = childMap.hooks;
|
|
144
146
|
const meta = childMap.meta;
|
|
147
|
+
const hasRootRoute = '/' in childMap;
|
|
145
148
|
|
|
146
149
|
for (const [key, val] of Object.entries(childMap)) {
|
|
147
150
|
if (key === 'layout' || key === 'hooks' || key === 'meta') {
|
|
148
151
|
continue;
|
|
149
152
|
}
|
|
150
153
|
|
|
154
|
+
const childMeta =
|
|
155
|
+
typeof val === 'object' && !Array.isArray(val) && 'meta' in val ? val.meta : undefined;
|
|
156
|
+
const mergedMeta =
|
|
157
|
+
childMeta && meta && !hasRootRoute ? [childMeta, meta].flat() : childMeta || meta;
|
|
158
|
+
|
|
151
159
|
/** @type {GeneratedRoutes} */
|
|
152
160
|
let routeWithGroupFiles = {};
|
|
153
161
|
if (typeof val === 'string') {
|
|
154
162
|
routeWithGroupFiles = { '/': val };
|
|
155
|
-
} else {
|
|
163
|
+
} else if (!Array.isArray(val)) {
|
|
156
164
|
routeWithGroupFiles = { ...val };
|
|
157
165
|
}
|
|
158
|
-
if (layout) routeWithGroupFiles.layout = layout;
|
|
159
|
-
if (hooks) routeWithGroupFiles.hooks = hooks;
|
|
160
|
-
if (
|
|
166
|
+
if (layout) routeWithGroupFiles.layout = /** @type {string} */ (layout);
|
|
167
|
+
if (hooks) routeWithGroupFiles.hooks = /** @type {string} */ (hooks);
|
|
168
|
+
if (mergedMeta) routeWithGroupFiles.meta = /** @type {string | string[]} */ (mergedMeta);
|
|
161
169
|
if (result[key]) {
|
|
162
170
|
throw new Error(`Route conflict at \`${key}\``);
|
|
163
171
|
}
|
|
@@ -184,12 +192,18 @@ export function createRouterCode(routes, routesPath, { allLazy = false, js = fal
|
|
|
184
192
|
/** @type {GeneratedRoutes} */
|
|
185
193
|
const result = {};
|
|
186
194
|
for (const [key, value] of Object.entries(routes)) {
|
|
187
|
-
if (typeof value === 'object') {
|
|
195
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
188
196
|
result[key] = handleImports(value, routesPath);
|
|
197
|
+
} else if (key === 'meta' && Array.isArray(value)) {
|
|
198
|
+
const varNames = value.map((metaPath) => {
|
|
199
|
+
const variableName = pathToCorrectCasing(metaPath);
|
|
200
|
+
importsMap.set(variableName, routesPath + metaPath);
|
|
201
|
+
return variableName;
|
|
202
|
+
});
|
|
203
|
+
result[key] = `{ ...${varNames.toReversed().join(', ...')} }`;
|
|
189
204
|
} else if (
|
|
190
|
-
|
|
191
|
-
key === 'meta' ||
|
|
192
|
-
(!value.endsWith('.lazy.svelte') && !allLazy)
|
|
205
|
+
typeof value === 'string' &&
|
|
206
|
+
(key === 'hooks' || key === 'meta' || (!value.endsWith('.lazy.svelte') && !allLazy))
|
|
193
207
|
) {
|
|
194
208
|
const variableName = pathToCorrectCasing(value);
|
|
195
209
|
importsMap.set(variableName, routesPath + value);
|
|
@@ -35,6 +35,7 @@ export function writeRouterCode() {
|
|
|
35
35
|
const routerCode = generateRouterCode(genConfig.routesPath, {
|
|
36
36
|
allLazy: genConfig.allLazy,
|
|
37
37
|
js: genConfig.routesInJs,
|
|
38
|
+
ignore: genConfig.ignore,
|
|
38
39
|
});
|
|
39
40
|
const written = writeFileIfDifferent(genConfig.routerPath, routerCode);
|
|
40
41
|
|
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) {
|
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;
|
|
@@ -19,6 +19,12 @@ export type RouterOptions = {
|
|
|
19
19
|
* @default 'src/routes'
|
|
20
20
|
*/
|
|
21
21
|
path?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Regular expressions for files to ignore when generating routes.
|
|
24
|
+
*
|
|
25
|
+
* @default empty array
|
|
26
|
+
*/
|
|
27
|
+
ignore?: RegExp[];
|
|
22
28
|
};
|
|
23
29
|
|
|
24
30
|
export const router: (options?: RouterOptions) => Plugin;
|
|
@@ -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.ignore = options?.ignore || [];
|
|
11
12
|
genConfig.routesInJs = options?.js || false;
|
|
12
13
|
genConfig.routesPath = options?.path || 'src/routes';
|
|
13
14
|
|