sv-router 0.1.0 → 0.3.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 +13 -13
- package/src/Router.svelte +14 -1
- package/src/cli/index.js +5 -2
- package/src/create-router.svelte.js +16 -11
- package/src/gen/config.js +4 -2
- package/src/gen/generate-router-code.js +6 -4
- package/src/gen/write-router-code.js +1 -1
- package/src/helpers/match-route.js +2 -0
- package/src/helpers/utils.js +15 -0
- package/src/index.d.ts +6 -3
- package/src/vite-plugin/index.d.ts +9 -3
- package/src/vite-plugin/plugin.js +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sv-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Modern Svelte Routing",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -37,23 +37,23 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@changesets/cli": "^2.28.1",
|
|
40
|
-
"@eslint/js": "^9.
|
|
41
|
-
"@types/node": "^22.13.
|
|
42
|
-
"eslint": "^9.
|
|
43
|
-
"eslint-config-prettier": "^10.
|
|
40
|
+
"@eslint/js": "^9.23.0",
|
|
41
|
+
"@types/node": "^22.13.14",
|
|
42
|
+
"eslint": "^9.23.0",
|
|
43
|
+
"eslint-config-prettier": "^10.1.1",
|
|
44
44
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
45
|
-
"eslint-plugin-svelte": "^3.
|
|
46
|
-
"eslint-plugin-unicorn": "^
|
|
45
|
+
"eslint-plugin-svelte": "^3.3.3",
|
|
46
|
+
"eslint-plugin-unicorn": "^58.0.0",
|
|
47
47
|
"globals": "^16.0.0",
|
|
48
|
-
"prettier": "^3.5.
|
|
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.
|
|
51
|
+
"svelte-check": "^4.1.5",
|
|
52
52
|
"type-testing": "^0.2.0",
|
|
53
53
|
"typescript": "^5.8.2",
|
|
54
|
-
"typescript-eslint": "^8.
|
|
55
|
-
"vite": "^6.2.
|
|
56
|
-
"vitest": "^3.0.
|
|
54
|
+
"typescript-eslint": "^8.28.0",
|
|
55
|
+
"vite": "^6.2.3",
|
|
56
|
+
"vitest": "^3.0.9"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"svelte": "^5"
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"docs:preview": "pnpm --filter docs preview",
|
|
65
65
|
"test": "vitest",
|
|
66
66
|
"check": "svelte-check && pnpm -r check",
|
|
67
|
-
"lint": "eslint .",
|
|
67
|
+
"lint": "eslint . --max-warnings 0",
|
|
68
68
|
"lint:fix": "eslint . --fix",
|
|
69
69
|
"format": "prettier . --check",
|
|
70
70
|
"format:fix": "prettier . --write",
|
package/src/Router.svelte
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { on } from 'svelte/events';
|
|
3
|
-
import { componentTree, onGlobalClick, onNavigate } from './create-router.svelte.js';
|
|
3
|
+
import { base, componentTree, onGlobalClick, onNavigate } from './create-router.svelte.js';
|
|
4
|
+
import { join } from './helpers/utils.js';
|
|
4
5
|
import RecursiveComponentTree from './RecursiveComponentTree.svelte';
|
|
5
6
|
|
|
7
|
+
/** @type {{ base?: string }} */
|
|
8
|
+
let { base: basename } = $props();
|
|
9
|
+
|
|
10
|
+
if (basename) {
|
|
11
|
+
base.name = (basename.startsWith('/') ? '' : '/') + basename;
|
|
12
|
+
const url = new URL(globalThis.location.href);
|
|
13
|
+
if (!url.pathname.startsWith(base.name)) {
|
|
14
|
+
url.pathname = join(base.name, url.pathname);
|
|
15
|
+
history.replaceState(history.state || {}, '', url.href);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
6
19
|
onNavigate();
|
|
7
20
|
|
|
8
21
|
$effect(() => {
|
package/src/cli/index.js
CHANGED
|
@@ -18,10 +18,13 @@ function arg(option) {
|
|
|
18
18
|
return args[pathArgIndex];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
21
|
+
const allLazyArg = arg('allLazy');
|
|
22
|
+
if (allLazyArg) genConfig.allLazy = true;
|
|
23
23
|
|
|
24
24
|
const jsArg = arg('js');
|
|
25
25
|
if (jsArg) genConfig.routesInJs = true;
|
|
26
26
|
|
|
27
|
+
const pathArg = arg('path');
|
|
28
|
+
if (pathArg) genConfig.routesPath = pathArg;
|
|
29
|
+
|
|
27
30
|
writeRouterCode();
|
|
@@ -2,11 +2,11 @@ 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
4
|
import { preloadOnHover } from './helpers/preload-on-hover.js';
|
|
5
|
-
import { constructPath, resolveRouteComponents } from './helpers/utils.js';
|
|
5
|
+
import { constructPath, join, resolveRouteComponents } from './helpers/utils.js';
|
|
6
6
|
import { syncSearchParams } from './search-params.svelte.js';
|
|
7
7
|
|
|
8
8
|
/** @type {import('./index.d.ts').Routes} */
|
|
9
|
-
|
|
9
|
+
let routes;
|
|
10
10
|
|
|
11
11
|
/** @type {{ value: import('svelte').Component[] }} */
|
|
12
12
|
export let componentTree = $state({ value: [] });
|
|
@@ -16,6 +16,11 @@ export let params = $state({ value: {} });
|
|
|
16
16
|
|
|
17
17
|
export let location = $state(updatedLocation());
|
|
18
18
|
|
|
19
|
+
/** @type {{ name?: string }} */
|
|
20
|
+
export const base = {
|
|
21
|
+
name: undefined,
|
|
22
|
+
};
|
|
23
|
+
|
|
19
24
|
/**
|
|
20
25
|
* @template {import('./index.d.ts').Routes} T
|
|
21
26
|
* @param {T} r
|
|
@@ -41,7 +46,7 @@ export function createRouter(r) {
|
|
|
41
46
|
return params.value;
|
|
42
47
|
},
|
|
43
48
|
get pathname() {
|
|
44
|
-
return location.pathname;
|
|
49
|
+
return /** @type {import('./index.d.ts').Path<T>} */ (location.pathname);
|
|
45
50
|
},
|
|
46
51
|
get search() {
|
|
47
52
|
return location.search;
|
|
@@ -85,12 +90,11 @@ export async function onNavigate(path, options = {}) {
|
|
|
85
90
|
if (!routes) {
|
|
86
91
|
throw new Error('Router not initialized: `createRouter` was not called.');
|
|
87
92
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
} = matchRoute(path || globalThis.location.pathname, routes);
|
|
93
|
+
let matchPath = path || globalThis.location.pathname;
|
|
94
|
+
if (base.name && matchPath.startsWith(base.name)) {
|
|
95
|
+
matchPath = matchPath.slice(base.name.length) || '/';
|
|
96
|
+
}
|
|
97
|
+
const { match, layouts, hooks, params: newParams } = matchRoute(matchPath, routes);
|
|
94
98
|
|
|
95
99
|
for (const { beforeLoad } of hooks) {
|
|
96
100
|
try {
|
|
@@ -107,7 +111,8 @@ export async function onNavigate(path, options = {}) {
|
|
|
107
111
|
if (options.search) path += options.search;
|
|
108
112
|
if (options.hash) path += options.hash;
|
|
109
113
|
const historyMethod = options.replace ? 'replaceState' : 'pushState';
|
|
110
|
-
|
|
114
|
+
const to = base.name ? join(base.name, path) : path;
|
|
115
|
+
globalThis.history[historyMethod](options.state || {}, '', to);
|
|
111
116
|
}
|
|
112
117
|
|
|
113
118
|
syncSearchParams();
|
|
@@ -148,7 +153,7 @@ function updatedLocation() {
|
|
|
148
153
|
return {
|
|
149
154
|
pathname: globalThis.location.pathname,
|
|
150
155
|
search: globalThis.location.search,
|
|
151
|
-
state:
|
|
156
|
+
state: history.state,
|
|
152
157
|
hash: globalThis.location.hash,
|
|
153
158
|
};
|
|
154
159
|
}
|
package/src/gen/config.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @type {{
|
|
3
|
-
*
|
|
3
|
+
* allLazy: boolean;
|
|
4
4
|
* routesInJs: boolean;
|
|
5
|
+
* routesPath: string;
|
|
5
6
|
* readonly genCodeDirPath: string;
|
|
6
7
|
* readonly routerPath: string;
|
|
7
8
|
* readonly tsconfigPath: string;
|
|
@@ -9,8 +10,9 @@
|
|
|
9
10
|
* }}
|
|
10
11
|
*/
|
|
11
12
|
export const genConfig = {
|
|
12
|
-
|
|
13
|
+
allLazy: false,
|
|
13
14
|
routesInJs: false,
|
|
15
|
+
routesPath: 'src/routes',
|
|
14
16
|
genCodeDirPath: '.router',
|
|
15
17
|
get routerPath() {
|
|
16
18
|
return '.router/router.' + (this.routesInJs ? 'js' : 'ts');
|
|
@@ -17,16 +17,17 @@ const HOOKS_FILENAME_REGEX = /(?<=[/.]|^)(hooks)(\.svelte)?\.(js|ts)$/; // hooks
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* @param {string} routesPath
|
|
20
|
+
* @param {{ allLazy?: boolean }} [options]
|
|
20
21
|
* @returns {string}
|
|
21
22
|
*/
|
|
22
|
-
export function generateRouterCode(routesPath) {
|
|
23
|
+
export function generateRouterCode(routesPath, options) {
|
|
23
24
|
const absoluteRoutesPath = path.join(process.cwd(), routesPath);
|
|
24
25
|
if (!fs.existsSync(absoluteRoutesPath)) {
|
|
25
26
|
throw new Error(`Routes directory not found at \`${routesPath}\``);
|
|
26
27
|
}
|
|
27
28
|
const fileTree = buildFileTree(absoluteRoutesPath);
|
|
28
29
|
const routeMap = createRouteMap(fileTree);
|
|
29
|
-
return createRouterCode(routeMap, path.posix.join('..', routesPath));
|
|
30
|
+
return createRouterCode(routeMap, path.posix.join('..', routesPath), options);
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -119,9 +120,10 @@ function filePathToRoute(filename) {
|
|
|
119
120
|
/**
|
|
120
121
|
* @param {GeneratedRoutes} routes
|
|
121
122
|
* @param {string} routesPath
|
|
123
|
+
* @param {{ allLazy?: boolean }} [options]
|
|
122
124
|
* @returns {string}
|
|
123
125
|
*/
|
|
124
|
-
export function createRouterCode(routes, routesPath) {
|
|
126
|
+
export function createRouterCode(routes, routesPath, { allLazy = false } = {}) {
|
|
125
127
|
if (!routesPath.endsWith('/')) {
|
|
126
128
|
routesPath += '/';
|
|
127
129
|
}
|
|
@@ -135,7 +137,7 @@ export function createRouterCode(routes, routesPath) {
|
|
|
135
137
|
for (const [key, value] of Object.entries(routes)) {
|
|
136
138
|
if (typeof value === 'object') {
|
|
137
139
|
result[key] = handleImports(value, routesPath);
|
|
138
|
-
} else if (key === 'hooks' || !value.endsWith('.lazy.svelte')) {
|
|
140
|
+
} else if (key === 'hooks' || (!value.endsWith('.lazy.svelte') && !allLazy)) {
|
|
139
141
|
const variableName = pathToCorrectCasing(value);
|
|
140
142
|
importsMap.set(variableName, routesPath + value);
|
|
141
143
|
result[key] = variableName;
|
|
@@ -32,7 +32,7 @@ export function writeRouterCode() {
|
|
|
32
32
|
writeFileIfDifferent(genConfig.tsconfigPath, JSON.stringify(tsConfig, undefined, 2));
|
|
33
33
|
|
|
34
34
|
// Write `.router/router.ts` file
|
|
35
|
-
const routerCode = generateRouterCode(genConfig.routesPath);
|
|
35
|
+
const routerCode = generateRouterCode(genConfig.routesPath, { allLazy: genConfig.allLazy });
|
|
36
36
|
const written = writeFileIfDifferent(genConfig.routerPath, routerCode);
|
|
37
37
|
|
|
38
38
|
if (written) {
|
|
@@ -61,6 +61,8 @@ export function matchRoute(pathname, routes) {
|
|
|
61
61
|
}
|
|
62
62
|
if (breakFromLayouts) {
|
|
63
63
|
routePart = `(${routePart})`;
|
|
64
|
+
} else if ('layout' in routes && routes.layout) {
|
|
65
|
+
layouts.push(routes.layout);
|
|
64
66
|
}
|
|
65
67
|
const resolvedPath = /** @type {keyof Routes} */ (
|
|
66
68
|
(index ? '/' : '') + routeParts.join('/')
|
package/src/helpers/utils.js
CHANGED
|
@@ -44,3 +44,18 @@ export function resolveRouteComponent(input) {
|
|
|
44
44
|
export function isLazyImport(input) {
|
|
45
45
|
return typeof input === 'function' && !!/\(\)\s?=>\s?import\(.*\)/.test(String(input));
|
|
46
46
|
}
|
|
47
|
+
|
|
48
|
+
/** @param {...string} parts */
|
|
49
|
+
export function join(...parts) {
|
|
50
|
+
let result = '';
|
|
51
|
+
for (let part of parts) {
|
|
52
|
+
if (!part.startsWith('/')) {
|
|
53
|
+
result += '/';
|
|
54
|
+
}
|
|
55
|
+
if (part.endsWith('/')) {
|
|
56
|
+
part = part.slice(0, -1);
|
|
57
|
+
}
|
|
58
|
+
result += part;
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -23,8 +23,11 @@ export const isActiveLink: IsActiveLink;
|
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
25
|
export function createRouter<T extends Routes>(r: T): RouterApi<T>;
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The component that will render the current route. You can pass a `base` prop to set the base path
|
|
28
|
+
* that is prepended to every url.
|
|
29
|
+
*/
|
|
30
|
+
export const Router: Component<{ base?: string }>;
|
|
28
31
|
/**
|
|
29
32
|
* The reactive search params of the URL. It is just a wrapper around `SvelteURLSearchParam` that
|
|
30
33
|
* will update the url on change.
|
|
@@ -131,7 +134,7 @@ export type RouterApi<T extends Routes> = {
|
|
|
131
134
|
*/
|
|
132
135
|
params: AllParams<T>;
|
|
133
136
|
/** The reactive pathname of the URL. */
|
|
134
|
-
pathname:
|
|
137
|
+
pathname: Path<T>;
|
|
135
138
|
/** The reactive query string part of the URL. */
|
|
136
139
|
search: string;
|
|
137
140
|
/** The reactive history state that can be passed to the `navigate` function. */
|
|
@@ -2,17 +2,23 @@ import type { Plugin } from 'vite';
|
|
|
2
2
|
|
|
3
3
|
export type RouterOptions = {
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* If true, all routes will be lazy loaded by default.
|
|
6
6
|
*
|
|
7
|
-
* @default
|
|
7
|
+
* @default false
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
allLazy?: boolean;
|
|
10
10
|
/**
|
|
11
11
|
* If true, generates the routes in a .js file instead of a .ts file.
|
|
12
12
|
*
|
|
13
13
|
* @default false
|
|
14
14
|
*/
|
|
15
15
|
js?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The path to the routes folder.
|
|
18
|
+
*
|
|
19
|
+
* @default 'src/routes'
|
|
20
|
+
*/
|
|
21
|
+
path?: string;
|
|
16
22
|
};
|
|
17
23
|
|
|
18
24
|
export const router: (options?: RouterOptions) => Plugin;
|
|
@@ -7,8 +7,9 @@ import { writeRouterCode } from '../gen/write-router-code.js';
|
|
|
7
7
|
* @returns {import('vite').Plugin}
|
|
8
8
|
*/
|
|
9
9
|
export function router(options) {
|
|
10
|
-
if (options?.
|
|
10
|
+
if (options?.allLazy) genConfig.allLazy = options.allLazy;
|
|
11
11
|
if (options?.js) genConfig.routesInJs = options.js;
|
|
12
|
+
if (options?.path) genConfig.routesPath = options.path;
|
|
12
13
|
|
|
13
14
|
return {
|
|
14
15
|
name: 'sv-router',
|