sv-router 0.16.3 → 0.18.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 +15 -14
- package/src/cli/index.js +4 -0
- package/src/create-router.svelte.js +62 -32
- package/src/gen/config.js +2 -0
- package/src/gen/generate-router-code.js +10 -7
- package/src/gen/write-router-code.js +10 -7
- package/src/helpers/is-active.js +2 -3
- package/src/helpers/match-route.js +1 -3
- package/src/helpers/preload.js +6 -3
- package/src/helpers/utils.js +15 -16
- package/src/index.d.ts +29 -6
- package/src/search-params.svelte.js +4 -3
- 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.18.0",
|
|
4
4
|
"description": "Type-safe routing for Svelte SPAs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"router",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"src"
|
|
21
21
|
],
|
|
22
22
|
"type": "module",
|
|
23
|
+
"sideEffects": false,
|
|
23
24
|
"exports": {
|
|
24
25
|
".": {
|
|
25
26
|
"import": "./src/index.js",
|
|
@@ -39,24 +40,24 @@
|
|
|
39
40
|
"@eslint/js": "^10.0.1",
|
|
40
41
|
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
|
41
42
|
"@testing-library/jest-dom": "^6.9.1",
|
|
42
|
-
"@testing-library/svelte": "^5.
|
|
43
|
+
"@testing-library/svelte": "^5.4.2",
|
|
43
44
|
"@testing-library/user-event": "^14.6.1",
|
|
44
|
-
"@types/node": "^24.
|
|
45
|
-
"@vitest/coverage-v8": "^4.1.
|
|
46
|
-
"eslint": "^10.
|
|
45
|
+
"@types/node": "^24.13.2",
|
|
46
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
47
|
+
"eslint": "^10.6.0",
|
|
47
48
|
"eslint-config-prettier": "^10.1.8",
|
|
48
49
|
"eslint-plugin-simple-import-sort": "^13.0.0",
|
|
49
|
-
"eslint-plugin-svelte": "^3.
|
|
50
|
-
"eslint-plugin-unicorn": "^
|
|
51
|
-
"globals": "^17.
|
|
52
|
-
"happy-dom": "^20.
|
|
53
|
-
"oxfmt": "^0.
|
|
54
|
-
"svelte-check": "^4.
|
|
50
|
+
"eslint-plugin-svelte": "^3.20.0",
|
|
51
|
+
"eslint-plugin-unicorn": "^70.0.0",
|
|
52
|
+
"globals": "^17.7.0",
|
|
53
|
+
"happy-dom": "^20.10.6",
|
|
54
|
+
"oxfmt": "^0.57.0",
|
|
55
|
+
"svelte-check": "^4.7.1",
|
|
55
56
|
"type-testing": "^0.2.0",
|
|
56
57
|
"typescript": "^6.0.3",
|
|
57
|
-
"typescript-eslint": "^8.
|
|
58
|
-
"vite": "^8.
|
|
59
|
-
"vitest": "^4.1.
|
|
58
|
+
"typescript-eslint": "^8.62.1",
|
|
59
|
+
"vite": "^8.1.3",
|
|
60
|
+
"vitest": "^4.1.9"
|
|
60
61
|
},
|
|
61
62
|
"peerDependencies": {
|
|
62
63
|
"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
|
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
stripBase,
|
|
13
13
|
updatedLocation,
|
|
14
14
|
} from './helpers/utils.js';
|
|
15
|
+
import { validateRoutes } from './helpers/validate-routes.js';
|
|
15
16
|
import { Navigation } from './navigation.js';
|
|
16
17
|
import { syncSearchParams } from './search-params.svelte.js';
|
|
17
18
|
|
|
@@ -53,7 +54,7 @@ let meta = $state({ value: {} });
|
|
|
53
54
|
const navigationBlockers = new Map();
|
|
54
55
|
|
|
55
56
|
let historyIndex = 0;
|
|
56
|
-
let
|
|
57
|
+
let isSkipNextPopstate = false;
|
|
57
58
|
|
|
58
59
|
/** @type {AbortController | null} */
|
|
59
60
|
let currentNavigationController = null;
|
|
@@ -62,30 +63,42 @@ let pendingController = /** @type {AbortController | null} */ (null);
|
|
|
62
63
|
/** @type {Promise<void | null> | null} */
|
|
63
64
|
let currentNavigationPromise = null;
|
|
64
65
|
|
|
66
|
+
/** @param {string} basename */
|
|
67
|
+
function setBase(basename) {
|
|
68
|
+
const url = new URL(globalThis.location.toString());
|
|
69
|
+
if (basename === '#') {
|
|
70
|
+
base.name = '#';
|
|
71
|
+
if (!globalThis.location.href.includes('#')) {
|
|
72
|
+
url.hash = '/';
|
|
73
|
+
history.replaceState(
|
|
74
|
+
{ _routerIndex: historyIndex, _userState: history.state ?? null },
|
|
75
|
+
'',
|
|
76
|
+
url.href,
|
|
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.href,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
Object.assign(location, updatedLocation());
|
|
91
|
+
}
|
|
92
|
+
|
|
65
93
|
/** @param {string | undefined} basename */
|
|
66
94
|
export function init(basename) {
|
|
67
95
|
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
|
-
}
|
|
96
|
+
if (base.name === undefined) {
|
|
97
|
+
setBase(basename);
|
|
98
|
+
} else if (DEV && base.name !== basename && `/${basename}` !== base.name) {
|
|
99
|
+
console.warn(
|
|
100
|
+
'sv-router: the `base` prop on `<Router>` is ignored because a base was already set in `createRouter`.',
|
|
101
|
+
);
|
|
89
102
|
}
|
|
90
103
|
}
|
|
91
104
|
if (history.state?._routerIndex === undefined) {
|
|
@@ -103,15 +116,18 @@ export function init(basename) {
|
|
|
103
116
|
/**
|
|
104
117
|
* @template {import('./index.d.ts').Routes} T
|
|
105
118
|
* @param {T} r
|
|
119
|
+
* @param {import('./index.d.ts').CreateRouterOptions} [options]
|
|
106
120
|
* @returns {import('./index.d.ts').RouterApi<T>}
|
|
107
121
|
*/
|
|
108
|
-
export function createRouter(r) {
|
|
122
|
+
export function createRouter(r, options = {}) {
|
|
109
123
|
routes = r;
|
|
110
124
|
|
|
125
|
+
if (BROWSER && options.base) {
|
|
126
|
+
setBase(options.base);
|
|
127
|
+
}
|
|
128
|
+
|
|
111
129
|
if (DEV && BROWSER) {
|
|
112
|
-
|
|
113
|
-
validateRoutes(routes);
|
|
114
|
-
});
|
|
130
|
+
validateRoutes(routes);
|
|
115
131
|
}
|
|
116
132
|
|
|
117
133
|
preloadOnHover(routes);
|
|
@@ -123,6 +139,9 @@ export function createRouter(r) {
|
|
|
123
139
|
async preload(pathname) {
|
|
124
140
|
await preload(routes, pathname);
|
|
125
141
|
},
|
|
142
|
+
resolveMeta(pathname) {
|
|
143
|
+
return matchRoute(pathname, routes).meta;
|
|
144
|
+
},
|
|
126
145
|
route: {
|
|
127
146
|
get params() {
|
|
128
147
|
return params.value;
|
|
@@ -161,7 +180,7 @@ export function createRouter(r) {
|
|
|
161
180
|
*/
|
|
162
181
|
async function navigate(path, options = {}) {
|
|
163
182
|
if (typeof path === 'number') {
|
|
164
|
-
|
|
183
|
+
history.go(path);
|
|
165
184
|
return new Navigation(`History entry: ${path}`);
|
|
166
185
|
}
|
|
167
186
|
|
|
@@ -196,8 +215,8 @@ export async function onNavigate(path, options = {}) {
|
|
|
196
215
|
throw new Error('Router not initialized: `createRouter` was not called.');
|
|
197
216
|
}
|
|
198
217
|
|
|
199
|
-
if (!path &&
|
|
200
|
-
|
|
218
|
+
if (!path && isSkipNextPopstate) {
|
|
219
|
+
isSkipNextPopstate = false;
|
|
201
220
|
return;
|
|
202
221
|
}
|
|
203
222
|
|
|
@@ -207,7 +226,7 @@ export async function onNavigate(path, options = {}) {
|
|
|
207
226
|
const shouldNavigate = typeof blocker === 'object' ? blocker.onNavigate : blocker;
|
|
208
227
|
if (!(await shouldNavigate())) {
|
|
209
228
|
if (!path && popstateDelta !== 0) {
|
|
210
|
-
|
|
229
|
+
isSkipNextPopstate = true;
|
|
211
230
|
history.go(popstateDelta);
|
|
212
231
|
}
|
|
213
232
|
return;
|
|
@@ -277,10 +296,10 @@ export async function onNavigate(path, options = {}) {
|
|
|
277
296
|
}
|
|
278
297
|
const historyMethod = options.replace ? 'replaceState' : 'pushState';
|
|
279
298
|
if (historyMethod === 'pushState') historyIndex++;
|
|
280
|
-
|
|
299
|
+
history[historyMethod](
|
|
281
300
|
{ _routerIndex: historyIndex, _userState: options.state ?? null },
|
|
282
301
|
'',
|
|
283
|
-
url.
|
|
302
|
+
url.href,
|
|
284
303
|
);
|
|
285
304
|
syncSearchParams(search);
|
|
286
305
|
} else {
|
|
@@ -328,6 +347,17 @@ function getMatchPath(path) {
|
|
|
328
347
|
|
|
329
348
|
/** @param {Event} event */
|
|
330
349
|
export function onGlobalClick(event) {
|
|
350
|
+
const mouseEvent = /** @type {MouseEvent} */ (event);
|
|
351
|
+
if (
|
|
352
|
+
mouseEvent.button !== 0 ||
|
|
353
|
+
mouseEvent.metaKey ||
|
|
354
|
+
mouseEvent.ctrlKey ||
|
|
355
|
+
mouseEvent.shiftKey ||
|
|
356
|
+
mouseEvent.altKey ||
|
|
357
|
+
mouseEvent.defaultPrevented
|
|
358
|
+
)
|
|
359
|
+
return;
|
|
360
|
+
|
|
331
361
|
const anchor = /** @type {HTMLElement} */ (event.target).closest('a');
|
|
332
362
|
if (!anchor) return;
|
|
333
363
|
|
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: [],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable unicorn/no-unsafe-string-replacement */
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
|
|
@@ -19,7 +20,7 @@ const META_FILENAME_REGEX = /(?<=[/.]|^)(meta)(\.svelte)?\.(js|ts)$/; // meta.js
|
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* @param {string} routesPath
|
|
22
|
-
* @param {{ allLazy?: boolean; js?: boolean; ignore?: RegExp[] }} [options]
|
|
23
|
+
* @param {{ allLazy?: boolean; base?: string; js?: boolean; ignore?: RegExp[] }} [options]
|
|
23
24
|
* @returns {string}
|
|
24
25
|
*/
|
|
25
26
|
export function generateRouterCode(routesPath, options) {
|
|
@@ -149,7 +150,7 @@ function mergeRouteGroup(result, childMap) {
|
|
|
149
150
|
const hasRootRoute = '/' in childMap;
|
|
150
151
|
|
|
151
152
|
for (const [key, val] of Object.entries(childMap)) {
|
|
152
|
-
if (
|
|
153
|
+
if (['layout', 'hooks', 'meta'].includes(key)) {
|
|
153
154
|
continue;
|
|
154
155
|
}
|
|
155
156
|
|
|
@@ -174,7 +175,7 @@ function mergeRouteGroup(result, childMap) {
|
|
|
174
175
|
}
|
|
175
176
|
if (hooks) routeWithGroupFiles.hooks = hooks;
|
|
176
177
|
if (mergedMeta) routeWithGroupFiles.meta = /** @type {string | string[]} */ (mergedMeta);
|
|
177
|
-
if (result
|
|
178
|
+
if (Object.hasOwn(result, key)) {
|
|
178
179
|
throw new Error(`Route conflict at \`${key}\``);
|
|
179
180
|
}
|
|
180
181
|
|
|
@@ -185,10 +186,10 @@ function mergeRouteGroup(result, childMap) {
|
|
|
185
186
|
/**
|
|
186
187
|
* @param {GeneratedRoutes} routes
|
|
187
188
|
* @param {string} routesPath
|
|
188
|
-
* @param {{ allLazy?: boolean; js?: boolean }} [options]
|
|
189
|
+
* @param {{ allLazy?: boolean; base?: string; js?: boolean }} [options]
|
|
189
190
|
* @returns {string}
|
|
190
191
|
*/
|
|
191
|
-
export function createRouterCode(routes, routesPath, { allLazy = false, js = false } = {}) {
|
|
192
|
+
export function createRouterCode(routes, routesPath, { allLazy = false, base, js = false } = {}) {
|
|
192
193
|
if (!routesPath.endsWith('/')) {
|
|
193
194
|
routesPath += '/';
|
|
194
195
|
}
|
|
@@ -223,7 +224,7 @@ export function createRouterCode(routes, routesPath, { allLazy = false, js = fal
|
|
|
223
224
|
return result;
|
|
224
225
|
})(routes, routesPath);
|
|
225
226
|
|
|
226
|
-
const imports = [...importsMap
|
|
227
|
+
const imports = [...importsMap].map(([key, value]) => {
|
|
227
228
|
if (value.endsWith('.ts')) {
|
|
228
229
|
value = value.replace('.ts', '');
|
|
229
230
|
}
|
|
@@ -240,7 +241,9 @@ export function createRouterCode(routes, routesPath, { allLazy = false, js = fal
|
|
|
240
241
|
'',
|
|
241
242
|
`export const routes = ${stringifiedRoutes};`,
|
|
242
243
|
...(js ? [] : ['export type Routes = typeof routes;']),
|
|
243
|
-
|
|
244
|
+
`export const { p, navigate, isActive, preload, resolveMeta, route } = createRouter(routes${
|
|
245
|
+
base === undefined ? '' : `, { base: '${base}' }`
|
|
246
|
+
});`,
|
|
244
247
|
'',
|
|
245
248
|
].join('\n');
|
|
246
249
|
}
|
|
@@ -12,16 +12,16 @@ export function writeRouterCode() {
|
|
|
12
12
|
|
|
13
13
|
// Write `.router/tsconfig.json` file
|
|
14
14
|
const tsMajor = getTypeScriptMajorVersion();
|
|
15
|
-
const
|
|
15
|
+
const isBaseUrl = tsMajor === undefined || tsMajor < 6;
|
|
16
16
|
let alias = genConfig.routerPath;
|
|
17
|
-
if (!
|
|
17
|
+
if (!isBaseUrl) {
|
|
18
18
|
alias = alias.replace(genConfig.genCodeDirPath, '.');
|
|
19
19
|
}
|
|
20
20
|
const tsConfig = {
|
|
21
21
|
compilerOptions: {
|
|
22
22
|
module: 'preserve',
|
|
23
23
|
moduleResolution: 'bundler',
|
|
24
|
-
...(
|
|
24
|
+
...(isBaseUrl && { baseUrl: '..' }),
|
|
25
25
|
paths: { [genConfig.genCodeAlias]: [alias] },
|
|
26
26
|
},
|
|
27
27
|
include: [
|
|
@@ -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
|
});
|
|
@@ -62,10 +63,12 @@ export function writeRouterCode() {
|
|
|
62
63
|
* @param {string} content
|
|
63
64
|
*/
|
|
64
65
|
function writeFileIfDifferent(filePath, content) {
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
return true;
|
|
66
|
+
if (fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf8') === content) {
|
|
67
|
+
return;
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
fs.writeFileSync(filePath, content);
|
|
71
|
+
return true;
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
function getTypeScriptMajorVersion() {
|
|
@@ -73,7 +76,7 @@ function getTypeScriptMajorVersion() {
|
|
|
73
76
|
const require = createRequire(process.cwd() + '/package.json');
|
|
74
77
|
const tsPackagePath = require.resolve('typescript/package.json');
|
|
75
78
|
const tsPackage = JSON.parse(fs.readFileSync(tsPackagePath, 'utf8'));
|
|
76
|
-
return Number(tsPackage.version.split('.')[0]);
|
|
79
|
+
return Number(tsPackage.version.split('.', 1)[0]);
|
|
77
80
|
} catch {
|
|
78
81
|
return;
|
|
79
82
|
}
|
package/src/helpers/is-active.js
CHANGED
|
@@ -34,9 +34,8 @@ function compare(compareFn, pathname, params) {
|
|
|
34
34
|
if (params) {
|
|
35
35
|
if (base.name === '#') {
|
|
36
36
|
return compareFn(location.pathname, constructPath(pathname, params).replace('/#', ''));
|
|
37
|
-
} else {
|
|
38
|
-
return compareFn(location.pathname, constructPath(pathname, params));
|
|
39
37
|
}
|
|
38
|
+
return compareFn(location.pathname, constructPath(pathname, params));
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
const pathParts = pathname.split('/').slice(1);
|
|
@@ -45,10 +44,10 @@ function compare(compareFn, pathname, params) {
|
|
|
45
44
|
return false;
|
|
46
45
|
}
|
|
47
46
|
for (const [index, pathPart] of pathParts.entries()) {
|
|
48
|
-
const routePart = routeParts[index];
|
|
49
47
|
if (pathPart.startsWith(':')) {
|
|
50
48
|
continue;
|
|
51
49
|
}
|
|
50
|
+
const routePart = routeParts[index];
|
|
52
51
|
if (pathPart !== routePart) {
|
|
53
52
|
return false;
|
|
54
53
|
}
|
|
@@ -84,11 +84,9 @@ function tryMatch(route, pathParts, pathname, routes, baseMeta) {
|
|
|
84
84
|
|
|
85
85
|
/** @type {Record<string, string>} */
|
|
86
86
|
const params = {};
|
|
87
|
-
/** @type {boolean} */
|
|
88
|
-
let breakFromLayouts;
|
|
89
87
|
|
|
90
88
|
for (let [index, routePart] of routeParts.entries()) {
|
|
91
|
-
breakFromLayouts = routePart.startsWith('(') && routePart.endsWith(')');
|
|
89
|
+
const breakFromLayouts = routePart.startsWith('(') && routePart.endsWith(')');
|
|
92
90
|
if (breakFromLayouts) {
|
|
93
91
|
routePart = routePart.slice(1, -1);
|
|
94
92
|
}
|
package/src/helpers/preload.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable unicorn/no-break-in-nested-loop */
|
|
1
2
|
import { matchRoute } from './match-route.js';
|
|
2
3
|
import { parseSearch, resolveRouteComponents, stripBase } from './utils.js';
|
|
3
4
|
|
|
@@ -139,10 +140,12 @@ export function preloadOnHover(routes) {
|
|
|
139
140
|
|
|
140
141
|
const intersectionObserver = new IntersectionObserver((entries) => {
|
|
141
142
|
for (const entry of entries) {
|
|
142
|
-
if (entry.isIntersecting) {
|
|
143
|
-
|
|
144
|
-
anchorPreload(/** @type {HTMLAnchorElement} */ (entry.target));
|
|
143
|
+
if (!entry.isIntersecting) {
|
|
144
|
+
continue;
|
|
145
145
|
}
|
|
146
|
+
|
|
147
|
+
intersectionObserver.unobserve(entry.target);
|
|
148
|
+
anchorPreload(/** @type {HTMLAnchorElement} */ (entry.target));
|
|
146
149
|
}
|
|
147
150
|
});
|
|
148
151
|
|
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
|
|
|
@@ -17,7 +17,8 @@ export function constructPath(path, params) {
|
|
|
17
17
|
return '/#/';
|
|
18
18
|
}
|
|
19
19
|
return join('#', path);
|
|
20
|
-
}
|
|
20
|
+
}
|
|
21
|
+
if (base.name) {
|
|
21
22
|
return join(base.name, path);
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -54,16 +55,15 @@ export function resolveRouteComponents(input) {
|
|
|
54
55
|
* @param {import('../index.d.ts').RouteComponent} input
|
|
55
56
|
* @returns {Promise<import('svelte').Component>}
|
|
56
57
|
*/
|
|
57
|
-
function resolveRouteComponent(input) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
});
|
|
63
|
-
} else {
|
|
64
|
-
resolve(input);
|
|
58
|
+
async function resolveRouteComponent(input) {
|
|
59
|
+
if (isLazyImport(input)) {
|
|
60
|
+
const module = await input();
|
|
61
|
+
if (!module) {
|
|
62
|
+
throw new Error('Failed to load route component: the lazy import resolved to nothing');
|
|
65
63
|
}
|
|
66
|
-
|
|
64
|
+
return module.default;
|
|
65
|
+
}
|
|
66
|
+
return input;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
@@ -112,12 +112,11 @@ export function getUserState(state) {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
export function updatedLocation() {
|
|
115
|
-
const pathname =
|
|
116
|
-
|
|
117
|
-
const hash = base.name === '#' ? '' : globalThis.location.hash;
|
|
115
|
+
const pathname = base.name === '#' ? location.hash.slice(1) : location.pathname;
|
|
116
|
+
const hash = base.name === '#' ? '' : location.hash;
|
|
118
117
|
return {
|
|
119
118
|
pathname,
|
|
120
|
-
search:
|
|
119
|
+
search: location.search,
|
|
121
120
|
state: getUserState(history.state),
|
|
122
121
|
hash,
|
|
123
122
|
};
|
|
@@ -161,7 +160,7 @@ export function parseSearch(value) {
|
|
|
161
160
|
if (typeof value === 'string') {
|
|
162
161
|
const searchParams = new URLSearchParams(value);
|
|
163
162
|
return Object.fromEntries(
|
|
164
|
-
[...searchParams
|
|
163
|
+
[...searchParams].map(([key, value]) => [key, parseSearchValue(value)]),
|
|
165
164
|
);
|
|
166
165
|
}
|
|
167
166
|
|
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
|
|
@@ -211,6 +227,13 @@ export type RouterApi<T extends Routes> = {
|
|
|
211
227
|
*/
|
|
212
228
|
preload<U extends Path<T>>(path: U): Promise<void>;
|
|
213
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Resolves the metadata for the given route path by merging meta from all ancestor route levels.
|
|
232
|
+
*
|
|
233
|
+
* @param path The route to resolve meta for.
|
|
234
|
+
*/
|
|
235
|
+
resolveMeta<U extends Path<T>>(path: U): RouteMeta;
|
|
236
|
+
|
|
214
237
|
route: {
|
|
215
238
|
/**
|
|
216
239
|
* An object containing the parameters of the current route.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SvelteURLSearchParams } from 'svelte/reactivity';
|
|
2
2
|
import { parseSearchValue } from './helpers/utils.js';
|
|
3
3
|
|
|
4
|
-
let searchParams = new SvelteURLSearchParams(
|
|
4
|
+
let searchParams = new SvelteURLSearchParams(location.search);
|
|
5
5
|
|
|
6
6
|
/** @type {import('./index.js').SearchParams} */
|
|
7
7
|
const shell = {
|
|
@@ -38,6 +38,7 @@ const shell = {
|
|
|
38
38
|
updateUrlSearchParams(options);
|
|
39
39
|
},
|
|
40
40
|
sort(options) {
|
|
41
|
+
// eslint-disable-next-line unicorn/require-array-sort-compare
|
|
41
42
|
searchParams.sort();
|
|
42
43
|
updateUrlSearchParams(options);
|
|
43
44
|
},
|
|
@@ -82,7 +83,7 @@ export function syncSearchParams(search) {
|
|
|
82
83
|
|
|
83
84
|
/** @param {{ replace?: boolean }} [options] */
|
|
84
85
|
function updateUrlSearchParams(options) {
|
|
85
|
-
const url = new URL(
|
|
86
|
+
const url = new URL(location.toString());
|
|
86
87
|
url.search = searchParams.toString();
|
|
87
|
-
|
|
88
|
+
history[options?.replace ? 'replaceState' : 'pushState']({}, '', url);
|
|
88
89
|
}
|
|
@@ -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';
|